Repository: Xlinlin/SpringCloud-Demo Branch: master Commit: 5efd083d427f Files: 742 Total size: 22.6 MB Directory structure: gitextract_ftqt9i7b/ ├── .gitattributes ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.md ├── SpringBoot-Admin/ │ ├── pom.xml │ ├── readme.md │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── spring/ │ │ └── boot/ │ │ └── admin/ │ │ └── SpringBootAdminApplication.java │ └── resources/ │ ├── application.yml │ └── bootstrap.yml ├── SpringBoot-Custom-Elasticsearch-Starter/ │ ├── Custom-Elasticsearch-Starter/ │ │ └── pom.xml │ ├── Custom-Elasticsearch-Starter-Autoconfig/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── elasticsearch/ │ │ │ └── start/ │ │ │ └── autoconfig/ │ │ │ ├── ElasticsearchAutoConfiguration.java │ │ │ └── properties/ │ │ │ ├── ElasticsearchProperties.java │ │ │ └── HostInfo.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── Custom-Elasticsearch-Starter-Example/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── xiao/ │ │ │ │ └── custom/ │ │ │ │ └── elasticsearch/ │ │ │ │ └── starter/ │ │ │ │ └── example/ │ │ │ │ └── ElasticsearchApplication.java │ │ │ └── resources/ │ │ │ ├── application.properties │ │ │ ├── application.yml │ │ │ ├── bootstrap.yml │ │ │ └── logback-spring.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── custom/ │ │ └── elasticsearch/ │ │ └── starter/ │ │ └── example/ │ │ └── ElasticsearchApplicationTest.java │ ├── Readme.md │ └── pom.xml ├── SpringBoot-Custom-Rest-Starter/ │ ├── Readme.md │ ├── SpringBoot-Custom-Rest-Autconfigure/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── rest/ │ │ │ └── starter/ │ │ │ └── autoconfigure/ │ │ │ ├── config/ │ │ │ │ ├── RestTemplateConfiguration.java │ │ │ │ └── properties/ │ │ │ │ ├── HttpPoolProperties.java │ │ │ │ └── OkHttpProperties.java │ │ │ ├── dto/ │ │ │ │ └── Request.java │ │ │ ├── interceptor/ │ │ │ │ └── RestInterceptor.java │ │ │ ├── log/ │ │ │ │ ├── annotation/ │ │ │ │ │ ├── RequestLog.java │ │ │ │ │ └── RequestLogAspect.java │ │ │ │ ├── dto/ │ │ │ │ │ └── HttpRequestLog.java │ │ │ │ └── service/ │ │ │ │ └── HttpRequestLogService.java │ │ │ ├── service/ │ │ │ │ ├── HttpClientService.java │ │ │ │ └── impl/ │ │ │ │ ├── HttpClientAsyncServiceImpl.java │ │ │ │ ├── HttpClientServiceImpl.java │ │ │ │ └── HttpRetryService.java │ │ │ └── util/ │ │ │ ├── RequestValidatorParamsUtil.java │ │ │ └── ThreadLocalUtil.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── SpringBoot-Custom-Rest-Example/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── xiao/ │ │ │ │ └── custom/ │ │ │ │ └── rest/ │ │ │ │ └── example/ │ │ │ │ ├── RestExampleApp.java │ │ │ │ └── log/ │ │ │ │ └── impl/ │ │ │ │ └── HttpLogServiceImpl.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── logback-spring.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── custom/ │ │ └── rest/ │ │ └── example/ │ │ ├── RestTemplateStarterAppTest.java │ │ ├── httpclient/ │ │ │ └── HttpClientTest.java │ │ └── template/ │ │ └── RestTemplateTest.java │ └── pom.xml ├── SpringBoot-Stock-Demo/ │ ├── doc/ │ │ ├── bootstrap.sh │ │ ├── stock.sql │ │ └── stock_demo_jmeter.jmx │ ├── pom.xml │ ├── readme.md │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── stock/ │ │ └── demo/ │ │ ├── StockDemoApplication.java │ │ ├── common/ │ │ │ └── StockOptTypeEnum.java │ │ ├── configure/ │ │ │ ├── DataSourceConfiguration.java │ │ │ └── RedissonConfiguration.java │ │ ├── entity/ │ │ │ ├── OrderDemo.java │ │ │ ├── StockChangeLodDemo.java │ │ │ └── StockDemo.java │ │ ├── mapper/ │ │ │ ├── OrderDemoMapper.java │ │ │ ├── StockChangeLodDemoMapper.java │ │ │ └── StockDemoMapper.java │ │ ├── rest/ │ │ │ └── StockRestService.java │ │ ├── service/ │ │ │ ├── StockDemoService.java │ │ │ └── impl/ │ │ │ └── StockDemoServiceImpl.java │ │ └── util/ │ │ ├── OrderNoUtil.java │ │ └── StockUtil.java │ └── resources/ │ ├── application.yml │ ├── com/ │ │ └── xiao/ │ │ └── stock/ │ │ └── demo/ │ │ └── mapper/ │ │ ├── OrderDemoMapper.xml │ │ ├── StockChangeLodDemoMapper.xml │ │ └── StockDemoMapper.xml │ └── logback-spring.xml ├── SpringCloud-Canal/ │ ├── doc/ │ │ └── ServerRunningMonitor源码注解.md │ ├── pom.xml │ ├── readme.md │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ ├── alibaba/ │ │ │ └── canal/ │ │ │ └── simple/ │ │ │ └── ClientSample.java │ │ └── xiao/ │ │ └── springcloud/ │ │ ├── CanalSimpleApplication.java │ │ ├── canal/ │ │ │ ├── CanalClientService.java │ │ │ └── CanalConfig.java │ │ └── disruptor/ │ │ ├── DataEvent.java │ │ ├── DataEventFactory.java │ │ ├── DisruptorConsumer.java │ │ ├── DisruptorExceptionHandler.java │ │ ├── DisruptorProducer.java │ │ ├── DisruptorThreadFactory.java │ │ ├── TableData.java │ │ └── service/ │ │ ├── DisruptorService.java │ │ └── impl/ │ │ └── DisruptorServiceImpl.java │ └── resources/ │ └── application.properties ├── SpringCloud-Common/ │ ├── README.md │ ├── pom.xml │ ├── script/ │ │ ├── auto_deploy.sh │ │ ├── bootstrap.sh │ │ └── remote_deploy.sh │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── xiao/ │ └── springcloud/ │ └── demo/ │ └── common/ │ ├── SkywalkingService.java │ ├── cache/ │ │ ├── README.md │ │ ├── code/ │ │ │ ├── FastJsonCodec.java │ │ │ └── SerializerObject.java │ │ ├── conf/ │ │ │ └── RedissonConfig.java │ │ ├── dto/ │ │ │ └── EntryDto.java │ │ ├── local/ │ │ │ └── SpringGuavaCacheConfig.java │ │ └── service/ │ │ ├── CacheService.java │ │ ├── DistributedService.java │ │ └── impl/ │ │ ├── CacheServiceRedisImpl.java │ │ └── DistributedServiceRedissonImpl.java │ ├── conf/ │ │ └── FeignConfiguration.java │ ├── disruptor/ │ │ ├── DataEventFactory.java │ │ ├── DisruptorConsumer.java │ │ ├── DisruptorExceptionHandler.java │ │ ├── DisruptorProducer.java │ │ ├── DisruptorThreadFactory.java │ │ ├── data/ │ │ │ ├── BasisData.java │ │ │ ├── DataEvent.java │ │ │ └── EventEnum.java │ │ ├── event/ │ │ │ └── ServiceEvent.java │ │ ├── readme.md │ │ └── service/ │ │ ├── DisruptorService.java │ │ └── impl/ │ │ └── DisruptorServiceImpl.java │ ├── eureka/ │ │ └── LoadBalancerAspect.java │ ├── exception/ │ │ ├── AbstractServiceException.java │ │ ├── CommonException.java │ │ └── CommonExceptionEnum.java │ ├── forkjoin/ │ │ ├── ForkjoinConfiguration.java │ │ ├── ForkjoinService.java │ │ └── task/ │ │ └── ForkjoinTask.java │ ├── gloab/ │ │ ├── interceptor/ │ │ │ ├── README.md │ │ │ ├── advice/ │ │ │ │ └── DefaultControllerAdvice.java │ │ │ ├── config/ │ │ │ │ ├── FastjsonConfig.java │ │ │ │ └── FeignConfig.java │ │ │ └── fegin/ │ │ │ ├── CommonFeignErrorDecoder.java │ │ │ ├── CommonFeignHeaderProcessInterceptor.java │ │ │ ├── DefaultCommonErrorAttributes.java │ │ │ ├── FeignBeanFactoryPostProcessor.java │ │ │ └── HttpContext.java │ │ └── response/ │ │ ├── ErrorResponseData.java │ │ ├── ResponseData.java │ │ └── SuccessResponseData.java │ ├── logaspect/ │ │ ├── LogAnnotation.java │ │ ├── LogAspect.java │ │ ├── LogInfo.java │ │ ├── LogService.java │ │ ├── README.md │ │ └── Slf4jLogService.java │ ├── sign/ │ │ ├── SignConstants.java │ │ ├── annotation/ │ │ │ ├── DisposeSign.java │ │ │ └── DisposeSignService.java │ │ ├── filter/ │ │ │ └── WrapperRequestFilter.java │ │ ├── readme.md │ │ ├── request/ │ │ │ └── BodyReaderHttpServletRequestWrapper.java │ │ ├── service/ │ │ │ ├── AppManagerService.java │ │ │ └── impl/ │ │ │ └── AppManagerServiceConfigImpl.java │ │ └── util/ │ │ ├── AsciiSortUtil.java │ │ ├── HttpRequestUtils.java │ │ └── SignUtil.java │ ├── util/ │ │ ├── CodeFormatConstants.java │ │ ├── DateUtils.java │ │ ├── ListPageUtil.java │ │ ├── StringLengthUtils.java │ │ ├── encode/ │ │ │ ├── AESEncryption.java │ │ │ ├── AESType.java │ │ │ ├── BinaryHelper.java │ │ │ ├── ByteUtils.java │ │ │ ├── HMACUtil.java │ │ │ └── Md5DigestUtil.java │ │ └── image/ │ │ ├── ImageDHashUtil.java │ │ └── ImagePHashUtil.java │ └── validator/ │ ├── CodePrefix.java │ ├── ParamAspect.java │ ├── ParamValidator.java │ ├── ParamVerify.java │ ├── Validator.java │ └── VerifyConstants.java ├── SpringCloud-ConfigCenter/ │ ├── Readme.txt │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── configure/ │ │ └── ConfigureApplication.java │ └── resources/ │ └── application.yml ├── SpringCloud-Configure/ │ ├── README.txt │ ├── consumer/ │ │ ├── springcloud-dev.properties │ │ ├── springcloud-dev.yml │ │ ├── springcloud-prod.properties │ │ ├── springcloud-prod.yml │ │ ├── springcloud-test.properties │ │ └── springcloud-test.yml │ ├── eureka-server/ │ │ ├── eureka-server-dev.properties │ │ ├── eureka-server-dev.yml │ │ ├── eureka-server-test.properties │ │ └── eureka-server-test.yml │ ├── pom.xml │ └── redisson/ │ ├── redission-dev.yml │ ├── redission-sentinel-dev.yml │ ├── redisson-cloud.yml │ ├── redisson-cluster-dev.yml │ └── redisson-cluster-test.yml ├── SpringCloud-Consumer/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── skywalking/ │ │ └── consumer/ │ │ ├── ConsumerApp.java │ │ ├── common/ │ │ │ ├── CommonConstants.java │ │ │ ├── CommonException.java │ │ │ ├── ExceptionEnum.java │ │ │ ├── advice/ │ │ │ │ ├── GlobalExceptionAdvice.java │ │ │ │ └── UnifiedReturnAdvice.java │ │ │ └── response/ │ │ │ ├── ErrorResponseData.java │ │ │ ├── ResponseData.java │ │ │ └── SuccessResponseData.java │ │ ├── controller/ │ │ │ └── FeignContoller.java │ │ ├── feign/ │ │ │ ├── FeignService.java │ │ │ └── impl/ │ │ │ ├── FQA │ │ │ └── FeignServiceImpl.java │ │ └── ribbon/ │ │ └── RibbonService.java │ └── resources/ │ ├── application.yml │ └── bootstrap.yml ├── SpringCloud-Custom-ConfigCenter/ │ ├── README.MD │ ├── custom-config-client/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── client/ │ │ │ ├── configuration/ │ │ │ │ ├── ConfigClientAutoConfiguration.java │ │ │ │ ├── ConfigClientHealthProperties.java │ │ │ │ ├── ConfigClientProperties.java │ │ │ │ ├── ConfigClientStateHolder.java │ │ │ │ ├── ConfigClientWatch.java │ │ │ │ ├── ConfigServerHealthIndicator.java │ │ │ │ ├── ConfigServiceBootstrapConfiguration.java │ │ │ │ ├── ConfigServicePropertySourceLocator.java │ │ │ │ ├── DiscoveryClientConfigServiceBootstrapConfiguration.java │ │ │ │ └── RetryProperties.java │ │ │ ├── environment/ │ │ │ │ ├── Environment.java │ │ │ │ └── PropertySource.java │ │ │ ├── netty/ │ │ │ │ ├── client/ │ │ │ │ │ └── NettyClient.java │ │ │ │ ├── coder/ │ │ │ │ │ ├── ProtoDecoder.java │ │ │ │ │ └── ProtoEncoder.java │ │ │ │ ├── dto/ │ │ │ │ │ ├── CommandEnum.java │ │ │ │ │ └── Message.java │ │ │ │ ├── factory/ │ │ │ │ │ ├── CoderFactory.java │ │ │ │ │ └── NamedThreadFactory.java │ │ │ │ ├── handler/ │ │ │ │ │ └── ServiceHandler.java │ │ │ │ └── util/ │ │ │ │ ├── ProtostuffUtil.java │ │ │ │ └── RemotingUtil.java │ │ │ └── refresh/ │ │ │ ├── api/ │ │ │ │ └── RefreshController.java │ │ │ ├── component/ │ │ │ │ └── RefreshBeanConfig.java │ │ │ └── service/ │ │ │ ├── ConfigRefreshService.java │ │ │ └── impl/ │ │ │ └── ConfigRefreshServiceImpl.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── custom-config-dependencies/ │ │ └── pom.xml │ ├── custom-config-pojo/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── pojo/ │ │ │ ├── common/ │ │ │ │ └── BaseQuery.java │ │ │ ├── dto/ │ │ │ │ ├── ApplicationConfigDto.java │ │ │ │ ├── ApplicationDto.java │ │ │ │ ├── ClientHostInfoDto.java │ │ │ │ ├── ConfigItemDto.java │ │ │ │ ├── ConfigItemGroupDto.java │ │ │ │ ├── RegionDto.java │ │ │ │ └── ServerHostConfigDto.java │ │ │ ├── entity/ │ │ │ │ ├── Application.java │ │ │ │ ├── ApplicationConfig.java │ │ │ │ ├── ApplicationItemGroupRelation.java │ │ │ │ ├── AuthUser.java │ │ │ │ ├── ClientApplication.java │ │ │ │ ├── ClientHostInfo.java │ │ │ │ ├── ClientInfo.java │ │ │ │ ├── ConfigItem.java │ │ │ │ ├── ConfigItemGroup.java │ │ │ │ ├── ConfigItemGroupRelation.java │ │ │ │ ├── Region.java │ │ │ │ ├── Role.java │ │ │ │ └── ServerHostConfig.java │ │ │ ├── mapper/ │ │ │ │ ├── ApplicationConfigMapper.java │ │ │ │ ├── ApplicationItemGroupRelationMapper.java │ │ │ │ ├── ApplicationMapper.java │ │ │ │ ├── AuthMapper.java │ │ │ │ ├── ClientApplicationMapper.java │ │ │ │ ├── ClientHostInfoMapper.java │ │ │ │ ├── ConfigItemGroupMapper.java │ │ │ │ ├── ConfigItemGroupRelationMapper.java │ │ │ │ ├── ConfigItemMapper.java │ │ │ │ ├── RegionMapper.java │ │ │ │ └── ServerHostConfigMapper.java │ │ │ └── query/ │ │ │ ├── AppQuery.java │ │ │ ├── ApplicationConfigQuery.java │ │ │ ├── ClientHostInfoQuery.java │ │ │ ├── ConfigItemGroupQuery.java │ │ │ ├── ConfigItemQuery.java │ │ │ ├── RegionQuery.java │ │ │ └── ServerHostConfigQuery.java │ │ └── resources/ │ │ └── com/ │ │ └── xiao/ │ │ └── custom/ │ │ └── config/ │ │ └── pojo/ │ │ └── mapper/ │ │ ├── ApplicationConfigMapper.xml │ │ ├── ApplicationItemGroupRelationMapper.xml │ │ ├── ApplicationMapper.xml │ │ ├── AuthMapper.xml │ │ ├── ClientApplicationMapper.xml │ │ ├── ClientHostInfoMapper.xml │ │ ├── ConfigItemGroupMapper.xml │ │ ├── ConfigItemGroupRelationMapper.xml │ │ ├── ConfigItemMapper.xml │ │ ├── RegionMapper.xml │ │ └── ServerHostConfigMapper.xml │ ├── custom-config-server/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── server/ │ │ │ ├── ConfigerCenterApplication.java │ │ │ ├── annotation/ │ │ │ │ └── CustomEnableConfigServer.java │ │ │ ├── config/ │ │ │ │ └── CustomEnvironmentRepositoryConfiguration.java │ │ │ ├── controller/ │ │ │ │ └── RefreshController.java │ │ │ ├── environment/ │ │ │ │ └── CustomEnvironmentRepository.java │ │ │ ├── manager/ │ │ │ │ ├── ClientManagerService.java │ │ │ │ ├── SqlConstants.java │ │ │ │ └── impl/ │ │ │ │ └── ClientManagerServiceDbImpl.java │ │ │ ├── netty/ │ │ │ │ ├── coder/ │ │ │ │ │ ├── ProtoDecoder.java │ │ │ │ │ └── ProtoEncoder.java │ │ │ │ ├── dto/ │ │ │ │ │ ├── CommandEnum.java │ │ │ │ │ └── Message.java │ │ │ │ ├── factory/ │ │ │ │ │ ├── CoderFactory.java │ │ │ │ │ └── NamedThreadFactory.java │ │ │ │ ├── handler/ │ │ │ │ │ └── ServiceHandler.java │ │ │ │ ├── manager/ │ │ │ │ │ ├── Connection.java │ │ │ │ │ └── ConnectionManager.java │ │ │ │ ├── server/ │ │ │ │ │ └── NettyServer.java │ │ │ │ └── util/ │ │ │ │ ├── NettyConfig.java │ │ │ │ ├── NettyEventLoopUtil.java │ │ │ │ ├── ProtostuffUtil.java │ │ │ │ └── RemotingUtil.java │ │ │ └── service/ │ │ │ ├── RefreshService.java │ │ │ ├── RepositoryService.java │ │ │ └── impl/ │ │ │ ├── JdbcRepositoryServiceImpl.java │ │ │ └── RefreshServiceImpl.java │ │ └── resources/ │ │ └── application.yml │ ├── custom-config-service/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── service/ │ │ │ ├── ConfigServiceApplication.java │ │ │ ├── api/ │ │ │ │ ├── ApplicationApi.java │ │ │ │ ├── AuthApi.java │ │ │ │ ├── ClientHostApi.java │ │ │ │ ├── ConfigItemApi.java │ │ │ │ ├── ConfigItemGroupApi.java │ │ │ │ ├── RegionApi.java │ │ │ │ └── ServerHostConfigApi.java │ │ │ ├── feign/ │ │ │ │ └── RefreshFeign.java │ │ │ └── service/ │ │ │ ├── ApplicationItemGroupRelationService.java │ │ │ ├── ApplicationService.java │ │ │ ├── AuthService.java │ │ │ ├── ClientHostService.java │ │ │ ├── ConfigItemGroupRelationService.java │ │ │ ├── ConfigItemGroupService.java │ │ │ ├── ConfigItemService.java │ │ │ ├── RegionService.java │ │ │ ├── ServerHostConfigService.java │ │ │ └── impl/ │ │ │ ├── ApplicationItemGroupRelationServiceImpl.java │ │ │ ├── ApplicationServiceImpl.java │ │ │ ├── AuthServiceImpl.java │ │ │ ├── ClientHostServiceImpl.java │ │ │ ├── ConfigItemGroupRelationServiceImpl.java │ │ │ ├── ConfigItemGroupServiceImpl.java │ │ │ ├── ConfigItemServiceImpl.java │ │ │ ├── RegionServiceImpl.java │ │ │ └── ServerHostConfigServiceImpl.java │ │ └── resources/ │ │ ├── application.yml │ │ └── bootstrap.yml │ ├── custom-config-simple/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── simple/ │ │ │ ├── ConfigClientApplication.java │ │ │ ├── datasource/ │ │ │ │ └── DataSourceConfigure.java │ │ │ └── demo/ │ │ │ └── ControllerDemo.java │ │ └── resources/ │ │ ├── bootstrap.yml │ │ └── logback-spring.xml │ ├── custom-config-web/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── custom/ │ │ │ └── config/ │ │ │ └── web/ │ │ │ ├── ConfigCenterWebApplication.java │ │ │ ├── auth/ │ │ │ │ ├── AuthContants.java │ │ │ │ ├── config/ │ │ │ │ │ ├── HttpSessionConfig.java │ │ │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ │ │ ├── JwtAuthenticationTokenFilter.java │ │ │ │ │ ├── RestAccessDeniedHandler.java │ │ │ │ │ └── WebSecurityConfig.java │ │ │ │ ├── controller/ │ │ │ │ │ └── AuthController.java │ │ │ │ ├── entity/ │ │ │ │ │ ├── ResponseUserToken.java │ │ │ │ │ ├── User.java │ │ │ │ │ └── UserDetail.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── CustomException.java │ │ │ │ │ └── DefaultExceptionHandler.java │ │ │ │ ├── service/ │ │ │ │ │ ├── AuthUserService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AuthUserServiceImpl.java │ │ │ │ │ └── ConfigUserDetailsServiceImpl.java │ │ │ │ └── util/ │ │ │ │ ├── JwtUtils.java │ │ │ │ ├── PageResult.java │ │ │ │ ├── ResultCode.java │ │ │ │ └── ResultJson.java │ │ │ ├── commo/ │ │ │ │ └── Constants.java │ │ │ ├── config/ │ │ │ │ └── AppControllerAdvice.java │ │ │ ├── controller/ │ │ │ │ ├── ClientInfoController.java │ │ │ │ ├── ConfigGroupController.java │ │ │ │ ├── ConfigItemController.java │ │ │ │ ├── IndexController.java │ │ │ │ ├── RegionController.java │ │ │ │ ├── ServerHostConfigController.java │ │ │ │ └── app/ │ │ │ │ ├── AppManagerController.java │ │ │ │ └── vo/ │ │ │ │ ├── ApplicationVo.java │ │ │ │ └── RegionVo.java │ │ │ ├── dto/ │ │ │ │ └── ServerHostConfigDto.java │ │ │ ├── exception/ │ │ │ │ └── ExceptionEnum.java │ │ │ └── feign/ │ │ │ ├── app/ │ │ │ │ └── ApplicationFeign.java │ │ │ ├── auth/ │ │ │ │ └── AuthFeign.java │ │ │ ├── client/ │ │ │ │ └── ClientInfoFeign.java │ │ │ ├── config/ │ │ │ │ ├── ConfigGroupFeign.java │ │ │ │ └── ConfigItemFeign.java │ │ │ ├── region/ │ │ │ │ └── RegionFeign.java │ │ │ └── server/ │ │ │ └── ServerHostConfigFeign.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── static/ │ │ ├── mock/ │ │ │ ├── index.js │ │ │ └── mock.js │ │ ├── pages/ │ │ │ ├── app/ │ │ │ │ ├── add.html │ │ │ │ ├── add.js │ │ │ │ ├── app.html │ │ │ │ ├── app.js │ │ │ │ ├── configgroup.html │ │ │ │ ├── configgroup.js │ │ │ │ ├── detail.html │ │ │ │ ├── detail.js │ │ │ │ ├── refconfiggroup.html │ │ │ │ └── refconfiggroup.js │ │ │ ├── client/ │ │ │ │ ├── client.html │ │ │ │ └── client.js │ │ │ ├── config/ │ │ │ │ ├── configitem.html │ │ │ │ └── configitem.js │ │ │ ├── configgroup/ │ │ │ │ ├── configgroup.html │ │ │ │ ├── configgroup.js │ │ │ │ ├── detail.html │ │ │ │ ├── detail.js │ │ │ │ ├── refdetail.html │ │ │ │ └── refdetail.js │ │ │ ├── error/ │ │ │ │ ├── 401.html │ │ │ │ ├── 404.html │ │ │ │ └── 500.html │ │ │ ├── home/ │ │ │ │ ├── home.html │ │ │ │ └── home.js │ │ │ ├── index/ │ │ │ │ ├── compents.js │ │ │ │ ├── index.css │ │ │ │ ├── index.html │ │ │ │ └── index.js │ │ │ ├── login/ │ │ │ │ └── login.html │ │ │ ├── region/ │ │ │ │ ├── region.html │ │ │ │ └── region.js │ │ │ ├── server/ │ │ │ │ ├── server.js │ │ │ │ └── serverlist.html │ │ │ ├── support/ │ │ │ │ ├── code_check.html │ │ │ │ └── support.html │ │ │ └── template/ │ │ │ ├── detail.html │ │ │ ├── detail.js │ │ │ ├── template.html │ │ │ └── template.js │ │ └── plugin/ │ │ ├── common/ │ │ │ ├── common.js │ │ │ └── env.js │ │ ├── element/ │ │ │ ├── js/ │ │ │ │ └── index.js │ │ │ └── styles/ │ │ │ ├── fonts/ │ │ │ │ ├── iconfont.css │ │ │ │ └── iconfont.js │ │ │ └── index.css │ │ ├── iview/ │ │ │ ├── js/ │ │ │ │ └── iview-editor.js │ │ │ └── styles/ │ │ │ ├── iview-editor.css │ │ │ └── iview.css │ │ ├── jquery/ │ │ │ ├── 3.3.1/ │ │ │ │ └── jquery.js │ │ │ └── jquery.history.js │ │ └── mock/ │ │ └── mock-min.js │ ├── custom-starter-config/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.provides │ ├── doc/ │ │ └── configMysql.sql │ └── pom.xml ├── SpringCloud-Custom-RestTemplate-Stater/ │ ├── Readme.md │ ├── SpringCloud-RestTemplate-Stater/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── springcloud/ │ │ │ └── rest/ │ │ │ └── stater/ │ │ │ └── autoconfig/ │ │ │ ├── common/ │ │ │ │ ├── dto/ │ │ │ │ │ └── Request.java │ │ │ │ ├── interceptor/ │ │ │ │ │ └── RestInterceptor.java │ │ │ │ ├── log/ │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── RequestLog.java │ │ │ │ │ │ └── RequestLogAspect.java │ │ │ │ │ ├── dto/ │ │ │ │ │ │ └── HttpRequestLog.java │ │ │ │ │ └── service/ │ │ │ │ │ └── HttpRequestLogService.java │ │ │ │ ├── service/ │ │ │ │ │ ├── HttpClientService.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── HttpClientServiceImpl.java │ │ │ │ └── util/ │ │ │ │ └── ThreadLocalUtil.java │ │ │ └── config/ │ │ │ ├── RestTemplateConfiguration.java │ │ │ └── properties/ │ │ │ ├── HttpPoolProperties.java │ │ │ └── OkHttpProperties.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring.factories │ ├── SpringCloud-RestTemplate-Stater-Example/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── xiao/ │ │ │ │ └── springcloud/ │ │ │ │ └── rest/ │ │ │ │ ├── RestTemplateStaterApp.java │ │ │ │ └── log/ │ │ │ │ └── impl/ │ │ │ │ └── HttpLogServiceiImpl.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── logback-spring.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── rest/ │ │ ├── RestTemplateStarterAppTest.java │ │ └── httpclient/ │ │ └── HttpClientTest.java │ └── pom.xml ├── SpringCloud-Demo-Doc/ │ ├── ES/ │ │ ├── ElasticSearch后台操作(使用)手册1.0.docx │ │ ├── ElasticSearch安装手册1.0.docx │ │ ├── Elasticsearch IK分词器扩展说明.docx │ │ └── es索引创建1.1.jmx │ ├── docker/ │ │ ├── batch_del.sh │ │ ├── docker-build.sh │ │ ├── docker-monitor.json │ │ ├── docker-monitor.yml │ │ └── docker-swarm-springcloud.md │ ├── gitlab/ │ │ ├── Gitlab使用手册.docx │ │ ├── Gitlab安装手册.docx │ │ ├── gitlab-ci.yml │ │ ├── sonar_analyze.sh │ │ └── sonar_preview.sh │ ├── k8s/ │ │ ├── CronJob.yaml │ │ ├── DaemonSet.yaml │ │ ├── Job.yaml │ │ ├── K8S Linux Centos 7安装.docx │ │ ├── Pod.yaml │ │ ├── ReplicaSet.yaml │ │ ├── ReplicationController.yaml │ │ ├── k8s-master-bootstrap.sh │ │ └── k8s-node-bootstrap.sh │ ├── kafka+elk/ │ │ ├── ELK安装文档.docx │ │ ├── ELK日志logstash解析JSON嵌套.md │ │ ├── ElasticSearch安装手册1.0.docx │ │ ├── Kafka安装指导手册.docx │ │ ├── elk+springboot+kafka日志跟踪配置.docx │ │ └── 使用logback-kafka导致服务之间调用多1分钟之坑.md │ ├── linux/ │ │ └── Linux-netstat命令.md │ ├── pom.xml │ ├── rxjava/ │ │ └── RxJavaHelloWorld.MD │ ├── spring-cloud/ │ │ ├── README.md │ │ ├── SpringCloud-FQA.md │ │ ├── 源码解析专栏/ │ │ │ ├── Spring Cloud Config Client加载配置源码分析.md │ │ │ ├── Spring Cloud Config 是如何实现热更新的 │ │ │ ├── Spring Cloud Netflix Eureka多网卡环境下Eureka服务注册IP选择问题.md │ │ │ ├── Spring Cloud Netflix Eureka源码导读与原理分析.md │ │ │ ├── eureka分区的深入讲解.md │ │ │ └── spring-cloud-feign源码深度解析.md │ │ └── 链路跟踪/ │ │ └── spring-cloud+skywalking链路跟踪.docx │ ├── sql/ │ │ └── 行政区域带经纬度.sql │ ├── 其他个人总结/ │ │ ├── Jmeter分布式压测.docx │ │ ├── openresty.conf │ │ ├── stock.lua │ │ ├── 参数校验注解使用指南.docx │ │ ├── 微信小程序MQTT协议通信.docx │ │ └── 聊一聊微服务.pptx │ └── 持续集成/ │ ├── Gitlab+P3C-PMD(Aliyun)标准化你团队的代码.docx │ ├── Gitlab安装手册.docx │ ├── Gitlab的Hooks(钩子)做Push代码检测.docx │ ├── auto_deploy.sh │ ├── bootstrap.sh │ ├── example-gitlab-ci.yml │ ├── gitlab-hooks/ │ │ ├── checkStyle模板 │ │ ├── pre-receive │ │ ├── pre-receive(checkstyle版本,使用请更名为pre-receive) │ │ └── pre-receive(p3c-pmd版,使用请更名为pre-receive) │ ├── monitor-jstat.sh │ ├── remote_deploy.sh │ ├── 代码质量监控体系方案.pptx │ ├── 代码质量监控平台.docx │ ├── 代码质量监控平台安装使用手册.docx │ ├── 持续集成之Jenkins安装和使用指导手册.docx │ └── 持续集成之Sonarqube安装和使用指导手册.docx ├── SpringCloud-Docker/ │ ├── Dockerfile │ ├── README.md │ ├── docker-build.sh │ ├── pom.xml │ ├── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── springboot/ │ │ │ └── docker/ │ │ │ ├── OmniZipkinServerApplication.java │ │ │ └── controller/ │ │ │ └── DockerDemo.java │ │ └── resources/ │ │ └── application.yml │ └── zipkin-server-docker.sh ├── SpringCloud-Eureka/ │ ├── pom.xml │ ├── readme.txt │ ├── sql/ │ │ └── config.sql │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── skywalking/ │ │ └── center/ │ │ └── Application.java │ └── resources/ │ ├── application.yml │ └── bootstrap.yml ├── SpringCloud-Gateway/ │ ├── README.txt │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── skywalking/ │ │ └── gateway/ │ │ └── zuul/ │ │ └── GatewayApplication.java │ └── resources/ │ └── application.yml ├── SpringCloud-Hystrix-Demo/ │ ├── README.MD │ ├── SpringCloud-Hystrix-Consumer/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── hystrix/ │ │ │ └── demo/ │ │ │ └── consumer/ │ │ │ ├── ConsumerApplication.java │ │ │ ├── api/ │ │ │ │ └── ConsumerRestService.java │ │ │ ├── common/ │ │ │ │ └── CacheConstants.java │ │ │ ├── config/ │ │ │ │ ├── CaffeineCacheConfiguration.java │ │ │ │ ├── FeignConfiguration.java │ │ │ │ └── HystrixCacheConfiguration.java │ │ │ ├── dynamic/ │ │ │ │ ├── DynamicConfigSource.java │ │ │ │ └── InitHystrixConfiguration.java │ │ │ ├── feign/ │ │ │ │ ├── ProducerFeign.java │ │ │ │ └── impl/ │ │ │ │ ├── ProducerFeignFactory.java │ │ │ │ └── ProducerFeignFallBack.java │ │ │ └── filter/ │ │ │ └── HystrixCacheFilter.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── application.yml │ │ └── bootstrap.yml │ ├── SpringCloud-Hystrix-Eureka/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── hystrix/ │ │ │ └── demo/ │ │ │ └── eureka/ │ │ │ └── EurekaApplication.java │ │ └── resources/ │ │ └── bootstrap.yml │ ├── SpringCloud-Hystrix-Producer/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── hystrix/ │ │ │ └── demo/ │ │ │ └── producer/ │ │ │ ├── ProducerApplication.java │ │ │ ├── api/ │ │ │ │ └── ProducerRestService.java │ │ │ └── service/ │ │ │ ├── ProducerService.java │ │ │ └── impl/ │ │ │ └── ProducerServiceImpl.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── application.yml │ │ └── bootstrap.yml │ └── pom.xml ├── SpringCloud-Kafka-Elk/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── demo/ │ │ └── kafka/ │ │ └── elk/ │ │ ├── ElkKafkaApplication.java │ │ └── kafka/ │ │ ├── KafkaProducerTest.java │ │ └── LogCompent.java │ └── resources/ │ ├── application.yml │ ├── bootstrap.yml │ └── logback-spring.xml ├── SpringCloud-MQTT/ │ ├── Readme.MD │ ├── doc/ │ │ ├── mqtt.conf │ │ ├── nginx.conf │ │ └── wss.conf │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── skywalking/ │ │ └── mqtt/ │ │ ├── ClientCallback.java │ │ ├── MqttServiceApp.java │ │ ├── MqttTestClient.java │ │ ├── MvcController.java │ │ ├── PurTrustManager.java │ │ └── TopicProducerTest.java │ ├── resources/ │ │ └── application.yml │ └── webapp/ │ └── WEB-INF/ │ ├── js/ │ │ ├── crypto-js.js │ │ ├── layer/ │ │ │ ├── layer.js │ │ │ ├── mobile/ │ │ │ │ ├── layer.js │ │ │ │ └── need/ │ │ │ │ └── layer.css │ │ │ └── skin/ │ │ │ └── default/ │ │ │ └── layer.css │ │ └── mqttws31-min.js │ └── jsp/ │ └── mqtt_client.jsp ├── SpringCloud-Mybatis/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── mybatis/ │ │ └── generator/ │ │ └── plugin/ │ │ └── LombokPlugin.java │ └── resources/ │ └── generatorConfig.xml ├── SpringCloud-Provider/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── skywalking/ │ │ └── provider/ │ │ ├── ProviderApp.java │ │ ├── controller/ │ │ │ └── SkywalkingController.java │ │ └── local/ │ │ └── cache/ │ │ └── SpringGuavaCacheConfig.java │ └── resources/ │ └── application.yml ├── SpringCloud-Quartz-JobService/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── job/ │ │ ├── JobServiceApplication.java │ │ ├── config/ │ │ │ ├── JobConfig.java │ │ │ └── TaskSchedulerFactory.java │ │ ├── entity/ │ │ │ └── TaskConfigDocument.java │ │ ├── job/ │ │ │ └── ServiceTaskExecuteJob.java │ │ ├── quartz/ │ │ │ └── JobManager.java │ │ └── util/ │ │ └── CronExpUtil.java │ └── resources/ │ ├── application.yml │ └── bootstrap.yml ├── SpringCloud-Redisson/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── spring/ │ │ │ └── cloud/ │ │ │ └── redisson/ │ │ │ ├── SpringDataRedissonApplication.java │ │ │ └── config/ │ │ │ └── SpringDataRedissonConfig.java │ │ └── resources/ │ │ ├── application.yml │ │ └── redisson.yml │ └── test/ │ └── java/ │ └── com/ │ └── xiao/ │ └── spring/ │ └── cloud/ │ └── redisson/ │ └── SpringDataRedissonApplicationTest.java ├── SpringCloud-SearchService/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── spring/ │ │ │ └── cloud/ │ │ │ └── search/ │ │ │ ├── SearchApplication.java │ │ │ ├── dto/ │ │ │ │ ├── ElasticSearchDoc.java │ │ │ │ ├── PaginationDo.java │ │ │ │ ├── SearchBrandDo.java │ │ │ │ ├── SearchCategoryDo.java │ │ │ │ ├── SearchCommoPropOptionDto.java │ │ │ │ ├── SearchCommodityPropertyDo.java │ │ │ │ ├── SearchCommodityResultDo.java │ │ │ │ ├── SearchLogDo.java │ │ │ │ ├── SearchMenusDo.java │ │ │ │ ├── SearchRequestDo.java │ │ │ │ ├── SearchResultDo.java │ │ │ │ ├── SearchShopWeightDto.java │ │ │ │ └── ShopPriceRangeDto.java │ │ │ ├── es/ │ │ │ │ ├── client/ │ │ │ │ │ └── ElasticSearchClient.java │ │ │ │ ├── common/ │ │ │ │ │ ├── AnalyzeType.java │ │ │ │ │ ├── ESConstants.java │ │ │ │ │ ├── OrderField.java │ │ │ │ │ └── SearchException.java │ │ │ │ ├── log/ │ │ │ │ │ ├── ISearchLogService.java │ │ │ │ │ ├── impl/ │ │ │ │ │ │ └── SearchLogServiceImpl.java │ │ │ │ │ └── thread/ │ │ │ │ │ ├── BatchSaveSearchLogThread.java │ │ │ │ │ ├── SearchLogThread.java │ │ │ │ │ ├── SearchLogThreadPool.java │ │ │ │ │ └── SearchThreadFactory.java │ │ │ │ └── service/ │ │ │ │ ├── SearchManagerEsImpl.java │ │ │ │ └── SearchServiceEsImpl.java │ │ │ ├── rest/ │ │ │ │ ├── SearchManagerRestService.java │ │ │ │ └── SearchRestService.java │ │ │ └── service/ │ │ │ ├── SearchManangerService.java │ │ │ └── SearchService.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── logback-spring.xml │ └── test/ │ └── java/ │ └── com/ │ └── xiao/ │ └── springcloud/ │ └── test/ │ ├── SearchApplicationTest.java │ ├── SearchManagerTest.java │ ├── SearchTest.java │ └── cache/ │ └── RedisCacheTest.java ├── SpringCloud-Sentinel/ │ ├── Readme.MD │ ├── SpringCloud-Sentinel-Consumer/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── springcloud/ │ │ │ └── sentinel/ │ │ │ └── consumer/ │ │ │ ├── ConsumerApplication.java │ │ │ ├── api/ │ │ │ │ └── ConsumerRestService.java │ │ │ ├── config/ │ │ │ │ └── FeignConfiguration.java │ │ │ └── feign/ │ │ │ ├── ProducerFeign.java │ │ │ └── fallback/ │ │ │ └── ProducerFeignFallBack.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── application.yml │ │ └── bootstrap.yml │ ├── SpringCloud-Sentinel-Eureka/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com.xiao.springcloud.sentinel.eureka/ │ │ │ └── EurekaApplication.java │ │ └── resources/ │ │ └── bootstrap.yml │ ├── SpringCloud-Sentinel-Producer/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── xiao/ │ │ │ └── springcloud/ │ │ │ └── sentinel/ │ │ │ └── producer/ │ │ │ ├── ProducerApplication.java │ │ │ ├── api/ │ │ │ │ └── ProducerRestService.java │ │ │ └── service/ │ │ │ ├── ProducerService.java │ │ │ └── impl/ │ │ │ └── ProducerServiceImpl.java │ │ └── resources/ │ │ ├── application.properties │ │ ├── application.yml │ │ └── bootstrap.yml │ ├── dashbord/ │ │ └── readme.md │ └── pom.xml ├── SpringCloud-Sharding-Sphere/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── purcotton/ │ │ └── sharding/ │ │ └── sphere/ │ │ └── demo/ │ │ ├── SpringBootStarterExample.java │ │ ├── entity/ │ │ │ ├── Order.java │ │ │ └── OrderItem.java │ │ ├── repository/ │ │ │ ├── CommonRepository.java │ │ │ ├── OrderItemRepository.java │ │ │ └── OrderRepository.java │ │ └── service/ │ │ ├── BasisCommonService.java │ │ ├── CommonService.java │ │ └── impl/ │ │ └── SpringPojoServiceImpl.java │ └── resources/ │ ├── META-INF/ │ │ ├── mappers/ │ │ │ ├── OrderItemMapper.xml │ │ │ └── OrderMapper.xml │ │ └── mybatis-config.xml │ ├── application-sharding-databases.properties │ ├── application.properties │ └── logback.xml ├── SpringCloud-ZipkinServer/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── xiao/ │ │ └── springcloud/ │ │ └── zs/ │ │ └── ZipkinServerApplication.java │ └── resources/ │ ├── application.properties │ └── application.yml ├── SpringCloud-Zookeeper/ │ ├── pom.xml │ └── readme.md └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.js linguist-language=Java *.css linguist-language=Java *.html linguist-language=Java ================================================ FILE: .gitignore ================================================ ## .gitignore for Grails 1.2 and 1.3 # .gitignore for maven target/ *.releaseBackup # web application files #/web-app/WEB-INF # IDE support files /.classpath /.launch /.project /.settings /*.launch /*.tmproj /ivy* /eclipse # default HSQL database files for production mode /prodDb.* # general HSQL database files *Db.properties *Db.script # logs /stacktrace.log /test/reports /logs *.log *.log.* # project release file /*.war # plugin release file /*.zip /*.zip.sha1 # older plugin install locations /plugins /web-app/plugins /web-app/WEB-INF/classes # "temporary" build files target/ out/ build/ # other *.iws #.gitignore for java *.class # Package Files # *.jar *.war *.ear ## .gitignore for eclipse *.pydevproject .project .metadata bin/** tmp/** tmp/**/* *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ## .gitignore for intellij *.iml *.ipr *.iws .idea/ ## .gitignore for linux .* !.gitignore !.gitattributes !.editorconfig !.eslintrc !.travis.yml *~ ## .gitignore for windows # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ ## .gitignore for mac os x .DS_Store .AppleDouble .LSOverride Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes ## hack for graddle wrapper !wrapper/*.jar !**/wrapper/*.jar ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 84226733@qq.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2021 Linlin xiao 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. ================================================ FILE: README.md ================================================ # spring-cloud-demo ## 不定期更新与记录在springcloud开发中所遇到的坑以及解决方法 ## 说明:由于版本不兼容,很多核心、重点的技术难题都以单独的项目形式提交,并同时更新到csdn,如有需要请关注:https://blog.csdn.net/xiaoll880214?type=blog 初始化添加 1. spring-cloud skywalking demo 2. add mqtt suports 3. add 微信小程序 suports mqtt 4. kafka elk支持 20180809 1. 更新 代码结构 2. 新增git配置中心 3. 新增kafka elk demo配置以及文档 4. 文档结构整理 5. 分离注册中心和配置中心 7. 添加zipkin服务跟踪 20180905 1. 自定义注解实现aop日志 2. 自定义注解实现实体类参数校验 3. 添加mybatis自定生成映射实体类、mapper等 4. 添加全局异常处理 5. 添加fegin自定义数据解析 20180907 1. 添加注解,作为参数校验入口 20180910 1. 解决服务之间调用fegin+hystrix 熔断异常拦截处理 20180914 1. 服务调用之间的rest请求,参数为对象时需要添加@RequestBody注解 ``eg: saveRegionCity(@RequestBody RegionCityDto regionCityDto)`` 2. 服务间调用接口的返回值,不能使接口返回,必须要使用实现类返回,fegin客户端获取不到数据返回Null `eg: public User getUser(@RequestBody UserQuery query); User必须为实现类,不能为接口` 3. 添加fastjson解析,解决部分调用对象内包含对象传值为空问题 20180921 1. Doc 目录结构调整 2. 记录[elk+kafka+logback服务之间调用多1分钟时间之坑](https://github.com/Xlinlin/spring-cloud-demo/blob/master/SpringCloud-Demo-Doc/kafka%2Belk/使用logback-kafka导致服务之间调用多1分钟之坑.md) 20180927 1. [elk+logstash+logback解析嵌套json数据](https://github.com/Xlinlin/spring-cloud-demo/blob/master/SpringCloud-Demo-Doc/kafka%2Belk/ELK%E6%97%A5%E5%BF%97logstash%E8%A7%A3%E6%9E%90JSON%E5%B5%8C%E5%A5%97.md) 20180930 1. 添加guava+spring-cache本地缓存实现,[参考入口](https://blog.csdn.net/mafei6827/article/details/80868931) 2. 记录springcloud 1.x版本解决feignclient下requestmapping与springmvc的定义冲突问题,[参考入口](http://blog.didispace.com/spring-cloud-feignclient-problem/?utm_source=tuicool&utm_medium=referral) 20181009 1. 添加ES实现电商[搜索基础服务](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-SearchService) 2. 添加Mockito实现api的junit测试 20181012 1. redisson yml配置加载,支持单机、集群、云托管、sentinel模式
2. 配置文件中添加配置文件即可开启redisson的配置:
`` redisson.fileName: redission-cluster(自定义)`` 3. 提供缓存基本服务和分布式服务:
``> CacheService 提供缓存基础服务``
``> DistributedService 提供分布式**可重入公平/非公平锁**、**读写锁**、**闭锁**``
[代码实现参考](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache)
[junit测试参考](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/cache)
20181016 1. redis缓存 redisson客户端添加批处理 20181018 1. [spring-cache+guava 添加本地缓存](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache) 20181022 1. 升级Springboot2.0 详情参考springboot2.0分之 2. 调整common包,可打成jar包 3. 添加启动shell脚本,参考common包script目录下.sh文件 20181027 1. bootstrap.sh 脚本参数简化 20181029 1. 优化bootstrap.sh脚本 2. 添加jenkins构建后自动部署脚本 3. 添加jenkins构建后远程自动部署脚本 [详情](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-Common/script) 20181114 1. Sharding-sphere尝试 20181115 1. bootstrap.sh 脚本添加jvm参数配置,以及停止时旧日志文件的备份 2. 本地和远程自动部署时,不进行原服务包的删除,按时间戳进行备份原来的可执行包 20181127 1. springcloud-config 自定义mysql实现,[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter) 20181210 1. 工程结果整理 2. 添加springboot+quartz自定义实现 任务调度 20190119 1. 新增Redisson集成springdata,使用RedisTemplate,[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Redisson) 20190124 1. 新增RedisTemplate 使用pipeline批量操作redis数据 2. 添加常用工具类 AES加解密、MD5等 20190126 1. 改造zipkin链路跟踪实现:SpringCloud Sleuth Stream Zipkin Kafka Elasticsearch 实现简单链路跟踪。
[参考](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-ZipkinServer/README.md) 20190131 1. 自定义配置中心重构,[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter) 2. 新增多条件搜索测试,[详情](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/SearchManagerTest.java)
3. 更多多条件搜索的[参考资料](http://www.scienjus.com/elasticsearch-function-score-query/) 20190201 1. 新增[Docker + SpringBoot + Maven 构建发布到远程仓库 DEMO](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Docker) 20190320 1. 新增 ES 权重查询 以及 聚合逻辑,[详情参考](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/service/SearchServiceEsImpl.java) 2. 新增阿里开源数据同步工具[Canal](https://github.com/alibaba/canal)的简单[Demo使用](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Canal) 20190327 1. 将Canal+Disruptor整合到springboot中,提供一套完整的Canal异步框架,在DisruptorServiceImpl服务中实现自己的业务逻辑即可,[更多详见](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Canal) 20190402 1. 自定义配置中心,引入Netty监测心跳[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter) 20190405 1. netty实现配置刷新[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter) 20190411 1. 添加maven+jenkins+docker+springboot 构建打包发布部署的jenkins shell脚本 20190402 1. 修复linux系统,客户端异常断开,服务端无感知问题,即在linux上使用kill或ctrl+c 中断服务,无法进入exceptionCaught方法导致无法感知应用下线问题。
更换为channelInactive方法来感知和下线客户端(netty) 20190504 1. [新增mqtt通过nginx代理配置](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-MQTT) 20190515 1. 新增spring session+ spring security + jwt简单鉴权,[参考入口](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter/custom-config-web) 20190611 1. 记录 fork join demo[详情](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/forkjoin) 20190621 1. Disruptor+spring event封装[详情以及使用说明](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor) 20190624 1. [入手Zookeeper](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Zookeeper) 20190702 1. 新增Canal启动 [ServerRunningMonitor部分源码注解记录](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Canal/doc/ServerRunningMonitor%E6%BA%90%E7%A0%81%E6%B3%A8%E8%A7%A3.md), 2. 修复定时任务重新启动时加载启动状态且已过期的任务报错问题:[新增在添加任务是校验表达的合法性](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/util/CronExpUtil.java) 20190720 1. Springboot-Admin 2.0服务端+Springboot-Admin 1.5.6客户端[集成使用](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Admin),掌控你的微服务。 20190801 1. [SpringBoot + SpringCloud + Feign + Sentinel 集成实现接口限流监控](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Sentinel) 20190813 1. [SpringCloud + Feign + Hystrix 熔断、线程池的一些坑记录](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Hystrix-Demo) 20190909 1. [定制SpringBoot Starter 之Elasticsearch Rest High Level Client Starter](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Custom-Elasticsearch-Starter) 20190910 1. [启动脚本添加GC参数和skywalking探针](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Common/script/bootstrap.sh) 20190929 1. 新增图片比较工具类,比较两张图片是否相同:[DHashUtil](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/image/ImageDHashUtil.java)&[PHashUtil](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/image/ImagePHashUtil.java) 20191019 1. redisson分布式锁库存使用,下单、取消单、出库单之jmeter ifelse程序并发测试--**预告** 20191022 1. [Springboot官方文档-配置新-Tomcat优化](https://docs.spring.io/spring-boot/docs/2.2.1.BUILD-SNAPSHOT/reference/html/appendix-application-properties.html#server-properties) 20191023 1. [Jmeter+Springboot+Redisson分布式锁并发订单操作(下单、取消单、完成单、加库存)](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Stock-Demo) 20191105 1. Api对外接口统一返回值,如:{"code":200,"erroMsg":"",data:{}},[参考实现](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/advice/UnifiedReturnAdvice.java) 20191119 1. Springboot 的Rest请求返回的Response中的HTTP响应行只有:HTTP/1.1 200 {OK},无OK返回导致老的http客户端无法识别,是因为springboot 1.4以上版本将tomcat升级到了8.5.x以后的版本,如果需要支持,需要设置tomcat的版本低于8.5的版本,设置: ```$xslt 8.0.29 ``` [参考资料1](http://www.mamicode.com/info-detail-2280850.html);
[参考资料2](https://stackoverflow.com/questions/49610522/spring-boot-return-http-1-1-200-not-http-1-1-200-ok); 20191125 1. [Gitlab+P3C-PMD(ali)标准化你团队的代码.doc](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/Gitlab%2BP3C-PMD(Aliyun)%E6%A0%87%E5%87%86%E5%8C%96%E4%BD%A0%E5%9B%A2%E9%98%9F%E7%9A%84%E4%BB%A3%E7%A0%81.docx) 20191206 1. [SpringCloud RestTemplate 封装stater](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-RestTemplate-Stater)支持使用http连接池、okhttp等 20191208 1. [Linux之netstat命令](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/linux/Linux-netstat%E5%91%BD%E4%BB%A4.md)-服务自动化发布时以此结果为依据停止服务 20191213 1. [RxJava Hello World](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/rxjava/RxJavaHelloWorld.MD),要入手一定要敲代码,敲起来! 20200104 1. Shell脚本+jstat+curl+crontab 监控JVM发短信[脚本](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/monitor-jstat.sh) 20200111 1. [基于Springboot1.5.9+SpringCloud+Zipkin+ELK链路跟踪实现](https://github.com/Xlinlin/spingcloud-zipkin-elk-demo) 20200114 1. String字符GBK和UTF编码格式长度判断以及截取 20200115 1. 经常用到对list进行分页批处理,写了[工具类](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/ListPageUtil.java)一劳永逸 20200117 1. 封装RestTemplate,支持okhttp,httpool,支持同步和异步请求,[ReadMe.MD](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Custom-Rest-Starter) 20200224 1. Springboot web应用签名包括工具类,[传送链接](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign) 20200229 1. K8S Cluster[安装文档](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/k8s/K8S%20Linux%20%20Centos%207%E5%AE%89%E8%A3%85.docx) 20200422 1. Sonarqube+Gitlab-CICD构建[代码质量管理平台](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90) 20200426 1. 完善代码质量[监控体系](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%9B%91%E6%8E%A7%E4%BD%93%E7%B3%BB%E6%96%B9%E6%A1%88.pptx) 20200508 1. SpringCloud Gateway + nacos实现灰度, + ribbon实现全链路版本请求,[详情](https://github.com/Xlinlin/SpringCloud-Gateway-Canary) 20200527 1. [添加docker-build脚本,执行脚本构建镜像并推送到私服仓库](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Docker/docker-build.sh) 20200528 1. swagger2 创建[api文档](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/swagger%E7%94%9F%E6%88%90html%E6%96%87%E4%BB%B6.pdf)整理 2. [docker-swarm集群监控文档](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Demo-Doc/docker)整理 20200529 1. 补充swarm集群部署springcloud项目,[详细文档](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/docker/docker-swarm-springcloud.md) 20200814 1. 新gitlab使用代码规则校验说明,[详情参考](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Demo-Doc/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/gitlab-hooks/pre-receive) 20201002 1. 说明:本仓库的代码以springboot 1.0版本,工作中已使用2.0以上版本,很多新的分享会单独创建git项目 2. 基于nacos或eureka实现 服务级别的灰度,支持网关、feign自由插件[详情参考](https://github.com/Xlinlin/canary) 20211012 1. [Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-服务端改造篇](https://blog.csdn.net/xiaoll880214/article/details/120191476?spm=1001.2014.3001.5501) 2. [Springboot Admin(SBA) + Nacos + Arthas 搭建你的在线性能分析和问题定位工具-客户端改造篇](https://blog.csdn.net/xiaoll880214/article/details/120295070?spm=1001.2014.3001.5501) ================================================ FILE: SpringBoot-Admin/pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.0.RELEASE Springboot-Admin 2.0版本和SpringBoot 1.5.X版本结合使用 SpringBoot-Admin UTF-8 UTF-8 1.8 1.8 1.8 2.1.0 de.codecentric spring-boot-admin-starter-server ${spring-boot-admin.version} de.codecentric spring-boot-admin-server-ui ${spring-boot-admin.version} org.jolokia jolokia-core org.projectlombok lombok org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 SpringBoot-Admin ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin build-info ================================================ FILE: SpringBoot-Admin/readme.md ================================================ Springboot-Admin 2.0服务端+Springboot-Admin 1.5.6客户端集成使用监控服务
原参考的[github地址](https://github.com/p555iii/spring-boot-admin1.5to2.0)
服务端配置参考[地址](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Admin)
客户端配置参考[地址](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Provider)
================================================ FILE: SpringBoot-Admin/src/main/java/com/xiao/spring/boot/admin/SpringBootAdminApplication.java ================================================ package com.xiao.spring.boot.admin; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; /** * [简要描述]: springboot-admin 集成eureka 监控服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/7/30 09:47 * @since JDK 1.8 */ @Configuration @SpringBootApplication @EnableAdminServer public class SpringBootAdminApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAdminApplication.class, args); } } ================================================ FILE: SpringBoot-Admin/src/main/resources/application.yml ================================================ info: groupId: @project.groupId@ artifactId: @project.artifactId@ version: @project.version@ ################### # 邮件通知配置 ################## #spring: #mail: #host: smtphm.qiye.163.com #username: # 用户名 #password: # 密码 #boot: #admin: #notify: #mail: #from: # 发件人 #to: # 收件人 #enabled: true ================================================ FILE: SpringBoot-Admin/src/main/resources/bootstrap.yml ================================================ server: port: 8080 spring: application: name: omni-service-admin security: user: name: "admin" password: "admin@123" #eureka: #client: #fetch-registry: true #service-url: #defaultZone: http://192.168.206.201:8888/eureka #register-with-eureka: true #registry-fetch-interval-seconds: 30 #instance: #hostname: localhost #prefer-ip-address: true #lease-renewal-interval-in-seconds: 10 #lease-expiration-duration-in-seconds: 30 #health-check-url-path: /actuator/health #metadata-map: #user.name: ${spring.security.user.name} #user.password: ${spring.security.user.password} #info: #name: @project.name@ #groupId: @project.groupId@ #artifactId: @project.artifactId@ #version: @project.version@ #management: #endpoints: #web: #exposure: #include: "*" #base-path: / #endpoint: #health: #show-details: ALWAYS ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter/pom.xml ================================================ SpringBoot-Custom-Elasticsearch-Starter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 Custom-Elasticsearch-Starter Custom ElasticSearch High Level Rest Client Stater AutoConfigure com.xiao.skywalking.demo Custom-Elasticsearch-Starter-Autoconfig ${project.version} ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Autoconfig/pom.xml ================================================ SpringBoot-Custom-Elasticsearch-Starter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 Custom-Elasticsearch-Starter-Autoconfig Custom ElasticSearch High Level Rest Client Stater AutoConfigure 6.8.17 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok 1.16.18 true org.elasticsearch elasticsearch ${elasticsearch.version} org.elasticsearch.client elasticsearch-rest-client ${elasticsearch.version} org.elasticsearch.client elasticsearch-rest-high-level-client ${elasticsearch.version} org.apache.commons commons-lang3 3.4 com.alibaba fastjson 1.2.83 ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Autoconfig/src/main/java/com/xiao/custom/elasticsearch/start/autoconfig/ElasticsearchAutoConfiguration.java ================================================ package com.xiao.custom.elasticsearch.start.autoconfig; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.elasticsearch.start.autoconfig.properties.ElasticsearchProperties; import com.xiao.custom.elasticsearch.start.autoconfig.properties.HostInfo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.impl.client.BasicCredentialsProvider; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.CollectionUtils; import java.util.List; /** * [简要描述]: 自动装配 * [详细描述]:

* EnableAutoConfiguration 利用SpringFactoriesLoader机制加载所有的AutoConfiguration类 META-INF/spring.factories * * @author llxiao * @version 1.0, 2019/8/28 10:28 * @since JDK 1.8 */ @Configuration @EnableConfigurationProperties(ElasticsearchProperties.class) @Slf4j public class ElasticsearchAutoConfiguration implements DisposableBean { private RestHighLevelClient restHighLevelClient; @Bean @ConditionalOnMissingBean public RestHighLevelClient restHighLevelClient(ElasticsearchProperties elasticsearchProperties) { if (log.isDebugEnabled()) { log.debug("初始化Elasticsearch Rest High Level Client...."); } List hosts = elasticsearchProperties.getHosts(); if (CollectionUtils.isEmpty(hosts)) { throw new RuntimeException("Elasticsearch host配置为空,请检查:spring.elasticsearch.rest.hosts的配置是否正确"); } if (log.isDebugEnabled()) { log.debug("Elasticsearch host: {}", JSONObject.toJSONString(hosts)); } HttpHost[] httpHosts = new HttpHost[hosts.size()]; int i = 0; for (HostInfo host : hosts) { httpHosts[i++] = new HttpHost(host.getHostname(), host.getPort(), host.getSchema()); } RestClientBuilder restClientBuilder = RestClient.builder(httpHosts); restClientBuilder.setMaxRetryTimeoutMillis(elasticsearchProperties.getMaxRetryTimeout()); // 请求参数设置 restClientBuilder.setRequestConfigCallback(requestConfigBuilder -> { requestConfigBuilder.setConnectTimeout(elasticsearchProperties.getConnectTimeout()); requestConfigBuilder.setSocketTimeout(elasticsearchProperties.getSocketTimeout()); requestConfigBuilder.setConnectionRequestTimeout(elasticsearchProperties.getRequestTimeout()); return requestConfigBuilder; }); //异步 httpclient 连接参数配置 restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> { httpClientBuilder.setMaxConnTotal(elasticsearchProperties.getMaxConnect()); httpClientBuilder.setMaxConnPerRoute(elasticsearchProperties.getMaxConnectRoute()); // httpClientBuilder.setThreadFactory(); // SSL 配置 // httpClientBuilder.setSSLContext() // 请求队列头部拦截 ,request response HttpResponseInterceptor HttpRequestInterceptor // httpClientBuilder.addInterceptorFirst(); // 请求队列尾部拦截 ,request response // httpClientBuilder.addInterceptorLast(); // 鉴权设置 if (StringUtils.isNotBlank(elasticsearchProperties.getUsername()) && StringUtils .isNotBlank(elasticsearchProperties.getPassword())) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider .setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticsearchProperties .getUsername(), elasticsearchProperties.getPassword())); httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } return httpClientBuilder; }); if (log.isDebugEnabled()) { log.debug("初始化Elasticsearch Rest High Level Client 成功!"); } restHighLevelClient = new RestHighLevelClient(restClientBuilder); return restHighLevelClient; } /** * Invoked by a BeanFactory on destruction of a singleton. * * @exception Exception in case of shutdown errors. * Exceptions will get logged but not rethrown to allow * other beans to release their resources too. */ @Override public void destroy() throws Exception { restHighLevelClient.close(); } } ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Autoconfig/src/main/java/com/xiao/custom/elasticsearch/start/autoconfig/properties/ElasticsearchProperties.java ================================================ package com.xiao.custom.elasticsearch.start.autoconfig.properties; import lombok.Data; import org.elasticsearch.client.RestClientBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import java.util.List; /** * [简要描述]: Elasticsearch 配置类 * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/28 10:18 * @since JDK 1.8 */ @Data @ConfigurationProperties(prefix = ElasticsearchProperties.ELASTIC_SEARCH_PREFIX) public class ElasticsearchProperties { public static final String ELASTIC_SEARCH_PREFIX = "spring.elasticsearch.rest"; /** * 集群名称 */ private String clusterName; /** * 节点信息 */ @NestedConfigurationProperty private List hosts; /** * 鉴权使用 */ private String username; private String password; /** * 高亮前缀 */ private String highlightPre = ""; /** * 高亮后缀 */ private String highlightPost = ""; /** * 连接超时时间 */ private int connectTimeout = RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS; /** * socket超时时间 */ private int socketTimeout = RestClientBuilder.DEFAULT_SOCKET_TIMEOUT_MILLIS; /** * 请求超时时间 */ private int requestTimeout = RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS; /** * 最大连接数 */ private int maxConnect = RestClientBuilder.DEFAULT_MAX_CONN_TOTAL; /** * 单主机并发最大数 */ private int maxConnectRoute = RestClientBuilder.DEFAULT_MAX_CONN_PER_ROUTE; /** * 重试最大超时时间 */ private int maxRetryTimeout = RestClientBuilder.DEFAULT_MAX_RETRY_TIMEOUT_MILLIS; } ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Autoconfig/src/main/java/com/xiao/custom/elasticsearch/start/autoconfig/properties/HostInfo.java ================================================ package com.xiao.custom.elasticsearch.start.autoconfig.properties; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/28 10:22 * @since JDK 1.8 */ @Data public class HostInfo { private String hostname; private int port; private String schema; } ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Autoconfig/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xiao.custom.elasticsearch.start.autoconfig.ElasticsearchAutoConfiguration ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/pom.xml ================================================ SpringBoot-Custom-Elasticsearch-Starter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 Custom-Elasticsearch-Starter-Example Custom ElasticSearch High Level Rest Client Stater Example 6.3.2 2.17.1 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.apache.logging.log4j log4j-api ${log4j2.version} org.apache.logging.log4j log4j-core ${log4j2.version} org.springframework.boot spring-boot-starter-test test com.xiao.skywalking.demo Custom-Elasticsearch-Starter ${project.version} ${project.artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/main/java/com/xiao/custom/elasticsearch/starter/example/ElasticsearchApplication.java ================================================ package com.xiao.custom.elasticsearch.starter.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/28 13:54 * @since JDK 1.8 */ @SpringBootApplication public class ElasticsearchApplication { public static void main(String[] args) { SpringApplication.run(ElasticsearchApplication.class, args); } } ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/main/resources/application.properties ================================================ #spring.elasticsearch.rest.clusterName=omni-dev-es #spring.elasticsearch.rest.hosts[0].hostname=192.168.206.210 #spring.elasticsearch.rest.hosts[0].port=9200 #spring.elasticsearch.rest.hosts[0].schema=http #spring.elasticsearch.rest.connectTimeout=1000 #spring.elasticsearch.rest.socketTimeout=30000 #spring.elasticsearch.rest.requestTimeout=500 #spring.elasticsearch.rest.maxConnect=30 #spring.elasticsearch.rest.maxConnectRoute=10 #spring.elasticsearch.rest.maxRetryTimeout=30000 ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/main/resources/application.yml ================================================ spring: elasticsearch: rest: clusterName: omni-dev-es hosts: - hostname: 192.168.206.210 port: 9200 schema: http #- #hostname: 192.168.206.212 #port: 9200 #schema: http #username: you username #password: you passwd # 连接超时时间,单位ms,默认1S connectTimeout: 1000 # socket超时时间,单位ms,默认30S socketTimeout: 30000 # 请求超时时间,单位ms,默认500ms requestTimeout: 500 # 单机最大连接数,默认30个 maxConnect: 30 # 单机最大并发数,默认10个 maxConnectRoute: 10 # 最大重试时间,默认30S maxRetryTimeout: 30000 # 高亮前缀 #highlightPre: # 高亮后缀 #highlightPost: ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/main/resources/bootstrap.yml ================================================ ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/main/resources/logback-spring.xml ================================================ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/test/java/com/xiao/custom/elasticsearch/starter/example/ElasticsearchApplicationTest.java ================================================ package com.xiao.custom.elasticsearch.starter.example; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.elasticsearch.start.autoconfig.properties.ElasticsearchProperties; import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.entity.ContentType; import org.apache.http.message.BasicHeader; import org.apache.http.nio.entity.NStringEntity; import org.apache.http.util.EntityUtils; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.close.CloseIndexRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexResponse; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexResponse; import org.elasticsearch.action.bulk.*; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.master.AcknowledgedRequest; import org.elasticsearch.action.support.replication.ReplicationResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; 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; import java.io.IOException; import java.util.*; import java.util.concurrent.TimeUnit; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/28 13:56 * @since JDK 1.8 */ @RunWith(SpringRunner.class) @SpringBootTest public class ElasticsearchApplicationTest { @Autowired private RestHighLevelClient restHighLevelClient; @Autowired private ElasticsearchProperties elasticsearchProperties; /** * 搜索 * https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.2/java-rest-high-search.html */ @Test public void testQuery() { SearchRequest request = new SearchRequest("10000"); // request.searchType("10000"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termQuery("commodityNo", "3439538790")); searchSourceBuilder.from(0); searchSourceBuilder.size(10); searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); // 高亮设置 HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false); if (StringUtils.isNoneEmpty(elasticsearchProperties.getHighlightPre())) { highlightBuilder.preTags(elasticsearchProperties.getHighlightPre()); highlightBuilder.postTags(elasticsearchProperties.getHighlightPost()); highlightBuilder.field("subTitle"); highlightBuilder.field("title"); } searchSourceBuilder.highlighter(highlightBuilder); request.source(searchSourceBuilder); try { final SearchResponse response = restHighLevelClient.search(request); response.getHits().forEach(document -> System.out.println(document.getSourceAsString())); } catch (IOException e) { e.printStackTrace(); } } /** * 批处理: * 增删改查 */ public void testBulkRequest() { BulkRequest request = new BulkRequest(); request.add(new DeleteRequest("posts", "doc", "3")); request.add(new UpdateRequest("posts", "doc", "2").doc(XContentType.JSON, "other", "test")); request.add(new IndexRequest("posts", "doc", "4").source(XContentType.JSON, "field", "baz")); try { // restHighLevelClient.bulkAsync(request, ActionListener); BulkResponse bulkResponse = restHighLevelClient.bulk(request); if (bulkResponse.hasFailures()) { for (BulkItemResponse bulkItemResponse : bulkResponse) { if (bulkItemResponse.isFailed()) { BulkItemResponse.Failure failure = bulkItemResponse.getFailure(); System.out.println("处理失败:" + failure.getId() + '-' + failure.getMessage()); } } } for (BulkItemResponse bulkItemResponse : bulkResponse) { DocWriteResponse itemResponse = bulkItemResponse.getResponse(); if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.INDEX || bulkItemResponse.getOpType() == DocWriteRequest.OpType.CREATE) { IndexResponse indexResponse = (IndexResponse) itemResponse; // 创建 } else if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.UPDATE) { UpdateResponse updateResponse = (UpdateResponse) itemResponse; // 更新 } else if (bulkItemResponse.getOpType() == DocWriteRequest.OpType.DELETE) { DeleteResponse deleteResponse = (DeleteResponse) itemResponse; // 删除 } } } catch (IOException e) { e.printStackTrace(); } } /** * BulkProcessor通过提供一个实用程序类简化了批量API的使用,该实用程序类允许在将索引/更新/删除操作添加到处理器时透明地执行这些操作。 */ @Test public void testBulkProcessor() { BulkProcessor.Listener listener = new BulkProcessor.Listener() { @Override public void beforeBulk(long executionId, BulkRequest request) { int numberOfActions = request.numberOfActions(); System.out.println("当前BulkProcessor中执行的操作数:" + numberOfActions); } @Override public void afterBulk(long executionId, BulkRequest request, BulkResponse response) { if (response.hasFailures()) { System.out.println("当前BulkProcessor执行出现异常:" + response.buildFailureMessage()); } else { System.out.println("执行成功!"); } } @Override public void afterBulk(long executionId, BulkRequest request, Throwable failure) { System.out.println("当前请求发生的错误消息:" + failure.getMessage()); failure.printStackTrace(); } }; BulkProcessor.Builder builder = BulkProcessor.builder(restHighLevelClient::bulkAsync, listener); //根据当前添加的操作数设置刷新新批量请求的时间 defaults to 1000 builder.setBulkActions(500); // 根据当前添加的操作的大小设置刷新新批量请求的时间 defaults to 5Mb builder.setBulkSize(new ByteSizeValue(1L, ByteSizeUnit.MB)); // 设置允许执行的并发请求数 默认0仅允许一个 builder.setConcurrentRequests(0); // 设置刷新间隔,如果间隔通过,则刷新任何挂起的BulkRequest builder.setFlushInterval(TimeValue.timeValueSeconds(10L)); // 回退策略 等待1秒,最多重试3 builder.setBackoffPolicy(BackoffPolicy.constantBackoff(TimeValue.timeValueSeconds(1L), 3)); BulkProcessor bulkProcessor = builder.build(); IndexRequest one = new IndexRequest("posts", "doc", "1"). source(XContentType.JSON, "title", "In which order are my Elasticsearch queries executed?"); IndexRequest two = new IndexRequest("posts", "doc", "2") .source(XContentType.JSON, "title", "Current status and upcoming changes in Elasticsearch"); IndexRequest three = new IndexRequest("posts", "doc", "3") .source(XContentType.JSON, "title", "The Future of Federated Search in Elasticsearch"); bulkProcessor.add(one); bulkProcessor.add(two); bulkProcessor.add(three); try { // 执行并等待,直到超时 boolean terminated = bulkProcessor.awaitClose(30L, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } // 关闭 bulkProcessor.close(); } /** * 插入数据 index api */ @Test public void testInsert() throws IOException { String index = "101010"; String type = index; String id = UUID.randomUUID().toString(); IndexRequest indexRequest = new IndexRequest(index, type, id); // 可以设置版本号,但可能出现版本冲突异常 ElasticsearchException e.status() == RestStatus.CONFLICT indexRequest.version(1); // or // IndexRequest indexRequest = new IndexRequest(); // indexRequest.index(index); // indexRequest.type(type); // indexRequest.id(id); // As json String String jsonString = "{" + "\"user\":\"kimchy\"," + "\"postDate\":\"2013-01-30\"," + "\"message\":\"trying out Elasticsearch\"" + "}"; indexRequest.source(jsonString, XContentType.JSON); // As map Map jsonMap = new HashMap<>(); jsonMap.put("user", "kimchy"); jsonMap.put("postDate", new Date()); jsonMap.put("message", "trying out Elasticsearch"); indexRequest.source(jsonMap); // As XContentBuilder XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.field("user", "kimchy"); builder.field("postDate", new Date()); builder.field("message", "trying out Elasticsearch"); } builder.endObject(); indexRequest.source(builder); // As source indexRequest.source("user", "kimchy", "postDate", new Date(), "message", "trying out Elasticsearch"); // index or indexAsync IndexResponse indexResponse = restHighLevelClient.index(indexRequest); index = indexResponse.getIndex(); type = indexResponse.getType(); id = indexResponse.getId(); long version = indexResponse.getVersion(); if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) { System.out.println("ES数据已经成功创建"); } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) { System.out.println("ES数据已经成功覆盖"); } ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo(); if (shardInfo.getTotal() != shardInfo.getSuccessful()) { System.out.println("数据创建成功,但成功的shard数量小于总shard数量"); } if (shardInfo.getFailed() > 0) { for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { System.out.println("失败原:" + failure.reason()); } } } /** * 文档ID 查找 */ @Test public void testGetIndex() { GetRequest getRequest = new GetRequest("101010", "101010", "101010"); // 设置版本 // getRequest.version(2); //禁用获取 _source字段 getRequest.fetchSourceContext(new FetchSourceContext(false)); try { // get or getAsync(request,ActionListener) GetResponse getResponse = restHighLevelClient.get(getRequest); String index = getResponse.getIndex(); String type = getResponse.getType(); String id = getResponse.getId(); if (getResponse.isExists()) { long version = getResponse.getVersion(); String sourceAsString = getResponse.getSourceAsString(); Map sourceAsMap = getResponse.getSourceAsMap(); byte[] sourceAsBytes = getResponse.getSourceAsBytes(); } else { System.out.println("不存在的数据!"); } } catch (ElasticsearchException e) { if (e.status() == RestStatus.NOT_FOUND) { System.out.println("索引不存在"); } else if (e.status() == RestStatus.CONFLICT) { System.out.println("版本冲突!"); } else { System.out.println("其他为未知异常:" + e.status().name()); } } catch (IOException e) { System.out.println("IO 异常"); } } /** * 文档ID 更新 * 1. 脚本更新 * 2. 文档更新:部分字段更新和不存在直接插入更新 */ @Test public void testUpdate() throws IOException { UpdateRequest request = new UpdateRequest("101010", "101010", "101010"); // ############## 更新文档数据部分字段使用 .doc ,如果不确定存在则直接插入使用 .upsert方法 // As JSON String String jsonString = "{" + "\"updated\":\"2017-01-01\"," + "\"reason\":\"daily update\"" + "}"; request.doc(jsonString, XContentType.JSON); // request.upsert(jsonString,XContentType.JSON); // As Map Map jsonMap = new HashMap<>(); jsonMap.put("updated", new Date()); jsonMap.put("reason", "daily update"); request.doc(jsonMap); // request.upsert(jsonMap); // AS XContentBuilder XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.field("updated", new Date()); builder.field("reason", "daily update"); } builder.endObject(); request.doc(builder); request.upsert(builder); // 设置版本 // request.version(2); //指示如果部分文档尚不存在,则必须将其用作upsert文档。 // request.docAsUpsert(true); try { //同步 update 异步 updateAsync(request,ActionListener) UpdateResponse updateResponse = restHighLevelClient.update(request); String index = updateResponse.getIndex(); String type = updateResponse.getType(); String id = updateResponse.getId(); long version = updateResponse.getVersion(); if (updateResponse.getResult() == DocWriteResponse.Result.CREATED) { System.out.println("文档不存在,创建成功!"); } else if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) { System.out.println("文档更新成功!"); } else if (updateResponse.getResult() == DocWriteResponse.Result.DELETED) { System.out.println("文档删除成功!"); } else if (updateResponse.getResult() == DocWriteResponse.Result.NOOP) { System.out.println("没有对文档做任何更新操作!"); } // 获取更新后的数据 GetResult result = updateResponse.getGetResult(); if (result.isExists()) { String sourceAsString = result.sourceAsString(); Map sourceAsMap = result.sourceAsMap(); byte[] sourceAsBytes = result.source(); } else { System.out.println("文档不存在!"); } // 分片更新失败 ReplicationResponse.ShardInfo shardInfo = updateResponse.getShardInfo(); if (shardInfo.getFailed() > 0) { for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { System.out.println("分片ID:" + failure.fullShardId() + "更新失败:" + failure.reason()); } } } catch (ElasticsearchException e) { // 可能有索引没找到,版本异常,参考RestStatus System.out.println("更新异常:" + e.status().name()); } } /** * 文档ID 删除 */ @Test public void testDel() { DeleteRequest request = new DeleteRequest("101010", "101010", "101010"); try { DeleteResponse deleteResponse = restHighLevelClient.delete(request); if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { System.out.println("删除的文档不存在"); } ReplicationResponse.ShardInfo shardInfo = deleteResponse.getShardInfo(); if (shardInfo.getTotal() != shardInfo.getSuccessful()) { System.out.println("删除成功了,但是删除的数量与分片的数量不符合!"); } if (shardInfo.getFailed() > 0) { for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { System.out.println("分片ID:" + failure.fullShardId() + "更新失败:" + failure.reason()); } } } catch (ElasticsearchException e) { System.out.println("删除异常:" + e.status().name()); } catch (IOException e) { e.printStackTrace(); } } /** * 创建索引 * * @exception IOException */ @Test public void testCreateIndex() throws IOException { CreateIndexRequest request = new CreateIndexRequest("101010"); // setting设置 request.settings(Settings.builder().put("index.number_of_shards", 3).put("index.number_of_replicas", 2)); // mapping // request.mapping(); request.alias(new Alias("101010_alias")); // 其他可选参数 setOptionParams(request); // 同步创建 final CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request); // 所有节点是否已确认请求 if (createIndexResponse.isAcknowledged()) { System.out.println("创建成功!"); } // 是否在超时前为索引中的每个碎片启动了所需数量的分片副本 if (createIndexResponse.isShardsAcknowledged()) { System.out.println("分片副本都已创建成功"); } // 异步创建 // restHighLevelClient.indices().createAsync(request, new ActionListener() // { // @Override // public void onResponse(CreateIndexResponse clearIndicesCacheResponse) // { // System.out.println("所有节点是否已确认请求: " + createIndexResponse.isAcknowledged()); // System.out.println("分片副本都已创建成功:" + createIndexResponse.isShardsAcknowledged()); // } // // @Override // public void onFailure(Exception e) // { // System.out.println("请求出现异常,异常信息:" + e.getMessage()); // e.printStackTrace(); // } // }); } /** * 所以是否存在 */ @Test public void testIndexExist() { GetIndexRequest request = new GetIndexRequest(); request.indices("10001"); try { System.out.println(restHighLevelClient.indices().exists(request)); } catch (IOException e) { e.printStackTrace(); } } /** * 删除索引 */ @Test public void testDelIndex() { DeleteIndexRequest request = new DeleteIndexRequest("101010"); // 其他可选参数 setOptionParams(request); // 同步 try { DeleteIndexResponse response = restHighLevelClient.indices().delete(request); System.out.println("所有节点已确认:" + response.isAcknowledged()); } catch (ElasticsearchException exception) { if (exception.status() == RestStatus.NOT_FOUND) { System.out.println("索引未找到!"); } } catch (IOException e) { e.printStackTrace(); } // 异步 // restHighLevelClient.indices().deleteAsync(request, new ActionListener() // { // @Override // public void onResponse(DeleteIndexResponse deleteIndexResponse) // { // System.out.println("所有节点已确认:" + deleteIndexResponse.isAcknowledged()); // } // // @Override // public void onFailure(Exception e) // { // if (e instanceof ElasticsearchException) // { // ElasticsearchException exception = (ElasticsearchException) e; // if (exception.status() == RestStatus.NOT_FOUND) // { // System.out.println("索引未找到!"); // } // } // System.out.println("删除出现位置异常!"); // } // }); } /** * 打开和关闭索引 */ @Test public void testOpenAndCloseIndex() throws IOException { OpenIndexRequest openIndexRequest = new OpenIndexRequest("index"); setOptionParams(openIndexRequest); // 同步 open or 异步 openAsync OpenIndexResponse openIndexResponse = restHighLevelClient.indices().open(openIndexRequest); System.out.println("所有节点已确认:" + openIndexResponse.isAcknowledged()); System.out.println("所有副本分片已确认:" + openIndexResponse.isShardsAcknowledged()); CloseIndexRequest closeIndexRequest = new CloseIndexRequest("index"); setOptionParams(closeIndexRequest); // 同步 close or 异步 closeAsync CloseIndexResponse closeIndexResponse = restHighLevelClient.indices().close(closeIndexRequest); System.out.println("所有节点已确认:" + closeIndexResponse.isAcknowledged()); } private void setOptionParams(AcknowledgedRequest request) { // 其他可选参数 request.timeout(TimeValue.timeValueMinutes(2)); // request.timeout("2m"); // master node // request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // request.masterNodeTimeout("1m"); // 创建索引API返回响应之前等待的活动分片副本数 // request.waitForActiveShards(2); // request.waitForActiveShards(ActiveShardCount.DEFAULT); } /** * [简要描述]:使用LowLevelClient 执行analyzer操作
* [详细描述]:
*

* llxiao 2019/9/17 - 10:45 **/ @Test public void testAnalysis() { RestClient lowLevelClient = restHighLevelClient.getLowLevelClient(); JSONObject entity = new JSONObject(); entity.put("analyzer", "ik_max_word"); entity.put("text", "我是中国人"); HttpEntity httpEntity = new NStringEntity(JSONObject.toJSONString(entity), ContentType.APPLICATION_JSON); Map params = Collections.emptyMap(); Header[] defaultHeaders = new Header[] { new BasicHeader("header", "value") }; try { Response response = lowLevelClient.performRequest("POST", "_analyze", params, httpEntity, defaultHeaders); JSONObject tokens = JSONObject.parseObject(EntityUtils.toString(response.getEntity())); JSONArray arrays = tokens.getJSONArray("tokens"); String[] result = new String[arrays.size()]; for (int i = 0; i < arrays.size(); i++) { JSONObject obj = JSONObject.parseObject(arrays.getString(i)); result[i] = obj.getString("token"); } System.out.println(Arrays.toString(result)); } catch (IOException e) { e.printStackTrace(); } } } ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/Readme.md ================================================ **定制SpringBoot Starter 之Elasticsearch Rest High Level Client Starter** **1. 自定义SpringBoot Starter 三要素:** >1.1.pom : ```$xslt org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-configuration-processor true ``` >1.2. 注解使用 ```$xslt @Data @ConfigurationProperties(prefix = ElasticsearchProperties.ELASTIC_SEARCH_PREFIX) public class ElasticsearchProperties{} @Configuration @EnableConfigurationProperties(ElasticsearchProperties.class) public class ElasticsearchAutoConfiguration{} ``` >1.3 EnableAutoConfiguration 利用SpringFactoriesLoader机制加载所有的AutoConfiguration类 META-INF/spring.factories ```$xslt org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xiao.custom.elasticsearch.start.autoconfig.ElasticsearchAutoConfiguration ``` Elasticsearch高级客户端打包集成为Springboot Starter包,详情参考[Example工程](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example)

**2. Custom ElasticSearch High Level Rest Client Starter使用说明:**

>2.1Pom引入
```$xslt 6.3.2 com.purcotton.omni omni-common-elasticsearch-starter ${project.version} org.elasticsearch elasticsearch ${elasticsearch.version} org.elasticsearch.client elasticsearch-rest-client ${elasticsearch.version} org.elasticsearch.client elasticsearch-rest-high-level-client ${elasticsearch.version} ``` >2.2配置文件:
```$xslt spring: elasticsearch: rest: clusterName: omni-dev-es hosts: - hostname: 192.168.206.210 port: 9200 schema: http #- #hostname: 192.168.206.212 #port: 9200 #schema: http #username: you username #password: you passwd # 连接超时时间,单位ms,默认1S connectTimeout: 1000 # socket超时时间,单位ms,默认30S socketTimeout: 30000 # 请求超时时间,单位ms,默认500ms requestTimeout: 500 # 单机最大连接数,默认30个 maxConnect: 30 # 单机最大并发数,默认10个 maxConnectRoute: 10 # 最大重试时间,默认30S maxRetryTimeout: 30000 ``` >2.3代码引用:
```$xslt @Autowired private RestHighLevelClient restHighLevelClient; ``` >2.4ElasticSearch High Level Rest Client 增删改Demo:
[ElasticsearchApplicationTest](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Custom-Elasticsearch-Starter/Custom-Elasticsearch-Starter-Example/src/test/java/com/xiao/custom/elasticsearch/starter/example/ElasticsearchApplicationTest.java)
```$xslt // 创建索引 ElasticsearchApplicationTest.testCreateIndex() // 索引是否存在 ElasticsearchApplicationTest.testIndexExist() // 删除索引 ElasticsearchApplicationTest.testDelIndex() // 打开和关闭索引 ElasticsearchApplicationTest.testOpenAndCloseIndex() // 添加文档 ElasticsearchApplicationTest.testInsert() // 主键ID获取文档 ElasticsearchApplicationTest.testGetIndex() // 更新文档 ElasticsearchApplicationTest.testUpdate() // 搜索 ElasticsearchApplicationTest.testQuery() // 删除文档 ElasticsearchApplicationTest.testDel() // 批处理1 ElasticsearchApplicationTest.testBulkRequest() // 批处理2 ElasticsearchApplicationTest.testBulkProcessor() ``` ================================================ FILE: SpringBoot-Custom-Elasticsearch-Starter/pom.xml ================================================ org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE 4.0.0 com.xiao.skywalking.demo SpringBoot-Custom-Elasticsearch-Starter 0.0.1-SNAPSHOT Custom ElasticSearch High Level Rest Client Stater pom Custom-Elasticsearch-Starter-Autoconfig Custom-Elasticsearch-Starter Custom-Elasticsearch-Starter-Example ================================================ FILE: SpringBoot-Custom-Rest-Starter/Readme.md ================================================ Springboot Rest Template配置 1. 支持Ok Http和Http连接池模式,内嵌包装成HttClientService服务,并提供完成的请求日志处理 2. 引入pom: ```$xslt com.xiao.skywalking.demo SpringBoot-Custom-Rest-Autconfigure 0.0.1-SNAPSHOT ``` 3. Ok Http使用: ```$xslt rest: # okhttp 配置 okhttp: enable: true connection-timeout: 12000 read-timeout: 30000 write-timeout: 12000 ``` 4. Http pool使用: ```$xslt rest # http pool pool: enable: true max-total: 20 default-max-per-route: 2 validate-after-inactivity: 2000 connect-timeout: 10000 connection-request-timeout: 10000 socket-timeout: 10000 ``` 5. 同步异步使用: ```$xslt rest: http: service: sync: true async: false ``` 6. 使用方式,推荐使用HttpClientService,因为提供了完整的日志记录: ```$xslt // 使用包装http client @Autowired private HttpClientService httpClientService; // 使用 resttemplate @Autowired private RestTemplate restTemplate; ``` 7. HttpClientService日志处理,实现HttpRequestLogService接口 ```$xslt public class HttpLogServiceImpl implements HttpRequestLogService { /** * [简要描述]:保存日志信息
* [详细描述]:
* * @param requestLog : * llxiao 2019/4/24 - 14:42 **/ @Override public void saveRequestLog(HttpRequestLog requestLog) { // 日志输出 log.info("Example log : {}", JSONObject.toJSONString(requestLog)); } } ``` ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/pom.xml ================================================ SpringBoot-Custom-Rest-Starter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringBoot-Custom-Rest-Autconfigure org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.retry spring-retry org.aspectj aspectjweaver org.springframework.cloud spring-cloud-context compile 1.3.4.RELEASE org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok 1.16.18 true org.apache.commons commons-lang3 3.4 commons-collections commons-collections 3.2.2 com.squareup.okhttp3 okhttp 3.11.0 org.apache.httpcomponents httpclient 4.5.13 com.alibaba fastjson 1.2.83 ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/config/RestTemplateConfiguration.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.config; import com.xiao.custom.rest.starter.autoconfigure.config.properties.HttpPoolProperties; import com.xiao.custom.rest.starter.autoconfigure.config.properties.OkHttpProperties; import com.xiao.custom.rest.starter.autoconfigure.interceptor.RestInterceptor; import com.xiao.custom.rest.starter.autoconfigure.service.HttpClientService; import com.xiao.custom.rest.starter.autoconfigure.service.impl.HttpClientAsyncServiceImpl; import com.xiao.custom.rest.starter.autoconfigure.service.impl.HttpClientServiceImpl; import com.xiao.custom.rest.starter.autoconfigure.service.impl.HttpRetryService; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.retry.annotation.EnableRetry; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Collections; /** * [简要描述]: 初始化rest template * [详细描述]: 开启重试 * * @author llxiao * @version 1.0, 2019/11/30 10:43 * @since JDK 1.8 */ @Configuration @ComponentScan("com.xiao.custom.rest.starter.autoconfigure") @Slf4j @EnableRetry public class RestTemplateConfiguration { /** * [简要描述]:okHttp支持
* [详细描述]:
* * @param okHttpProperties : * @return org.springframework.http.client.ClientHttpRequestFactory * xiaolinlin 2020/1/16 - 18:43 **/ @Bean @ConditionalOnProperty(value = "rest.okhttp.enable", havingValue = "true") @ConditionalOnMissingBean(ClientHttpRequestFactory.class) public ClientHttpRequestFactory okHttpHttpRequestFactory(OkHttpProperties okHttpProperties) { log.info("Init request factory for okHttp!"); OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(buildOkHttpsClient() .build()); //连接超时 clientHttpRequestFactory.setConnectTimeout(okHttpProperties.getConnectionTimeout()); //读超时 clientHttpRequestFactory.setReadTimeout(okHttpProperties.getReadTimeout()); //写超时 clientHttpRequestFactory.setWriteTimeout(okHttpProperties.getWriteTimeout()); return clientHttpRequestFactory; } /** * [简要描述]:http pool支持
* [详细描述]:
* * @param httpPoolProperties : * @return org.springframework.http.client.ClientHttpRequestFactory * xiaolinlin 2020/1/16 - 18:43 **/ @Bean @ConditionalOnProperty(value = "rest.pool.enable", havingValue = "true") @ConditionalOnMissingBean(ClientHttpRequestFactory.class) public ClientHttpRequestFactory httpPoolRequestFactory(HttpPoolProperties httpPoolProperties) { log.info("Init request factory for http pool"); SSLConnectionSocketFactory socketFactory = null; SSLContext sslContext = buildSslContext(); if (null != sslContext) { socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); } else { socketFactory = SSLConnectionSocketFactory.getSocketFactory(); } Registry registry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", socketFactory) .build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); connectionManager.setMaxTotal(httpPoolProperties.getMaxTotal()); connectionManager.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute()); connectionManager.setValidateAfterInactivity(httpPoolProperties.getValidateAfterInactivity()); RequestConfig requestConfig = RequestConfig.custom() //服务器返回数据(response)的时间,超过抛出read timeout .setSocketTimeout(httpPoolProperties.getSocketTimeout()) //连接上服务器(握手成功)的时间,超出抛出connect timeout .setConnectTimeout(httpPoolProperties.getConnectTimeout()) //从连接池中获取连接的超时时间,超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool .setConnectionRequestTimeout(httpPoolProperties.getConnectionRequestTimeout()).build(); return new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).build()); } /** * [简要描述]:RestTemplate
* [详细描述]:
* * @param clientHttpRequestFactory : * @return org.springframework.web.client.RestTemplate * xiaolinlin 2020/1/16 - 18:47 **/ @Bean @ConditionalOnMissingBean(RestTemplate.class) public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { log.info("Init rest template!"); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); restTemplate.setRequestFactory(clientHttpRequestFactory); restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); // 拦截 restTemplate.setInterceptors(Collections.singletonList(new RestInterceptor())); return restTemplate; } /** * [简要描述]:集成重试机制
* [详细描述]:
* * @param restTemplate : * @return com.purcotton.omni.rest.stater.common.service.impl.HttpRetryService * xiaolinlin 2020/1/16 - 18:47 **/ @Bean public HttpRetryService retryService(RestTemplate restTemplate) { log.info("Init http retry support!"); return new HttpRetryService(restTemplate); } /** * [简要描述]:http 同步服务
* [详细描述]:
* * @param retryService: 支持重试请求 * @return com.purcotton.omni.rest.stater.common.service.HttpClientService * xiaolinlin 2020/1/16 - 18:48 **/ @Bean @ConditionalOnProperty(value = "rest.http.service.sync", havingValue = "true") @ConditionalOnMissingBean(HttpClientService.class) public HttpClientService httpClientService(HttpRetryService retryService) { log.info("Use sync http client service!"); return new HttpClientServiceImpl(retryService); } /** * [简要描述]:http 异步服务
* [详细描述]:
* * @param retryService : 支持重试请求 * @return com.purcotton.omni.rest.stater.common.service.HttpClientService * xiaolinlin 2020/1/16 - 18:48 **/ @Bean @ConditionalOnProperty(value = "rest.http.service.async", havingValue = "true") @ConditionalOnMissingBean(HttpClientService.class) public HttpClientService asyncHttpClientService(HttpRetryService retryService) { log.info("User async http client service!"); return new HttpClientAsyncServiceImpl(retryService); } /** * [简要描述]:okhttp3 跳过https验证
* [详细描述]:
* * @return okhttp3.OkHttpClient.Builder * xiaolinlin 2020/1/4 - 10:31 **/ private OkHttpClient.Builder buildOkHttpsClient() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); TrustManager[] trustAllCerts = buildTrustManagers(); SSLContext sslContext = buildSslContext(); if (null != sslContext && null != trustAllCerts) { final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]); } builder.hostnameVerifier((hostname, session) -> true); return builder; } private SSLContext buildSslContext() { TrustManager[] trustAllCerts = buildTrustManagers(); SSLContext sslContext = null; try { sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); } catch (NoSuchAlgorithmException | KeyManagementException e) { log.error("Init SSLContext error :\n", e); } return sslContext; } private TrustManager[] buildTrustManagers() { return new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[] {}; } } }; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/config/properties/HttpPoolProperties.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.config.properties; import lombok.Data; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * [简要描述]: http 连接池参数配置 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 11:09 * @since JDK 1.8 */ @Component @ConfigurationProperties(prefix = "rest.pool") @ConditionalOnProperty(value = "rest.pool.enable", havingValue = "true") @Data public class HttpPoolProperties { private boolean enable; /** * 最大连接数 */ private Integer maxTotal = 20; /** * 最大路由数 */ private Integer defaultMaxPerRoute = 2; /** * 连接超时时间 */ private Integer connectTimeout = 5000; /** * 请求超时时间 */ private Integer connectionRequestTimeout = 1000; /** * socket超时时间 */ private Integer socketTimeout = 6500; /** * 校验时间 */ private Integer validateAfterInactivity = 2000; } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/config/properties/OkHttpProperties.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.config.properties; import lombok.Data; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * [简要描述]: ok http 参数配置 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 11:09 * @since JDK 1.8 */ @Component @ConfigurationProperties("rest.okhttp") @ConditionalOnProperty(value = "rest.okhttp.enable", havingValue = "true") @Data public class OkHttpProperties { private boolean enable; /** * 连接超时时间 */ private int connectionTimeout = 12000; /** * 读超时时间 */ private int readTimeout = 300000; /** * 写超时时间 */ private int writeTimeout = 120000; } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/dto/Request.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.dto; import lombok.Data; import org.springframework.http.HttpHeaders; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 13:48 * @since JDK 1.8 */ @Data public class Request { public static final int POST = 0; public static final int JSON = 1; /** * 请求uri */ private String uri; /** * 返回值类型 */ private Class responseType; /** * 请求参数 */ private Object params; /** * 执行方式:0普通请求,1.JSON请求 */ private int method; /** * 执行请求的ID,用于重复请求更新操作 */ private Long requestId; /** * 自定义请求头 */ private HttpHeaders headers; /** * url变量 */ private Object uriVariables; } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/interceptor/RestInterceptor.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.interceptor; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.log.dto.HttpRequestLog; import com.xiao.custom.rest.starter.autoconfigure.util.ThreadLocalUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import java.io.IOException; import java.sql.Timestamp; import java.util.HashMap; import java.util.List; import java.util.Map; /** * [简要描述]: restTemplate 拦截 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/23 16:47 * @since JDK 1.8 */ @Slf4j public class RestInterceptor implements ClientHttpRequestInterceptor { /** * Intercept the given request, and return a response. The given {@link ClientHttpRequestExecution} allows * the interceptor to pass on the request and response to the next entity in the chain. * *

A typical implementation of this method would follow the following pattern: *

    *
  1. Examine the {@linkplain HttpRequest request} and body
  2. *
  3. Optionally {@linkplain HttpRequestWrapper wrap} the request to filter HTTP attributes.
  4. *
  5. Optionally modify the body of the request.
  6. *
  7. Either * *
  8. Optionally wrap the response to filter HTTP attributes.
  9. *
* * @param request the request, containing method, URI, and headers * @param body the body of the request * @param execution the request execution * @return the response * @exception IOException in case of I/O errors */ @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpRequestLog requestLog = (HttpRequestLog) ThreadLocalUtil.get(HttpRequestLog.REQUEST_LOG); if (null != requestLog) { requestLog.setMethod(request.getMethod().name()); requestLog.setHeader(JSONObject.toJSONString(this.filtrationHeaders(request.getHeaders()))); requestLog.setRequestTime(new Timestamp(System.currentTimeMillis())); } ClientHttpResponse response = execution.execute(request, body); if (null != requestLog) { requestLog.setHttpStatus(response.getStatusCode().value()); requestLog.setResponseTime(new Timestamp(System.currentTimeMillis())); } return response; } private Map filtrationHeaders(HttpHeaders httpHeaders) { Map logMap = null; if (null != httpHeaders) { logMap = new HashMap<>(); Map> headerMap = (Map>) JSONArray.toJSON(httpHeaders); for (Map.Entry> stringListEntry : headerMap.entrySet()) { if (StringUtils.isNotBlank(stringListEntry.getKey()) && CollectionUtils .isNotEmpty(stringListEntry.getValue())) { logMap.put(stringListEntry.getKey(), stringListEntry.getValue()); } } } return logMap; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/log/annotation/RequestLog.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.log.annotation; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * [简要描述]: 请HTTP求日志注解 * [详细描述]: * Retention 注解会在class字节码文件中存在,在运行时可以通过反射获取到 * Inherited 说明子类可以继承父类中的该注解 * Target 既可以在方法上,也可以在类上 * Documented说明该注解将被包含在javadoc中 * * @author llxiao * @version 1.0, 2019/4/24 11:40 * @since JDK 1.8 */ @Retention(RUNTIME) @Inherited @Target(ElementType.METHOD) @Documented public @interface RequestLog { String value() default ""; } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/log/annotation/RequestLogAspect.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.log.annotation; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import com.xiao.custom.rest.starter.autoconfigure.log.dto.HttpRequestLog; import com.xiao.custom.rest.starter.autoconfigure.log.service.HttpRequestLogService; import com.xiao.custom.rest.starter.autoconfigure.util.ThreadLocalUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.sql.Timestamp; /** * [简要描述]: 请求日志切面 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 11:39 * @since JDK 1.8 */ @Aspect @Component @Slf4j public class RequestLogAspect { /** * 请求响应最大长度 */ private static final int MAX_PARAMS_LENGTH = 256; /** * 日志服务 */ @Autowired(required = false) private HttpRequestLogService httpRequestLogService; /** * [简要描述]:定义一个annotation切入点
* [详细描述]:切入点
* llxiao 2018/9/2 - 17:02 **/ @Pointcut("@annotation(com.xiao.custom.rest.starter.autoconfigure.log.annotation.RequestLog)") public void logAnnotatison() { } /** * [简要描述]:around 切面强化
* [详细描述]:
* * @param joinPoint : * @return Object * llxiao 2019/11/27 - 19:10 **/ @Around("logAnnotatison()") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { HttpRequestLog requestLog = null; Object retrunobj = null; Object[] args = joinPoint.getArgs(); if (args.length > 0) { Object params = args[0]; if (params instanceof Request) { Request request = (Request) params; Long requestId = request.getRequestId(); if (null == requestId) { requestLog = new HttpRequestLog(); requestLog.setCreateTime(new Timestamp(System.currentTimeMillis())); requestLog.setRequest(subParams(JSONObject.toJSONString(request))); requestLog.setUri(request.getUri()); requestLog.setParams(subParams(JSONObject.toJSONString(request.getParams()))); requestLog.setResponseType(request.getResponseType().getName()); } ThreadLocalUtil.put(HttpRequestLog.REQUEST_LOG, requestLog); } } try { retrunobj = joinPoint.proceed(args); if (null != requestLog) { requestLog.setResponse(subParams(JSONObject.toJSONString(retrunobj))); } } catch (Throwable e) { if (null != requestLog) { requestLog.setErrorMsg(e.getMessage()); } log.error("Http 请求执行错误: ", e); throw e; } finally { //删除当前线程保存数据,防止内存溢出 ThreadLocalUtil.remove(); if (null != httpRequestLogService) { httpRequestLogService.saveRequestLog(requestLog); } // else // { // log.info("Http 执行日志:{}", JSONObject.toJSONString(requestLog)); // } } return retrunobj; } /** * [简要描述]:参数截取,参数太长超过2000直接用*号代替
* [详细描述]:
* * @param toJsonString : * @return java.lang.String * llxiao 2019/8/8 - 11:43 **/ private String subParams(String toJsonString) { String params = ""; if (StringUtils.isNotEmpty(toJsonString)) { if (toJsonString.length() > MAX_PARAMS_LENGTH) { params = toJsonString.substring(0, MAX_PARAMS_LENGTH); } else { params = toJsonString; } } return params; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/log/dto/HttpRequestLog.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.log.dto; import lombok.Data; import java.sql.Timestamp; /** * [简要描述]: 请求日志,以此来做请求补偿,请求日志记录等等 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 10:00 * @since JDK 1.8 */ @Data public class HttpRequestLog { public static final String REQUEST_LOG = "HttpRequestLog"; /** * 主键ID */ private Long id; /** * 请求Url */ private String uri; /** * 请求方式 */ private String method; /** * JSON键值对header */ private String header; /** * 请求参数,JSON数据 */ private String params; /** * 响应参数,JSON数据 */ private String response; /** * 响应参数需要转换的类型 */ private String responseType; /** * http状态 */ private int httpStatus; /** * 请求最终状态 */ private int status; /** * 尝试次数 */ private int tryNum; /** * 整个请求request-JSON串 */ private String request; /** * 错误消息 */ private String errorMsg; /** * 请求时间 */ private Timestamp requestTime; /** * 响应时间 */ private Timestamp responseTime; private Timestamp createTime; private Timestamp updateTime; } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/log/service/HttpRequestLogService.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.log.service; import com.xiao.custom.rest.starter.autoconfigure.log.dto.HttpRequestLog; /** * [简要描述]: http 请求日志记录 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/29 15:33 * @since JDK 1.8 */ public interface HttpRequestLogService { /** * [简要描述]:保存日志信息
* [详细描述]:
* * @param requestLog : * llxiao 2019/4/24 - 14:42 **/ void saveRequestLog(HttpRequestLog requestLog); } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/service/HttpClientService.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.service; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import org.springframework.http.ResponseEntity; /** * [简要描述]: http服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/23 19:53 * @since JDK 1.8 */ public interface HttpClientService { /** * [简要描述]:发起post请求
* [详细描述]:
* * @param request : 请求参数 * @return T * llxiao 2019/4/23 - 19:56 **/ T doForObject(Request request); T doRequest(Request request); T getForObject(Request request); /** * [简要描述]:formdata 获取请求Response
* [详细描述]:
* * @param request : * @return org.springframework.http.ResponseEntity * llxiao 2019/8/26 - 16:33 **/ ResponseEntity postFormData(Request request); } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/service/impl/HttpClientAsyncServiceImpl.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.service.impl; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import com.xiao.custom.rest.starter.autoconfigure.log.annotation.RequestLog; import com.xiao.custom.rest.starter.autoconfigure.service.HttpClientService; import com.xiao.custom.rest.starter.autoconfigure.util.RequestValidatorParamsUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; /** * [简要描述]: CompletableFuture实现http异步服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 09:25 * @since JDK 1.8 */ @Slf4j public class HttpClientAsyncServiceImpl implements HttpClientService { private HttpClientServiceImpl httpClientService; public HttpClientAsyncServiceImpl(HttpRetryService retryService) { this.httpClientService = new HttpClientServiceImpl(retryService); } /** * [简要描述]: 发起post请求
* [详细描述]: @Retryable默认重试 等待2000ms 3次 * * @param request : 请求参数 * @return T * llxiao 2019/4/23 - 19:56 **/ @Override @RequestLog public T doForObject(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } CompletableFuture tCompletableFuture = CompletableFuture .supplyAsync(() -> httpClientService.doForObject(request)); return futureResult(tCompletableFuture, request); } @Override @RequestLog public T doRequest(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } CompletableFuture tCompletableFuture = CompletableFuture .supplyAsync(() -> httpClientService.doRequest(request)); return futureResult(tCompletableFuture, request); } /** * [简要描述]: 不进行encode编码的get请求 * [详细描述]: 请求参数中的url必须进行手动encode编码 * * @param request : 请求参数 * @return T * mjye 2019/10/23 - 11:32 **/ @Override @RequestLog public T getForObject(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } CompletableFuture future = CompletableFuture.supplyAsync(() -> httpClientService.doForObject(request)); return futureResult(future, request); } /** * [简要描述]:formdata 获取请求Response
* [详细描述]:
* * @param request : * @return org.springframework.http.ResponseEntity * llxiao 2019/8/26 - 16:33 **/ @Override @RequestLog public ResponseEntity postFormData(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } CompletableFuture> completableFuture = CompletableFuture .supplyAsync(() -> httpClientService.postFormData(request)); return futureResult(completableFuture, request); } private T futureResult(CompletableFuture tCompletableFuture, Request request) { try { return tCompletableFuture.get(); } catch (InterruptedException e) { log.error("请求参数:{}", JSONObject.toJSONString(request)); log.error("Http异步请求线程中断:", e); } catch (ExecutionException e) { log.error("请求参数:{}", JSONObject.toJSONString(request)); log.error("Http异步请求异常:", e); } return null; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/service/impl/HttpClientServiceImpl.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.service.impl; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import com.xiao.custom.rest.starter.autoconfigure.log.annotation.RequestLog; import com.xiao.custom.rest.starter.autoconfigure.service.HttpClientService; import com.xiao.custom.rest.starter.autoconfigure.util.RequestValidatorParamsUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.util.Map; /** * [简要描述]: http同步阻塞服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 09:25 * @since JDK 1.8 */ @Slf4j public class HttpClientServiceImpl implements HttpClientService { private static final String JSON_UTF_8 = "application/json; charset=UTF-8"; private static final String HEADER_ACCEPT = "Accept"; /** * 带重试机制 */ private HttpRetryService retryService; public HttpClientServiceImpl(HttpRetryService retryService) { this.retryService = retryService; } /** * [简要描述]: 发起post请求
* [详细描述]: @Retryable默认重试 等待1000ms 3次 * * @param request : 请求参数 * @return T * llxiao 2019/4/23 - 19:56 **/ @Override @RequestLog public T doForObject(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } T entity = null; if (null != request) { String uri = request.getUri(); Object params = request.getParams(); int method = request.getMethod(); Class responseType = request.getResponseType(); HttpHeaders headers = request.getHeaders(); Object uriVariables = request.getUriVariables(); if (Request.POST == method) { entity = retryService.postForObject(uri, params, responseType, uriVariables); } else if (Request.JSON == method) { if (null == headers) { headers = new HttpHeaders(); } headers.setContentType(MediaType.parseMediaType(JSON_UTF_8)); headers.add(HEADER_ACCEPT, MediaType.APPLICATION_JSON.toString()); HttpEntity formEntity = new HttpEntity<>(JSONObject.toJSONString(params), headers); entity = retryService.postForObject(uri, formEntity, responseType, uriVariables); } else { log.error("当期请求暂不支持的操作,请求参数:{}", JSONObject.toJSONString(request)); } } return entity; } /** * [简要描述]:普通HTTP请求
* [详细描述]:喆道对接在使用
* * @param request : * @return T * xiaolinlin 2020/1/16 - 18:38 **/ @Override @RequestLog public T doRequest(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } return retryService.doRequest(request.getUri(), request.getParams(), request.getResponseType()); } /** * [简要描述]: 不进行encode编码的get请求 * [详细描述]: 请求参数中的url必须进行手动encode编码 * * @param request : 请求参数 * @return T * mjye 2019/10/23 - 11:32 **/ @Override @RequestLog public T getForObject(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } return retryService.getForObject(request.getUri(), request.getHeaders(), request.getResponseType()); } /** * [简要描述]:formdata 获取请求Response
* [详细描述]:
* * @param request : * @return org.springframework.http.ResponseEntity * llxiao 2019/8/26 - 16:33 **/ @Override @RequestLog public ResponseEntity postFormData(Request request) { if (RequestValidatorParamsUtil.validateParams(request)) { return null; } HttpEntity httpEntity = null; Object params = request.getParams(); // 请求参数为httpEntity直接发送请求 if (params instanceof HttpEntity) { httpEntity = (HttpEntity) params; } // 需要重新组装 HttpEntity else if (params instanceof MultiValueMap) { MultiValueMap multiValueMap = (MultiValueMap) params; httpEntity = new HttpEntity<>(multiValueMap, request.getHeaders()); } else if (params instanceof Map) { Map parmasMap = (Map) params; MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); for (Map.Entry entry : parmasMap.entrySet()) { multiValueMap.add(entry.getKey(), entry.getValue()); } httpEntity = new HttpEntity<>(multiValueMap, request.getHeaders()); } else { log.error("当亲请求暂不支持的操作请求,请求数据:{}", JSONObject.toJSONString(request)); } if (null != httpEntity) { return retryService.postFormData(httpEntity, request.getUri(), request.getResponseType()); } else { log.error("请求异常,无法识别请求数据,请求数据:{}", JSONObject.toJSONString(request)); return null; } } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/service/impl/HttpRetryService.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.service.impl; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import java.net.SocketTimeoutException; /** * [简要描述]: 重试不能使用接口实现类 * [详细描述]: * 重试机制注解说明(https://blog.csdn.net/u011116672/article/details/77823867): * EnableRetry注解: * 能否重试,proxyTargetClass属性为true时(默认false),使用CGLIB代理 *

* Retryable注解:注解需要被重试的方法 * include 指定处理的异常类。默认为空 * exclude指定不需要处理的异常。默认为空 * vaue指定要重试的异常。默认为空 * maxAttempts 最大重试次数。默认3次 * backoff 重试等待策略。默认使用@Backoff注解 *

* Backoff注解:重试回退策略(立即重试还是等待一会再重试) * 不设置参数时,默认使用FixedBackOffPolicy,重试等待1000ms * 只设置delay()属性时,使用FixedBackOffPolicy,重试等待指定的毫秒数 * 当设置delay()和maxDealy()属性时,重试等待在这两个值之间均态分布 * 使用delay(),maxDealy()和multiplier()属性时,使用ExponentialBackOffPolicy * 当设置multiplier()属性不等于0时,同时也设置了random()属性时,使用ExponentialRandomBackOffPolicy *

* Recover注解: 用于方法。 * 用于@Retryable失败时的“兜底”处理方法。 @Recover注释的方法必须要与@Retryable注解的方法“签名”保持一致,第一入参为要重试的异常,其他参数与@Retryable保持一致,返回值也要一样,否则无法执行! *

* CircuitBreaker注解:用于方法,实现熔断模式。 * include 指定处理的异常类。默认为空 * exclude指定不需要处理的异常。默认为空 * vaue指定要重试的异常。默认为空 * maxAttempts 最大重试次数。默认3次 * openTimeout 配置熔断器打开的超时时间,默认5s,当超过openTimeout之后熔断器电路变成半打开状态(只要有一次重试成功,则闭合电路) * resetTimeout 配置熔断器重新闭合的超时时间,默认20s,超过这个时间断路器关闭 * * @author xiaolinlin * @version 1.0, 2020/1/16 17:45 * @since JDK 1.8 */ public class HttpRetryService { private RestTemplate restTemplate; public HttpRetryService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } /** * [简要描述]:发起post请求
* [详细描述]:
* * @param uri : * @param params : * @param responseType : * @param uriVariables : * @return T * xiaolinlin 2020/1/16 - 18:20 **/ @Retryable(value = SocketTimeoutException.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5)) public T postForObject(String uri, Object params, Class responseType, Object uriVariables) { if (StringUtils.isNotBlank(uri) && null != params && null != responseType) { ResponseEntity responseEntity = restTemplate.postForEntity(uri, params, responseType, uriVariables); return null == responseEntity ? null : responseEntity.getBody(); } return null; } /** * [简要描述]:formdata 获取请求Response
* [详细描述]:
* * @param httpEntity : * @param uri : * @param responseType : * @return org.springframework.http.ResponseEntity * xiaolinlin 2020/1/16 - 18:27 **/ @Retryable(value = SocketTimeoutException.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5)) public ResponseEntity postFormData(HttpEntity httpEntity, String uri, Class responseType) { if (null != httpEntity && StringUtils.isNotBlank(uri) && null != responseType) { return restTemplate.postForEntity(uri, httpEntity, responseType); } return null; } /** * [简要描述]:不进行encode编码的get请求
* [详细描述]:
* * @param uri : * @param headers : * @param responseType : * @return T * xiaolinlin 2020/1/16 - 18:33 **/ @Retryable(value = SocketTimeoutException.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5)) public T getForObject(String uri, HttpHeaders headers, Class responseType) { if (StringUtils.isNotBlank(uri) && null != responseType) { HttpEntity requestEntity = null; if (null != headers) { requestEntity = new HttpEntity<>(null, headers); } UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(uri); ResponseEntity resEntity = restTemplate .exchange(builder.build(true).toUri(), HttpMethod.GET, requestEntity, responseType); return resEntity.getBody(); } return null; } /** * [简要描述]:普通post请求
* [详细描述]:
* * @param uri : * @param params : * @param responseType : * @return T * xiaolinlin 2020/1/16 - 18:37 **/ @Retryable(value = SocketTimeoutException.class, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 1.5)) public T doRequest(String uri, Object params, Class responseType) { if (StringUtils.isNotBlank(uri) && null != params && null != responseType) { return (T) restTemplate.postForObject(uri, params, responseType); } return null; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/util/RequestValidatorParamsUtil.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.util; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; /** * [简要描述]: * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/1/16 15:47 * @since JDK 1.8 */ @Slf4j public class RequestValidatorParamsUtil { public static boolean validateParams(Request request) { if (null == request || StringUtils.isEmpty(request.getUri()) || null == request.getResponseType()) { log.error("请求参数不能为空:{}", null == request ? "Request is null!" : JSONObject.toJSONString(request)); return true; } return false; } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/java/com/xiao/custom/rest/starter/autoconfigure/util/ThreadLocalUtil.java ================================================ package com.xiao.custom.rest.starter.autoconfigure.util; import java.util.HashMap; import java.util.Map; /** * [简要描述]: ThreadLocalUtil * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/24 09:25 * @since JDK 1.8 */ public class ThreadLocalUtil { private static final ThreadLocal> context = ThreadLocal.withInitial(() -> new HashMap<>()); public static Map getThreadLocal() { return context.get(); } /** * [简要描述]:从ThreadLocal中获取一个线程变量
* [详细描述]:不存在,返回null
* * @param key : * @return java.lang.Object * llxiao 2019/4/24 - 9:43 **/ public static Object get(String key) { Map map = context.get(); if (null != map) { return map.get(key); } return null; } /** * [简要描述]:设置一个键值对到ThreadLocal中
* [详细描述]:
* * @param key : * @param value : * llxiao 2019/4/24 - 9:42 **/ public static void put(String key, Object value) { Map map = context.get(); if (null == map) { map = new HashMap<>(); context.set(map); } map.put(key, value); } /** * [简要描述]:从ThreadLocal的当前线程中删除一个key
* [详细描述]:
* * @param key : * @return void * llxiao 2019/4/24 - 9:41 **/ public static void remove(String key) { Map map = context.get(); if (null != map) { map.remove(key); } } /** * [简要描述]:从ThreadLocal中移除当前线程的变量
* [详细描述]:
*

* llxiao 2019/4/24 - 9:41 **/ public static void remove() { context.remove(); } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Autconfigure/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xiao.custom.rest.starter.autoconfigure.config.RestTemplateConfiguration ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/pom.xml ================================================ SpringBoot-Custom-Rest-Starter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringBoot-Custom-Rest-Example com.xiao.skywalking.demo SpringBoot-Custom-Rest-Autconfigure ${project.version} org.projectlombok lombok 1.16.18 true org.springframework.boot spring-boot-starter-test test ${project.artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/main/java/com/xiao/custom/rest/example/RestExampleApp.java ================================================ package com.xiao.custom.rest.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * [简要描述]: * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/1/17 20:03 * @since JDK 1.8 */ @SpringBootApplication public class RestExampleApp { public static void main(String[] args) { SpringApplication.run(RestExampleApp.class, args); } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/main/java/com/xiao/custom/rest/example/log/impl/HttpLogServiceImpl.java ================================================ package com.xiao.custom.rest.example.log.impl; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.rest.starter.autoconfigure.log.dto.HttpRequestLog; import com.xiao.custom.rest.starter.autoconfigure.log.service.HttpRequestLogService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * [简要描述]: HTTP 日志处理实现类 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 10:26 * @since JDK 1.8 */ @Service @Slf4j public class HttpLogServiceImpl implements HttpRequestLogService { /** * [简要描述]:保存日志信息
* [详细描述]:
* * @param requestLog : * llxiao 2019/4/24 - 14:42 **/ @Override public void saveRequestLog(HttpRequestLog requestLog) { log.info("Example log : {}", JSONObject.toJSONString(requestLog)); } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/main/resources/application.yml ================================================ rest: # okhttp 配置 okhttp: enable: false connection-timeout: 12000 read-timeout: 30000 write-timeout: 12000 # http pool pool: enable: true max-total: 20 default-max-per-route: 2 validate-after-inactivity: 2000 connect-timeout: 10000 connection-request-timeout: 10000 socket-timeout: 10000 http: service: sync: true async: false ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/main/resources/logback-spring.xml ================================================ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/test/java/com/xiao/custom/rest/example/RestTemplateStarterAppTest.java ================================================ package com.xiao.custom.rest.example; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 10:30 * @since JDK 1.8 */ @RunWith(SpringRunner.class) @SpringBootTest public class RestTemplateStarterAppTest { } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/test/java/com/xiao/custom/rest/example/httpclient/HttpClientTest.java ================================================ package com.xiao.custom.rest.example.httpclient; import com.xiao.custom.rest.example.RestTemplateStarterAppTest; import com.xiao.custom.rest.starter.autoconfigure.dto.Request; import com.xiao.custom.rest.starter.autoconfigure.service.HttpClientService; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; /** * [简要描述]: HTTP client测试 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 10:32 * @since JDK 1.8 */ public class HttpClientTest extends RestTemplateStarterAppTest { @Autowired private HttpClientService httpClientService; @Test public void testHttpGet() { Request request = new Request(); request.setResponseType(String.class); request.setUri("http://www.baidu.com"); String forObject = httpClientService.getForObject(request); System.out.println(forObject); } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/SpringBoot-Custom-Rest-Example/src/test/java/com/xiao/custom/rest/example/template/RestTemplateTest.java ================================================ package com.xiao.custom.rest.example.template; import com.xiao.custom.rest.example.RestTemplateStarterAppTest; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.client.RestTemplate; /** * [简要描述]: resttemplate测试类 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/30 10:31 * @since JDK 1.8 */ public class RestTemplateTest extends RestTemplateStarterAppTest { @Autowired private RestTemplate restTemplate; @Test public void testRest() { } } ================================================ FILE: SpringBoot-Custom-Rest-Starter/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 pom SpringBoot-Custom-Rest-Autconfigure SpringBoot-Custom-Rest-Example SpringBoot-Custom-Rest-Starter ================================================ FILE: SpringBoot-Stock-Demo/doc/bootstrap.sh ================================================ #!/bin/bash ## 加载配置,避免获取不到java_home source /etc/profile SERVICE_HOME=/home/admin/services SERVICE_NAME=omni-basis-service cd $SERVICE_HOME/$SERVICE_NAME PROG=$SERVICE_HOME/$SERVICE_NAME PIDFILE=$SERVICE_HOME/$SERVICE_NAME/$SERVICE_NAME.pid JARFILE=$SERVICE_HOME/$SERVICE_NAME/$SERVICE_NAME.jar SKYWALKING_AGENT=-javaagent:/home/admin/agent/skywalking-agent.jar SKYWALKING_SERVCIE_NAME=OMNI-BASIS-SERVICE status() { if [ -f $PIDFILE ]; then PID=$(cat $PIDFILE) if [ ! -x /proc/${PID} ]; then return 1 else return 0 fi else return 1 fi } case "$1" in start) status RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----$PIDFILE exists, process is already running or crashed" exit 1 fi ##检测 java环境 if [ ! -n $JAVA_HOME ]; then echo "-----Please check JAVA_HOME!" echo "-----Exist" exist 1 else echo "-----Jave home: $JAVA_HOME" echo "-----Starting $PROG ..." #nohup java -server -Xms512m -Xmx512m -jar $JARFILE > $SERVICE_NAME.log 2>&1 & nohup java -server -Xms512m -Xmx512m $SKYWALKING_AGENT -Dskywalking.agent.service_name=$SKYWALKING_SERVCIE_NAME -jar $JARFILE > $SERVICE_NAME.log 2>&1 & RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----$PROG is started" echo $! > $PIDFILE exit 0 else echo "-----Stopping $PROG" rm -f $PIDFILE exit 1 fi fi ;; stop) status RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----Shutting down $PROG" kill -15 `cat $PIDFILE` RETVAL=$? if [ $RETVAL -eq 0 ]; then rm -f $PIDFILE else echo "-----Failed to stopping $PROG" fi fi ;; status) status RETVAL=$? if [ $RETVAL -eq 0 ]; then PID=$(cat $PIDFILE) echo "-----$PROG is running ($PID)" else echo "-----$PROG is not running" fi ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart|status}" ;; esac ================================================ FILE: SpringBoot-Stock-Demo/doc/stock.sql ================================================ CREATE TABLE `t_stock_demo`( `id` bigint(64) NOT NULL AUTO_INCREMENT, `product_no` varchar(64) NULL, `shop_code` varchar(255) NULL, `pre_stock` int(3) DEFAULT 0, `ava_stock` int(3) DEFAULT 0, `total_stock` int(3) DEFAULT 0 , `update_time` timestamp(0) NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_order_demo`( `id` bigint(64) NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NULL, `shop_code` varchar(255) NULL, `product_no` varchar(64) DEFAULT 0, `product_num` int(3) DEFAULT 0 , `status` int(3) NULL, `update_time` timestamp(0) NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_stock_change_log_demo`( `id` bigint(64) NOT NULL AUTO_INCREMENT, `product_no` varchar(64) NULL, `shop_code` varchar(255) NULL, `order_no` varchar(255) NULL, `opt_type` varchar(255) NULL, `pre_stock_after` int(3) DEFAULT 0, `ava_stock_after` int(3) DEFAULT 0, `total_stock_after` int(3) DEFAULT 0 , `pre_stock_before` int(3) DEFAULT 0, `ava_stock_before` int(3) DEFAULT 0, `total_stock_before` int(3) DEFAULT 0 , `update_time` timestamp(0) NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4; ================================================ FILE: SpringBoot-Stock-Demo/doc/stock_demo_jmeter.jmx ================================================ false true false continue false 10 5 1 false JDBC操作、ifelse、随机数、用户参数、控制器、定时器、循环、计数器、变量、函数等等Jmeter并发操作库存实操,下单、取消单、出库、添加库存等操作并发执行,分布式并发执行。 问题:jmeter并发执行操作订单(取消、出库)时,订单加redisson分布式锁解决订单重复操作问题 172.16.80.194 7878 6 192.168.206.240 7878 6 true select 1 5000 stockDemo jdbc:mysql://192.168.206.201:3306/basisdb?useSSL=false&allowMultiQueries=true com.mysql.jdbc.Driver true Basisuser123 20 10000 DEFAULT 60000 basisuser shopCode 1000 false productNo A001001001 A001001002 A001001003 A001001004 A001001005 A001001006 true productNo A001001001 false stockDemo Select Statement select ava_stock from `t_stock_demo` where shop_code = '${shopCode}' and product_no = '${productNo}'; avaStock Store as String ${__jexl3(${avaStock_1}>0 ,)} false true num 3 2 4 1 true false ${productNo} = true productNo false ${shopCode} = true shopCode false ${num} = true num /stock/preStock POST true false true false java 操作response as string。 prev api : https://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html beanshell docs: https://jmeter.apache.org/usermanual/component_reference.html#BeanShell_Assertion String result = prev.getResponseDataAsString(); if("0".equals(result)){ Failure = true; } else { Failure = false; } false num 3 2 4 1 true false ${productNo} = true productNo false ${shopCode} = true shopCode false ${num} = true num /stock/preStock POST true false true false java 操作response as string。 prev api : https://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html beanshell docs: https://jmeter.apache.org/usermanual/component_reference.html#BeanShell_Assertion String result = prev.getResponseDataAsString(); if("0".equals(result)){ Failure = true; FailureMessage = "下单失败,库存不足!"; } else { Failure = false; } false stockDemo Select Statement SELECT count(order_no) FROM `t_order_demo` where status = 1; count Store as String tools 工具选项使用 jex13 表达式 ${__jexl3(${count_1}>0 ,)} false true stockDemo SELECT order_no FROM `t_order_demo` where status = 1; Select Statement Store as String orderNo true 获取 sql count返回的结果 ${count_1} 1 1 index 0 true 从1开始,递增1 true false ${__V(orderNo_${index})} = true orderNo /stock/releaseStock POST true false true false ${__jexl3(${count_1}>1 ,)} false true false ${__V(orderNo_${__Random(1,${count_1})})} = true orderNo /stock/releaseStock POST true false true false ${__jexl3(${count_1}==1 ,)} false true false ${__V(orderNo_1)} = true orderNo /stock/releaseStock POST true false true false stockDemo Select Statement SELECT count(order_no) FROM `t_order_demo` where status = 1; count Store as String ${__jexl3(${count_1}>0 ,)} false true stockDemo SELECT order_no FROM `t_order_demo` where status = 1; Select Statement Store as String orderNo true 获取 sql count返回的结果 ${count_1} 1 1 index 0 true 从1开始,递增1 true false ${__V(orderNo_${index})} = true orderNo /stock/releaseStock POST true false true false ${__jexl3(${count_1}>1 ,)} false true false ${__V(orderNo_${__Random(1,${count_1})})} = true orderNo /stock/warehouse POST true false true false ${__jexl3(${count_1}==1 ,)} false true false ${__V(orderNo_1)} = true orderNo /stock/warehouse POST true false true false 定时任务,每10分钟执行一次加库存操作 productNo A001001001 A001001002 A001001003 A001001004 A001001005 A001001006 true productNo A001001001 false num 1 2 3 true false ${shopCode} = true shopCode false ${num} = true num false ${productNo} = true productNo /stock/addStock POST true false true false 30000 false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true false saveConfig true true true true true true true false true true false false false true false false false true 0 true true true true true true ================================================ FILE: SpringBoot-Stock-Demo/pom.xml ================================================ 4.0.0 com.purcotton.stock.demo stock-demo 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE UTF-8 UTF-8 1.8 Dalston.RC1 5.1.2 2.04 3.11.4 4.6.8 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-aop mysql mysql-connector-java runtime com.zaxxer HikariCP 3.3.1 org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 com.github.pagehelper pagehelper-spring-boot-starter 1.2.5 com.github.pagehelper pagehelper 5.1.4 org.projectlombok lombok 1.18.4 provided com.alibaba fastjson 1.2.83 org.apache.commons commons-collections4 4.1 org.apache.commons commons-lang3 3.6 org.redisson redisson ${redission.version} cn.hutool hutool-core ${hutool.version} cn.hutool hutool-captcha ${hutool.version} org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import stock-demo ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringBoot-Stock-Demo/readme.md ================================================ **Jmeter+Springboot+Redisson分布式锁并发订单操作(下单、取消单、完成单、加库存)**
涉及知识点:
> java+springboot+mybatis开发
> redis分布式锁+Redisson客户端
> Jmeter各种骚操作:用户变量、随机取值、jdbc操作、if else操作、循环、控制器、beanshell断言等等
1. 环境工具:
idea、jmeter
jdk1.8、maven、mysql、redis
三台服务器:两个4C16G服务节点+一个台nginx(淘宝的tengine-2.3.0)节点
2. 思路概要:
>(1) 主要提供四个接口:下单、取消、出库、添加库存,四种操作在操作库存表t_stock_demo行的时候都需要添加Redis的锁,使用:``Future res = fairLock.tryLockAsync(50, 10, TimeUnit.SECONDS);``
>(2) 另外取消和出库,因为是用Jmeter直接查询数据库获取可用的订单数量,为防止统一订单重复操作在RestSevice层使用订单号orderNo做了一层Redis分布式锁,订单已在操作直接返回结果。
>(3) 使用jmeter的jdbc操作+函数、随机数获取已确认的订单结合if控制器判断结果,进行取消和出库操作
>(4) 划重点:使用分布式锁和本地事物,一定要**先提交事物再释放锁、先提交事物再放锁、先提交事物再放锁**
3. SQL、jmeter脚本、jar包启动脚本请到[doc](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringBoot-Stock-Demo/doc)目录查看。 4. 操作指南: >(1) git clone https://github.com/Xlinlin/SpringCloud-Demo
>(2) cd SpringCloud-Demo/SpringBoot-Stock-Demo
>(3) 配置数据mysql和redis配置,application.yml文件,(自行准备mysql、redis环境)
>(4) mvn install
>(5) 拷贝stock_demo.jar和doc/bootstrap.sh到 linux服务器(自行准备java环境)上
>(6) 适当修改bootstrap.sh脚本目录,保持与springboot包在同一目录,直接执行脚本:``./bootstrap start``
>(7) 查看进程、端口是否启动:``jps 或 ps -ef|grep stock_demo 或 lsof -i:7878``
>(8) 配好nginx跳转 >(9) 下载[jmeter](http://jmeter.apache.org/download_jmeter.cgi) ,解压进入jmeter目录,双击:ApacheJMeter
>(10) 文件->打开->找到doc下的[.jmx](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Stock-Demo/doc/stock_demo_jmeter.jmx)文件,大概的画面:
![](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Stock-Demo/doc/stock_demo_jmeter.jpg?raw=true)
>(11) 修改远程服务器地址信息为你的nginx服务
>(12) 修改你的数据地址,此处需要将mysql的驱动jar包引入jmeter/lib目录下
>(13) 线程、参数、请求调整好后,然后点击启动(Ctrl+R)
部分截图: >(14) 后台日志![](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Stock-Demo/doc/sever_console_log.jpg?raw=true) >(15) 库存表![](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Stock-Demo/doc/stock_query.jpg?raw=true) >(16) 订单表![](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringBoot-Stock-Demo/doc/order_query.jpg?raw=true) 5. 遗留一个业务问题:
总库存(Total) = 可用库存(Ava) + 预占库存(Prev)
A:下单:T A- P+
B:取消:T A+ P-
C:出库:T- A P-
D:同步库存+:T+ A+ P
E:同步库存-:T- A- P
如果仅仅只是 A+B 或者 A+C 或A B C并发跑能保证 T=P+A
但是 A+C +D +E 并发跑,就一定会出现 打破这个 T=P+A的平衡,这个要业务逻辑要怎么处理?有大佬解答?
================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/StockDemoApplication.java ================================================ package com.xiao.stock.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; /** * [简要描述]: 库存扣减测试 * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 09:23 * @since JDK 1.8 */ @SpringBootApplication(scanBasePackages = "com.purcotton.stock.demo", exclude = DataSourceAutoConfiguration.class) @MapperScan("com.purcotton.stock.demo.mapper") public class StockDemoApplication { public static void main(String[] args) { SpringApplication.run(StockDemoApplication.class, args); } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/common/StockOptTypeEnum.java ================================================ package com.xiao.stock.demo.common; /** * [简要描述]: 库存操作记录 * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 15:13 * @since JDK 1.8 */ public enum StockOptTypeEnum { LOCK_STOCK("LOCK_STOCK", "预占锁库存"), RELEASE_STOCK("RELEASE_STOCK", "释放预占库存"), OUT_WAREHOUSE_STOCK("OUT_WAREHOUSE_STOCK", "订单出库"), ADD_STOCK("ADD_STOCK","添加库存"); private String optType; private String optName; StockOptTypeEnum(String optType, String optName) { this.optType = optType; this.optName = optName; } public String getOptType() { return optType; } public String getOptName() { return optName; } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/configure/DataSourceConfiguration.java ================================================ package com.xiao.stock.demo.configure; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DataSourceConfiguration { @Value(value = "${hikari-jdbc-url}") private String hikariJdbcUrl = ""; @Value(value = "${hikari-jdbc-password}") private String hikariJdbcPassword; @Value(value = "${hikari-jdbc-username}") private String hikariJdbcUsername; @Value(value = "${hikari-jdbc-driver-class-name}") private String hikariJdbcDriverClassName; @Value(value = "${hikari-jdbc-pool-size}") private int hikariJdbcPoolSize; // Hikari 连接池 @Bean(name = "dataSource") public DataSource dataSource() { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl(hikariJdbcUrl); ds.setUsername(hikariJdbcUsername); ds.setPassword(hikariJdbcPassword); ds.setDriverClassName(hikariJdbcDriverClassName); ds.setMaximumPoolSize(hikariJdbcPoolSize); ds.setConnectionInitSql("set names utf8mb4;"); return ds; } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/configure/RedissonConfiguration.java ================================================ package com.xiao.stock.demo.configure; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 10:49 * @since JDK 1.8 */ @Configuration public class RedissonConfiguration { @Value("${redisson.host}") private String redisHost; @Value("${redisson.password}") private String password; @Value("${thread.pool.core.size:50}") private int coreSize; @Value("${thread.pool.max.size:100}") private int maxSize; @Value("${thread.pool.queue.capacity:10000}") private int queueCapacity; @Bean public Config config() { Config config = new Config(); config.useSingleServer().setAddress(redisHost); config.useSingleServer().setPassword(password); return config; } @Bean public RedissonClient redissonClient(Config config) { return Redisson.create(config); } @Bean public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(coreSize); threadPoolTaskExecutor.setMaxPoolSize(maxSize); threadPoolTaskExecutor.setQueueCapacity(queueCapacity); threadPoolTaskExecutor.setKeepAliveSeconds(60); threadPoolTaskExecutor.setThreadNamePrefix("Stock-Demo-"); return threadPoolTaskExecutor; } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/entity/OrderDemo.java ================================================ package com.xiao.stock.demo.entity; import lombok.*; import java.util.Date; /** * t_order_demo * Created by Mybatis Generator on 2019/10/19 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class OrderDemo { /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.id * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private Long id; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.order_no * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private String orderNo; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.shop_code * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private String shopCode; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.product_no * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private String productNo; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.product_num * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private Integer productNum; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_order_demo.update_time * * @mbggenerated Sat Oct 19 09:17:42 CST 2019 */ private Date updateTime; private Integer status; } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/entity/StockChangeLodDemo.java ================================================ package com.xiao.stock.demo.entity; import lombok.*; import java.util.Date; /** * t_stock_change_log_demo * Created by Mybatis Generator on 2019/10/17 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class StockChangeLodDemo { /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.id */ private Long id; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.product_no */ private String productNo; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.shop_code */ private String shopCode; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.order_no */ private String orderNo; /** * 操作类型 */ private String optType; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.pre_stock_after */ private Integer preStockAfter; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.ava_stock_after */ private Integer avaStockAfter; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.total_stock_after */ private Integer totalStockAfter; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.pre_stock_before */ private Integer preStockBefore; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.ava_stock_before */ private Integer avaStockBefore; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.total_stock_before */ private Integer totalStockBefore; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_change_log_demo.update_time */ private Date updateTime; } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/entity/StockDemo.java ================================================ package com.xiao.stock.demo.entity; import lombok.*; import java.util.Date; /** * t_stock_demo * Created by Mybatis Generator on 2019/10/17 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class StockDemo { /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.id */ private Long id; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.product_no */ private String productNo; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.shop_code */ private String shopCode; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.pre_stock */ private Integer preStock; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.ava_stock */ private Integer avaStock; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.total_stock */ private Integer totalStock; /** * This field was generated by MyBatis Generator. * This field corresponds to the database column t_stock_demo.update_time */ private Date updateTime; /** * 订单号 */ private String orderNo; /** * 商品数量 */ private int productNum; /** * 更新预占库存 */ private Integer newPreStock; /** * 更新可用库存 */ private Integer newAvaStock; /** * 更新总库存 */ private Integer newTotalStock; } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/mapper/OrderDemoMapper.java ================================================ package com.xiao.stock.demo.mapper; import com.xiao.stock.demo.entity.OrderDemo; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2019/10/19 */ public interface OrderDemoMapper { /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ int deleteByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ int insert(OrderDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ int insertSelective(OrderDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ OrderDemo selectByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ int updateByPrimaryKeySelective(OrderDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_order_demo */ int updateByPrimaryKey(OrderDemo record); // 获取订单信息 OrderDemo getOrderByNo(String orderNo); // 取消订单 int updateByOrderNo(@Param("orderNo") String orderNo, @Param("status") int status); } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/mapper/StockChangeLodDemoMapper.java ================================================ package com.xiao.stock.demo.mapper; import com.xiao.stock.demo.entity.StockChangeLodDemo; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2019/10/17 */ public interface StockChangeLodDemoMapper { /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ int deleteByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ int insert(StockChangeLodDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ int insertSelective(StockChangeLodDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ StockChangeLodDemo selectByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ int updateByPrimaryKeySelective(StockChangeLodDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_change_log_demo */ int updateByPrimaryKey(StockChangeLodDemo record); /** * 订单、商品、操作类型查询库存记录 * * @param orderNo * @param productNo * @param optType * @return */ StockChangeLodDemo selectByOrderNo(@Param("orderNo") String orderNo, @Param("productNo") String productNo, @Param("optType") String optType); } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/mapper/StockDemoMapper.java ================================================ package com.xiao.stock.demo.mapper; import com.xiao.stock.demo.entity.StockDemo; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2019/10/17 */ public interface StockDemoMapper { /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ int deleteByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ int insert(StockDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ int insertSelective(StockDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ StockDemo selectByPrimaryKey(Long id); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ int updateByPrimaryKeySelective(StockDemo record); /** * This method was generated by MyBatis Generator. * This method corresponds to the database table t_stock_demo */ int updateByPrimaryKey(StockDemo record); /** * [简要描述]:商品店铺查询库存数据
* [详细描述]:
* @param productNo : * @param shopCode : * @param random : * @return * llxiao 2019/10/18 - 14:39 **/ StockDemo queryByProduct(@Param("productNo") String productNo, @Param("shopCode") String shopCode, @Param("random") int random); /** * 预占库存
* * @param tempStock * @return */ int preStock(StockDemo tempStock); } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/rest/StockRestService.java ================================================ package com.xiao.stock.demo.rest; import com.xiao.stock.demo.common.StockOptTypeEnum; import com.xiao.stock.demo.entity.OrderDemo; import com.xiao.stock.demo.entity.StockDemo; import com.xiao.stock.demo.service.StockDemoService; import com.xiao.stock.demo.util.OrderNoUtil; import com.xiao.stock.demo.util.StockUtil; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: 库存接口服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 17:28 * @since JDK 1.8 */ @RestController @RequestMapping("/stock") public class StockRestService { @Autowired private StockDemoService stockDemoService; @Autowired private OrderNoUtil orderNoUtil; @Autowired private RedissonClient redissonClient; /** * 预占库存 * * @param productNo * @param shopCode * @param num 下单数量 * @return */ @RequestMapping("/preStock") public int preStock(@RequestParam("productNo") String productNo, @RequestParam("shopCode") String shopCode, @RequestParam(value = "num", required = false) int num) { return optStock(productNo, shopCode, num, StockOptTypeEnum.LOCK_STOCK, orderNoUtil.buildOrderNo()); } /** * 取消释放库存,实际释放的应该是订单明细商品的数量 * * @param orderNo: 订单编号 * @return */ @RequestMapping("/releaseStock") public int releaseStock(@RequestParam("orderNo") String orderNo) { RLock fairLock = redissonClient.getFairLock(StockUtil.getOrderLockPath(orderNo)); try { // 并发防止重复处理订单 if (fairLock.tryLock()) { OrderDemo orderDemo = stockDemoService.getOrderByNo(orderNo); return optStock(orderDemo.getProductNo(), orderDemo.getShopCode(), orderDemo .getProductNum(), StockOptTypeEnum.RELEASE_STOCK, orderNo); } } finally { fairLock.unlock(); } return 0; } /** * 出库,实际应该是订单明细里面的数据
* 仓库发货回写应该是具体的订单明细的商品数据和数量
* * @param orderNo: 订单编号 * @return */ @RequestMapping("/warehouse") public int warehouse(@RequestParam("orderNo") String orderNo) { RLock fairLock = redissonClient.getFairLock(StockUtil.getOrderLockPath(orderNo)); try { if (fairLock.tryLock()) { OrderDemo orderDemo = stockDemoService.getOrderByNo(orderNo); return optStock(orderDemo.getProductNo(), orderDemo.getShopCode(), orderDemo .getProductNum(), StockOptTypeEnum.OUT_WAREHOUSE_STOCK, orderNo); } } finally { fairLock.unlock(); } return 0; } @RequestMapping("/addStock") public int addStock(@RequestParam("productNo") String productNo, @RequestParam("shopCode") String shopCode, @RequestParam(value = "num", required = false) int num) { return optStock(productNo, shopCode, num, StockOptTypeEnum.ADD_STOCK, orderNoUtil.buildOrderNo()); } /** * 库存操作 * * @param productNo * @param shopCode * @param num 下单数量 * @return */ public int optStock(String productNo, String shopCode, int num, StockOptTypeEnum stockOptTypeEnum, String orderNo) { StockDemo stockDemo = new StockDemo(); stockDemo.setShopCode(shopCode); stockDemo.setProductNo(productNo); stockDemo.setProductNum(num); stockDemo.setOrderNo(orderNo); Long result = 0L; switch (stockOptTypeEnum) { case LOCK_STOCK: result = stockDemoService.preStock(stockDemo); break; case RELEASE_STOCK: result = stockDemoService.releaseStock(stockDemo); break; case OUT_WAREHOUSE_STOCK: result = stockDemoService.outHourse(stockDemo); break; case ADD_STOCK: result = stockDemoService.addStock(stockDemo); break; default: break; } return result.intValue(); } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/service/StockDemoService.java ================================================ package com.xiao.stock.demo.service; import com.xiao.stock.demo.entity.OrderDemo; import com.xiao.stock.demo.entity.StockDemo; /** * [简要描述]: 库存操作服务 * [详细描述]: * 1.使用redis 分布式锁 * 2. * 3.使用分布式锁,必须要先提交事务在释放锁,否则下一个锁拿到的数据就是上一次未提交的数据 * * @author llxiao * @version 1.0, 2019/10/17 09:33 * @since JDK 1.8 */ public interface StockDemoService { /** * 预占库存 * * @param stockDemo * @return */ long preStock(StockDemo stockDemo); /** * 释放库存 * * @param stockDemo * @return */ long releaseStock(StockDemo stockDemo); /** * 出库 * * @param stockDemo * @return */ long outHourse(StockDemo stockDemo); /** * [简要描述]:添加总库存
* [详细描述]:
* * @param stockDemo : * @return long * llxiao 2019/10/19 - 8:41 **/ long addStock(StockDemo stockDemo); /** * 获取订单信息 * @param orderNo * @return */ OrderDemo getOrderByNo(String orderNo); } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/service/impl/StockDemoServiceImpl.java ================================================ package com.xiao.stock.demo.service.impl;/** * [简要描述]: * [详细描述]: * * @since JDK 1.8 */ import com.xiao.stock.demo.common.StockOptTypeEnum; import com.xiao.stock.demo.entity.OrderDemo; import com.xiao.stock.demo.entity.StockChangeLodDemo; import com.xiao.stock.demo.entity.StockDemo; import com.xiao.stock.demo.mapper.OrderDemoMapper; import com.xiao.stock.demo.mapper.StockChangeLodDemoMapper; import com.xiao.stock.demo.mapper.StockDemoMapper; import com.xiao.stock.demo.service.StockDemoService; import com.xiao.stock.demo.util.StockUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 10:45 * @since JDK 1.8 */ @Service @Slf4j public class StockDemoServiceImpl implements StockDemoService { @Autowired private RedissonClient redissonClient; @Autowired private StockDemoMapper stockDemoMapper; @Autowired private StockChangeLodDemoMapper stockChangeLodDemoMapper; @Autowired private OrderDemoMapper orderDemoMapper; /** * 下单预占库存 * * @param stockDemo * @return */ @Override public long preStock(StockDemo stockDemo) { if (checkStock(stockDemo, StockOptTypeEnum.LOCK_STOCK.getOptType())) { return 0; } String productNo = stockDemo.getProductNo(); String shopCode = stockDemo.getShopCode(); SecureRandom secureRandom = null; try { secureRandom = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } // 公平锁 final RLock fairLock = redissonClient.getFairLock(StockUtil.getLockPath(productNo, shopCode)); // 异步 尝试加锁,最多等待50秒,上锁以后10秒自动解锁 Future res = fairLock.tryLockAsync(50, 10, TimeUnit.SECONDS); try { //final boolean lock = fairLock.tryLock(50, 10, TimeUnit.SECONDS); if (res.get()) { //System.out.println(Thread.currentThread().getName() + "Redis获得锁。。。" + System.currentTimeMillis()); // 校验 StockDemo stock = stockDemoMapper .queryByProduct(stockDemo.getProductNo(), stockDemo.getShopCode(), secureRandom.nextInt(9999)); if (null == stock) { // 如果不存在直接抛异常扣库存失败 log.error("商品不存在库存信息:{}", stockDemo); return 0; } // 需要再次从新获取,库存数据 StockDemo tempStock = null; boolean preStatus = false; // 重试次数,5次更新机会 int tryPreTimes = 0; while (null != stock) { //System.out.println(Thread.currentThread().getName() + "查询的===========:" + stock); int avaStock = stock.getAvaStock(); int productNum = stockDemo.getProductNum(); if (productNum > avaStock) { // 预占库存不足,抛异常,下单失败 log.error("下单失败,可用库存不足,单号:{},店铺:{}", stockDemo.getOrderNo(), shopCode); log.error("商品:{},预占库存:{},实际可用库存:{}", productNo, productNum, avaStock); throw new Exception("下单失败,库存不足!"); } int preStock = stock.getPreStock(); // 预占+ 可用- int newAvaStock = avaStock - productNum; int newPreStock = preStock + productNum; tempStock = new StockDemo(); tempStock.setId(stock.getId()); // 旧库存 预占和可用库存 tempStock.setAvaStock(stock.getAvaStock()); tempStock.setPreStock(stock.getPreStock()); // 新库存 预占和可用库存 tempStock.setNewAvaStock(newAvaStock); tempStock.setNewPreStock(newPreStock); tempStock.setTotalStock(stock.getTotalStock()); //System.out.println(Thread.currentThread().getName() + "更新的===========:" + tempStock); //尝试CAS,先比较旧值,再更新 if (optStock(tempStock) > 0) { // 库存扣减成功 log.info("下单成功,订单号:{},预占库存数量:{}", stockDemo.getOrderNo(), productNum); addOrder(stockDemo); preStatus = true; break; } else { // 库存扣减与预期的值不对,更新失败,尝试重新更新 log.info("库存扣减与预期的值不对,更新失败,尝试重新更新!"); // tryPreTimes++; stock = stockDemoMapper.queryByProduct(productNo, shopCode, secureRandom.nextInt(9999)); } } // 总库存 tempStock.setTotalStock(stock.getTotalStock()); tempStock.setNewTotalStock(stock.getTotalStock()); // 预占成功处理日志 return saveChangeLog(stockDemo, productNo, shopCode, stock, tempStock, preStatus, StockOptTypeEnum.LOCK_STOCK); } else { // 此处需要考虑下单失败后,补偿下单扣库存的 log.error("下单失败,获取不到锁!"); throw new Exception("下单失败,获取不到锁"); } } catch (Exception e) { log.error("库存占用失败!"); } finally { //System.out.println(Thread.currentThread().getName() + "Redis释放锁。。。" + System.currentTimeMillis()); fairLock.unlock(); } return 0; } // 下单 @Transactional(rollbackFor = Exception.class) public void addOrder(StockDemo stockDemo) { OrderDemo orderDemo = new OrderDemo(); orderDemo.setOrderNo(stockDemo.getOrderNo()); orderDemo.setProductNo(stockDemo.getProductNo()); orderDemo.setStatus(1); orderDemo.setProductNum(stockDemo.getProductNum()); orderDemo.setShopCode(stockDemo.getShopCode()); orderDemoMapper.insert(orderDemo); } // 更新订单状态 @Transactional(rollbackFor = Exception.class) public int updateOrderStatus(String orderNo, int status) { return orderDemoMapper.updateByOrderNo(orderNo, status); } /** * 释放库存 * * @param stockDemo * @return */ @Override public long releaseStock(StockDemo stockDemo) { if (checkStock(stockDemo, StockOptTypeEnum.RELEASE_STOCK.getOptType())) { return 0; } String productNo = stockDemo.getProductNo(); String shopCode = stockDemo.getShopCode(); SecureRandom secureRandom = null; try { secureRandom = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } // 公平锁 final RLock fairLock = redissonClient.getFairLock(StockUtil.getLockPath(productNo, shopCode)); // 异步 尝试加锁,最多等待50秒,上锁以后10秒自动解锁 Future res = fairLock.tryLockAsync(50, 10, TimeUnit.SECONDS); try { //final boolean lock = fairLock.tryLock(50, 10, TimeUnit.SECONDS); if (res.get()) { //System.out.println(Thread.currentThread().getName() + "Redis获得锁。。。" + System.currentTimeMillis()); // 校验 StockDemo stock = stockDemoMapper .queryByProduct(stockDemo.getProductNo(), stockDemo.getShopCode(), secureRandom.nextInt(9999)); if (null == stock) { // 如果不存在直接抛异常扣库存失败 log.error("商品不存在库存信息:{}", stockDemo); return 0; } StockDemo tempStock = null; boolean release = false; // 重试次数,5次更新机会 while (null != stock) { //System.out.println(Thread.currentThread().getName() + "查询的===========:" + stock); int avaStock = stock.getAvaStock(); int preStock = stock.getPreStock(); int productNum = stockDemo.getProductNum(); // 可用+ 预占- int newAvaStock = avaStock + productNum; int newPreStock = preStock - productNum; // 释放的库存必须是原有订单的库存数,也就是释放的库存不能为负数,否则就会出现数据异常 if (productNum > preStock) { log.error("释放库存异常,释放库存已经大与现有的预占库存,释放库存数据本身有问题"); throw new Exception("释放库存已经大与现有的预占库存!"); } int totalStock = stock.getTotalStock(); // 预占+可用应该永远是=总库存的。即使总库存真实的减了,预占库存应该会对应的减 if (newAvaStock + newPreStock != totalStock) { // log.error("库存释放异常,可用库存+预占库存与总库存数量不符合!总库存:{},预占库存:{},可用库存:{}", totalStock, newPreStock, newAvaStock); throw new Exception("库存扣释放异常,可用库存+预占库存与总库存数量不符合"); } tempStock = new StockDemo(); tempStock.setId(stock.getId()); // 旧库存 可用库存和预占库存 tempStock.setAvaStock(stock.getAvaStock()); tempStock.setPreStock(stock.getPreStock()); // 新库存 可用库存和预占库存 tempStock.setNewAvaStock(newAvaStock); tempStock.setNewPreStock(newPreStock); //System.out.println(Thread.currentThread().getName() + "更新的===========:" + tempStock); //尝试CAS,先比较旧值,再更新 if (optStock(tempStock) > 0) { // 库存释放成功 log.info("库存释放成功,订单号:{},释放数量:{}", stockDemo.getOrderNo(), productNum); release = true; // 订单已完成 this.updateOrderStatus(stockDemo.getOrderNo(), 0); break; } else { // 库存释放与预期的值不对,更新失败,尝试重新更新 log.info("库存释放更新时与预期的值不对,更新失败,尝试重新更新!"); // tryPreTimes++; stock = stockDemoMapper.queryByProduct(productNo, shopCode, secureRandom.nextInt(9999)); } } // 总库存 tempStock.setTotalStock(stock.getTotalStock()); tempStock.setNewTotalStock(stock.getTotalStock()); // 预占成功处理日志 return saveChangeLog(stockDemo, productNo, shopCode, stock, tempStock, release, StockOptTypeEnum.RELEASE_STOCK); } else { // 此处需要考虑下单失败后,补偿下单扣库存的 log.error("库存释放失败,获取不到锁!"); throw new Exception("库存释放失败,获取不到锁"); } } catch (Exception e) { log.error("库存释放失败!"); } finally { //System.out.println(Thread.currentThread().getName() + "释放锁。。。" + System.currentTimeMillis()); fairLock.unlock(); } return 0; } /** * 出库 * * @param stockDemo * @return */ @Override public long outHourse(StockDemo stockDemo) { if (checkStock(stockDemo, StockOptTypeEnum.OUT_WAREHOUSE_STOCK.getOptType())) { return 0; } String productNo = stockDemo.getProductNo(); String shopCode = stockDemo.getShopCode(); SecureRandom secureRandom = null; try { secureRandom = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } // 公平锁 final RLock fairLock = redissonClient.getFairLock(StockUtil.getLockPath(productNo, shopCode)); // 异步 尝试加锁,最多等待50秒,上锁以后10秒自动解锁 Future res = fairLock.tryLockAsync(50, 10, TimeUnit.SECONDS); try { // final boolean lock = fairLock.tryLock(50, 10, TimeUnit.SECONDS); if (res.get()) { // 校验 StockDemo stock = stockDemoMapper .queryByProduct(stockDemo.getProductNo(), stockDemo.getShopCode(), secureRandom.nextInt(999)); if (null == stock) { // 如果不存在直接抛异常扣库存失败 log.error("商品不存在库存信息:{}", stockDemo); return 0; } StockDemo tempStock = null; boolean outHourse = false; // 重试次数,5次更新机会 int tryPreTimes = 0; //&& tryPreTimes < 5 while (null != stock) { // System.out.println("查询的===========:" + stock); int preStock = stock.getPreStock(); int totalStock = stock.getTotalStock(); int productNum = stockDemo.getProductNum(); // 出库查库存应该是原来总库存中的数据,不能出现扣库存超数量情况 if (productNum > preStock || productNum > totalStock) { log.error("扣减实际库存异常,释放库存已经大与现有的预占库存或大与现有的总库存,扣减库存数据本身有问题"); throw new Exception("扣减实际库存大与现有的预占和总库存!"); } //预占-,总库存- int newPreStock = preStock - productNum; int newTotalStock = totalStock - productNum; int avaStock = stock.getAvaStock(); // 预占+可用应该永远是=总库存的。即使总库存真实的减了,预占库存应该会对应的减 if (avaStock + newPreStock != newTotalStock) { // log.error("库存扣减异常,可用库存+预占库存与总库存数量不符合!总库存:{},预占库存:{},可用库存:{}", totalStock, newPreStock, avaStock); throw new Exception("库存扣减放异常,可用库存+预占库存与总库存数量不符合"); } tempStock = new StockDemo(); tempStock.setId(stock.getId()); // 旧库 总库存和预占库存 tempStock.setTotalStock(totalStock); tempStock.setPreStock(stock.getPreStock()); // 新库存 总库存和预占库存 tempStock.setNewPreStock(newPreStock); tempStock.setNewTotalStock(newTotalStock); // tempStock.setAvaStock(stock.getAvaStock()); // System.out.println("更新的===========:" + tempStock); //尝试CAS,先比较旧值,再更新 if (optStock(tempStock) > 0) { // 总库存减成功 log.info("总库存减成功,订单号:{},减库存数量:{}", stockDemo.getOrderNo(), productNum); outHourse = true; // 订单已完成 this.updateOrderStatus(stockDemo.getOrderNo(), 2); break; } else { // 库存释放与预期的值不对,更新失败,尝试重新更新 log.info("总库存更新与预期的值不对,库存出库扣减失败,尝试重新更新!"); // tryPreTimes++; stock = stockDemoMapper.queryByProduct(productNo, shopCode, secureRandom.nextInt(9999)); } } // 可用库存 tempStock.setAvaStock(stock.getAvaStock()); tempStock.setNewAvaStock(stock.getAvaStock()); // 预占成功处理日志 return saveChangeLog(stockDemo, productNo, shopCode, stock, tempStock, outHourse, StockOptTypeEnum.OUT_WAREHOUSE_STOCK); } else { // 此处需要考虑下单失败后,补偿下单扣库存的 log.error("库存出库扣减失败,获取不到锁!"); throw new Exception("库存出库扣减失败,获取不到锁"); } } catch (Exception e) { log.error("库存出库扣减失败!"); } finally { fairLock.unlock(); } return 0; } /** * [简要描述]:添加总库存
* [详细描述]:
* // 总库存+、可用库存+ 总之要保证:可用库存+预占库存=总库存 * * @param stockDemo : * @return long * llxiao 2019/10/19 - 8:41 **/ @Override public long addStock(StockDemo stockDemo) { if (checkStock(stockDemo, StockOptTypeEnum.ADD_STOCK.getOptType())) { log.error("库存添加校验不通过!"); return 0; } String productNo = stockDemo.getProductNo(); String shopCode = stockDemo.getShopCode(); SecureRandom secureRandom = null; try { secureRandom = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } // 公平锁 final RLock fairLock = redissonClient.getFairLock(StockUtil.getLockPath(productNo, shopCode)); // 异步 尝试加锁,最多等待50秒,上锁以后10秒自动解锁 Future res = fairLock.tryLockAsync(50, 10, TimeUnit.SECONDS); try { //final boolean lock = fairLock.tryLock(50, 10, TimeUnit.SECONDS); if (res.get()) { // 校验 StockDemo stock = stockDemoMapper .queryByProduct(stockDemo.getProductNo(), stockDemo.getShopCode(), secureRandom.nextInt(999)); if (null == stock) { // 如果不存在直接抛异常扣库存失败 log.error("商品不存在库存信息:{}", stockDemo); return 0; } StockDemo tempStock = null; boolean addStatus = false; // 重试次数,5次更新机会 while (null != stock) { int avaStock = stock.getAvaStock(); int totalStock = stock.getTotalStock(); int productNum = stockDemo.getProductNum(); //可用+,总库存+ int newAvaStock = avaStock + productNum; int newTotalStock = totalStock + productNum; int preStock = stock.getPreStock(); // 预占+可用应该永远是=总库存的。即使总库存真实的减了,预占库存应该会对应的减 if (newAvaStock + preStock != newTotalStock) { log.error("库存扣减异常,可用库存+预占库存与总库存数量不符合!总库存:{},预占库存:{},可用库存:{}", totalStock, preStock, newAvaStock); throw new Exception("库存扣减放异常,可用库存+预占库存与总库存数量不符合"); } tempStock = new StockDemo(); tempStock.setId(stock.getId()); // 旧库 总库存和预占库存 tempStock.setTotalStock(totalStock); tempStock.setPreStock(stock.getPreStock()); tempStock.setAvaStock(avaStock); // 新库存 总库存和预占库存 tempStock.setNewPreStock(stock.getPreStock()); tempStock.setNewTotalStock(newTotalStock); tempStock.setNewAvaStock(newAvaStock); //System.out.println("更新的===========:" + tempStock); //尝试CAS,先比较旧值,再更新 if (optStock(tempStock) > 0) { // 总库存减成功 log.info("总库存添加成功功,添加数量:{},总库存存数量:{}", productNum, newTotalStock); addStatus = true; break; } else { // 库存释放与预期的值不对,更新失败,尝试重新更新 log.info("总库存更新与预期的值不对,更新失败,尝试重新更新!"); // tryPreTimes++; stock = stockDemoMapper.queryByProduct(productNo, shopCode, secureRandom.nextInt(9999)); } } // 预占成功处理日志 return saveChangeLog(stockDemo, productNo, shopCode, stock, tempStock, addStatus, StockOptTypeEnum.OUT_WAREHOUSE_STOCK); } else { // 此处需要考虑下单失败后,补偿下单扣库存的 log.error("库存释放失败,获取不到锁!"); throw new Exception("库存释放失败,获取不到锁"); } } catch (Exception e) { log.error("库存释放失败!错误消息:", e.getMessage()); } finally { fairLock.unlock(); } return 0; } /** * 获取订单信息 * * @param orderNo * @return */ @Override public OrderDemo getOrderByNo(String orderNo) { return orderDemoMapper.getOrderByNo(orderNo); } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public int optStock(StockDemo updateStock) { return stockDemoMapper.preStock(updateStock); } private boolean checkStock(StockDemo stockDemo, String optType) { if (null == stockDemo || StringUtils.isBlank(stockDemo.getOrderNo()) || StringUtils .isBlank(stockDemo.getShopCode()) || StringUtils.isBlank(stockDemo.getProductNo())) { // 参数为直接抛异常扣库存失败 log.error("请求参数为空:{}", stockDemo); return true; } StockChangeLodDemo stockChangeLodDemo = stockChangeLodDemoMapper .selectByOrderNo(stockDemo.getOrderNo(), stockDemo.getProductNo(), optType); if (null != stockChangeLodDemo) { log.error("预占库存失败,库存日志记录已经存在,订单号:{},商品SKU:{}", stockDemo.getOrderNo(), stockDemo.getProductNo()); return true; } return false; } @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) public long saveChangeLog(StockDemo stockDemo, String productNo, String shopCode, StockDemo stock, StockDemo tempStock, boolean release, StockOptTypeEnum optTypeEnum) throws Exception { if (release) { tempStock.setProductNo(stockDemo.getProductNo()); tempStock.setShopCode(stockDemo.getShopCode()); tempStock.setOrderNo(stockDemo.getOrderNo()); return saveStockChangeLog(tempStock, optTypeEnum); } else { log.error("下单失败,可用库存不足,单号:{},店铺:{}", stockDemo.getOrderNo(), shopCode); log.error("商品:{},预占库存:{}", productNo, stockDemo.getProductNum()); throw new Exception("下单失败,库存不足!"); } } private Long saveStockChangeLog(StockDemo stockDemo, StockOptTypeEnum stockOptTypeEnum) { StockChangeLodDemo stockChangeLodDemo = new StockChangeLodDemo(); stockChangeLodDemo.setOrderNo(stockDemo.getOrderNo()); stockChangeLodDemo.setProductNo(stockDemo.getProductNo()); stockChangeLodDemo.setShopCode(stockDemo.getShopCode()); stockChangeLodDemo.setAvaStockBefore(stockDemo.getAvaStock()); stockChangeLodDemo.setPreStockBefore(stockDemo.getPreStock()); stockChangeLodDemo.setTotalStockBefore(stockDemo.getTotalStock()); stockChangeLodDemo.setAvaStockAfter(stockDemo.getNewAvaStock()); stockChangeLodDemo.setPreStockAfter(stockDemo.getNewPreStock()); stockChangeLodDemo.setTotalStockAfter(stockDemo.getNewTotalStock()); stockChangeLodDemo.setOptType(stockOptTypeEnum.getOptType()); // System.out.println("保存日志:" + stockChangeLodDemo); stockChangeLodDemoMapper.insert(stockChangeLodDemo); return stockChangeLodDemo.getId(); } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/util/OrderNoUtil.java ================================================ package com.xiao.stock.demo.util; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.net.NetUtil; import cn.hutool.core.util.IdUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * 订单枷锁工具 */ @Component @Slf4j public class OrderNoUtil { private static long workerId = 0; private static Snowflake snowflake; static { try { workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr()); } catch (Exception e) { log.warn("获取机器 ID 失败", e); workerId = NetUtil.getLocalhost().hashCode(); } workerId = workerId % 32; log.info("当前机器 workerId: {}", workerId); // workerId 0~31 snowflake = IdUtil.createSnowflake(workerId, workerId + 1); } public synchronized long snowflakeId() { return snowflake.nextId(); } public synchronized long snowflakeId(long workerId, long dataCenterId) { Snowflake snowflake = IdUtil.createSnowflake(workerId, dataCenterId); return snowflake.nextId(); } /** * 订单编号规则:14位毫秒时间戳+3位顺序编号 * * @return */ public String buildOrderNo() { return "O" + snowflakeId(); } public String buildOrderItemNo() { return "OI" + snowflakeId(); } /** * 快递单号 * * @return */ public String buildDeliveryNo() { return "D" + snowflakeId(); } /** * 预售单号 * * @return */ public String buildAdvanceSaleOrderNo() { return "Y" + snowflakeId(); } /** * 跨境购生成单号 * * @return */ public String buildHaitaoOrderNo() { return "H" + snowflakeId(); } /** * [简要描述]:内购会生成订单,由I变更为O,便于识别
* [详细描述]:
* * @return java.lang.String * llxiao 2019/9/18 - 14:19 **/ public String buildInOrderNo() { return "N" + snowflakeId(); } /** * 拼团单号 * * @return */ public String buildPintuanOrderNo() { return "P" + snowflakeId(); } /** * 拼明细单号 * * @return */ public String buildPintuanOrderItemNo() { return "PI" + snowflakeId(); } /** * 团号 * * @return */ public String buildTuanNo() { return "T" + snowflakeId(); } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/java/com/xiao/stock/demo/util/StockUtil.java ================================================ package com.xiao.stock.demo.util;/** * [简要描述]: * [详细描述]: * * @since JDK 1.8 */ /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/10/17 11:38 * @since JDK 1.8 */ public class StockUtil { private static final String STOCK_LOCK_PATH = "/stock/"; private static final String ORDER_LOCK_PATH = "/order/"; /** * 店铺+产品编号 获取redisson 锁 * * @return */ public static String getLockPath(String productNo, String shopCode) { return STOCK_LOCK_PATH + shopCode + '/' + productNo; } /** * 订单编号 redisson 锁 * * @param orderNo * @return */ public static String getOrderLockPath(String orderNo) { return ORDER_LOCK_PATH + orderNo; } } ================================================ FILE: SpringBoot-Stock-Demo/src/main/resources/application.yml ================================================ spring: application: name: stock-demo server: port: 7878 ## tomcat 最大线程池配置 tomcat: # 最大连接数 max-connections: 10000 # 最大线程数 max-threads: 500 # 最小线程数 min-spare-threads: 20 #最大队列长度 accept-count: 1000 redisson: host: redis://192.168.206.204:6789 password: jl!@12 thread: pool: core: 50 max: 100 queue: capacity: 10000 hikari-jdbc-url: jdbc:mysql://192.168.206.201:3306/basisdb?useSSL=false&allowMultiQueries=true hikari-jdbc-username: basisuser hikari-jdbc-password: Basisuser123 hikari-jdbc-driver-class-name: com.mysql.jdbc.Driver hikari-jdbc-pool-size: 10 ================================================ FILE: SpringBoot-Stock-Demo/src/main/resources/com/xiao/stock/demo/mapper/OrderDemoMapper.xml ================================================ id, order_no, shop_code, product_no, product_num,status, update_time delete from t_order_demo where id = #{id,jdbcType=BIGINT} insert into t_order_demo (id, order_no, shop_code, product_no, product_num,status, update_time ) values (#{id,jdbcType=BIGINT}, #{orderNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{productNo,jdbcType=VARCHAR}, #{productNum,jdbcType=INTEGER},#{status,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP} ) insert into t_order_demo id, order_no, shop_code, product_no, product_num, status, update_time, #{id,jdbcType=BIGINT}, #{orderNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{productNo,jdbcType=VARCHAR}, #{productNum,jdbcType=INTEGER}, #{status,jdbcType=INTEGER}, #{updateTime,jdbcType=TIMESTAMP}, update t_order_demo order_no = #{orderNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, product_no = #{productNo,jdbcType=VARCHAR}, product_num = #{productNum,jdbcType=INTEGER}, status = #{status,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_order_demo set order_no = #{orderNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, product_no = #{productNo,jdbcType=VARCHAR}, product_num = #{productNum,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP}, status = #{status,jdbcType=INTEGER} where id = #{id,jdbcType=BIGINT} update t_order_demo set status = #{status} where order_no = #{orderNo} ================================================ FILE: SpringBoot-Stock-Demo/src/main/resources/com/xiao/stock/demo/mapper/StockChangeLodDemoMapper.xml ================================================ id, product_no, shop_code, order_no, opt_type, pre_stock_after, ava_stock_after, total_stock_after, pre_stock_before, ava_stock_before, total_stock_before, update_time delete from t_stock_change_log_demo where id = #{id,jdbcType=BIGINT} insert into t_stock_change_log_demo (id, product_no, shop_code, order_no,opt_type, pre_stock_after, ava_stock_after, total_stock_after, pre_stock_before, ava_stock_before, total_stock_before, update_time) values (#{id,jdbcType=BIGINT}, #{productNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{orderNo,jdbcType=VARCHAR},#{optType,jdbcType=VARCHAR}, #{preStockAfter,jdbcType=INTEGER}, #{avaStockAfter,jdbcType=INTEGER}, #{totalStockAfter,jdbcType=INTEGER}, #{preStockBefore,jdbcType=INTEGER}, #{avaStockBefore,jdbcType=INTEGER}, #{totalStockBefore,jdbcType=INTEGER}, #{updateTime,jdbcType=TIMESTAMP}) insert into t_stock_change_log_demo id, product_no, shop_code, order_no, opt_type, pre_stock_after, ava_stock_after, total_stock_after, pre_stock_before, ava_stock_before, total_stock_before, update_time, #{id,jdbcType=BIGINT}, #{productNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{orderNo,jdbcType=VARCHAR}, #{optType,jdbcType=VARCHAR}, #{preStockAfter,jdbcType=INTEGER}, #{avaStockAfter,jdbcType=INTEGER}, #{totalStockAfter,jdbcType=INTEGER}, #{preStockBefore,jdbcType=INTEGER}, #{avaStockBefore,jdbcType=INTEGER}, #{totalStockBefore,jdbcType=INTEGER}, #{updateTime,jdbcType=TIMESTAMP}, update t_stock_change_log_demo product_no = #{productNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, order_no = #{orderNo,jdbcType=VARCHAR}, opt_type = #{optType,jdbcType=VARCHAR}, pre_stock_after = #{preStockAfter,jdbcType=INTEGER}, ava_stock_after = #{avaStockAfter,jdbcType=INTEGER}, total_stock_after = #{totalStockAfter,jdbcType=INTEGER}, pre_stock_before = #{preStockBefore,jdbcType=INTEGER}, ava_stock_before = #{avaStockBefore,jdbcType=INTEGER}, total_stock_before = #{totalStockBefore,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_stock_change_log_demo set product_no = #{productNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, order_no = #{orderNo,jdbcType=VARCHAR}, opt_type = #{optType,jdbcType=VARCHAR}, pre_stock_after = #{preStockAfter,jdbcType=INTEGER}, ava_stock_after = #{avaStockAfter,jdbcType=INTEGER}, total_stock_after = #{totalStockAfter,jdbcType=INTEGER}, pre_stock_before = #{preStockBefore,jdbcType=INTEGER}, ava_stock_before = #{avaStockBefore,jdbcType=INTEGER}, total_stock_before = #{totalStockBefore,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=BIGINT} ================================================ FILE: SpringBoot-Stock-Demo/src/main/resources/com/xiao/stock/demo/mapper/StockDemoMapper.xml ================================================ id, product_no, shop_code, pre_stock, ava_stock, total_stock, update_time delete from t_stock_demo where id = #{id,jdbcType=BIGINT} insert into t_stock_demo (id, product_no, shop_code, pre_stock, ava_stock, total_stock, update_time) values (#{id,jdbcType=BIGINT}, #{productNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{preStock,jdbcType=INTEGER}, #{avaStock,jdbcType=INTEGER}, #{totalStock,jdbcType=INTEGER}, #{updateTime,jdbcType=TIMESTAMP}) insert into t_stock_demo id, product_no, shop_code, pre_stock, ava_stock, total_stock, update_time, #{id,jdbcType=BIGINT}, #{productNo,jdbcType=VARCHAR}, #{shopCode,jdbcType=VARCHAR}, #{preStock,jdbcType=INTEGER}, #{avaStock,jdbcType=INTEGER}, #{totalStock,jdbcType=INTEGER}, #{updateTime,jdbcType=TIMESTAMP}, update t_stock_demo product_no = #{productNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, pre_stock = #{preStock,jdbcType=INTEGER}, ava_stock = #{avaStock,jdbcType=INTEGER}, total_stock = #{totalStock,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_stock_demo set product_no = #{productNo,jdbcType=VARCHAR}, shop_code = #{shopCode,jdbcType=VARCHAR}, pre_stock = #{preStock,jdbcType=INTEGER}, ava_stock = #{avaStock,jdbcType=INTEGER}, total_stock = #{totalStock,jdbcType=INTEGER}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=BIGINT} update t_stock_demo ava_stock = #{newAvaStock,jdbcType=INTEGER}, pre_stock = #{newPreStock,jdbcType=INTEGER}, total_stock = #{newTotalStock,jdbcType=INTEGER}, id = #{id,jdbcType=BIGINT} and ava_stock = #{avaStock,jdbcType=INTEGER} and pre_stock = #{preStock,jdbcType=INTEGER} and total_stock = #{totalStock,jdbcType=INTEGER} ================================================ FILE: SpringBoot-Stock-Demo/src/main/resources/logback-spring.xml ================================================ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ================================================ FILE: SpringCloud-Canal/doc/ServerRunningMonitor源码注解.md ================================================ 设计到主要知识点: 1. Zookeeper 临时节点创建 2. CAS 操作 3. MDC 线程安全的存放诊断日志的容器 ```html public ServerRunningMonitor(){ // 创建父节点 IZkDataListener dataListener = new IZkDataListener() { // ZK节点数据出现变更,比如:主动释放、激活、新增等 public void handleDataChange(String dataPath, Object data) throws Exception { // 线程安全的存放诊断日志的容器,其实是放到当前线程的ThreadLocalMap中,不明所以? MDC.put("destination", destination); ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class); // 如果不是本机节点 if (!isMine(runningData.getAddress())) { // 互斥设置false,内部使用CAS操作 mutex.set(false); } // zk上现有节点不活跃,且zk的节点是本机节点,说明是主动释放 if (!runningData.isActive() && isMine(runningData.getAddress())) { // 说明出现了主动释放的操作,并且本机之前是active release = true; // 从zk上删除节点 releaseRunning();// 彻底释放mainstem } // 设置当前活动节点为zk上正在运行的节点 activeData = (ServerRunningData) runningData; } // 节点删除 public void handleDataDeleted(String dataPath) throws Exception { MDC.put("destination", destination); // 互斥设置false,内部使用CAS操作 mutex.set(false); // 不为释放状态、活动节点不会为空且为本机节点,则即时触发一下active抢占 if (!release && activeData != null && isMine(activeData.getAddress())) { initRunning(); } else { // 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作 delayExector.schedule(new Runnable() { public void run() { initRunning(); } }, delayTime, TimeUnit.SECONDS); } } }; } private void initRunning() { // 不是运行中直接返回 if (!isStart()) { return; } // 生产当前运行的实例的节点信息:/otter/canal/destinations/{0} 实例信息如example String path = ZookeeperPathUtils.getDestinationServerRunning(destination); // 序列化 当前运行的节点,此处应该是本机的节点 byte[] bytes = JsonUtils.marshalToByte(serverData); try { // 互斥false CAS操作 阻塞? mutex.set(false); // ZK上创建临时节点(带open ACL)并设置数据 zkClient.create(path, bytes, CreateMode.EPHEMERAL); activeData = serverData; // 触发激活事件 processActiveEnter(); // 设置互斥 CAS操作,释放一下锁对象,唤醒一下阻塞的Thread mutex.set(true); } catch (ZkNodeExistsException e) { // 节点已经存在,或去数据 bytes = zkClient.readData(path, true); if (bytes == null) { // 节点中的数据为空,则立即出发抢占 initRunning(); } else { // 反之设置当前可用节点为ZK上的节点 activeData = JsonUtils.unmarshalFromByte(bytes, ServerRunningData.class); } } catch (ZkNoNodeException e) { // 不存在则创建父节点,尝试抢占动作 zkClient.createPersistent(ZookeeperPathUtils.getDestinationPath(destination), true); initRunning(); } } ``` ================================================ FILE: SpringCloud-Canal/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Canal org.springframework.boot spring-boot-starter-web com.alibaba.otter canal.client 1.1.2 com.lmax disruptor 3.4.2 org.projectlombok lombok ${lombok.version} cn.hutool hutool-core 4.1.12 ================================================ FILE: SpringCloud-Canal/readme.md ================================================ 1. 本DEMO主要实现:Canal+disruptor+springboot,订阅mysql binlog日志,实现数据同步,比如缓存,ES等。
2. 组件简介:
[Canal](https://github.com/alibaba/canal)-阿里巴巴mysql数据库binlog的增量订阅&消费组件
[Disruptor](https://github.com/LMAX-Exchange/disruptor)-开源的并发框架,能够在无锁的情况下实现网络的Queue并发操作
3. 使用方式:
Canal的服务端搭建[参考](https://www.jianshu.com/p/6299048fad66)
Disruptor+Canal异步操作[参考](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Canal/src/main/java/com/xiao/springcloud)
业务集成时在[DisruptorServiceImpl](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/service/impl/DisruptorServiceImpl.java)
服务中实现自己的业务逻辑即可,代码片段:
```$xslt public void execute(TableData tableData) { if (log.isDebugEnabled()) { log.debug("接受数据更新请求,更新表名:{},更新的主键为:{}", tableData.getTableName(), tableData.getId()); } if (null != tableData) { //业务处理 TODO } } ``` 在[CanalClientService](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Canal/src/main/java/com/xiao/springcloud/canal/CanalClientService.java)中定制自己需要的binlog事件处理
代码片段: ```$xslt private void processData(List entrys) { if (log.isDebugEnabled()) { log.debug("接收到需要处理数据,总数量:{}", entrys.size()); } List tableDataList = new ArrayList<>(entrys.size()); // 表名 String tableName; TableData tableData; CanalEntry.RowChange rowChange; for (CanalEntry.Entry entry : entrys) { // 事物数据不处理 if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { continue; } if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) { continue; } //获取表名 tableName = entry.getHeader().getTableName(); //仅处理部分表接口数据 if (tableNames.contains(tableName)) { try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); // RowData --具体insert/update/delete的变更数据,可为多条,1个binlog event事件可对应多条变更,比如批处理 for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { switch (rowChange.getEventType()) { //当前仅处理Insert和更新数据 case INSERT: case UPDATE: tableData = new TableData(); tableData.setTableName(tableName); tableData.setId(parseKey(rowData.getAfterColumnsList())); tableDataList.add(tableData); break; default: break; } } } catch (Exception e) { log.error("当前行数据解析错误:", e); } } } if (CollectionUtil.isNotEmpty(tableDataList)) { //发送数据到异步队列框架 disruptorProducer.send(tableDataList); } if (log.isDebugEnabled()) { log.debug("接收到Mysql更新数据,总数量:{}", tableDataList.size()); } } ``` 4. Canal使用——数据库初始化配置(5.* 必须)
_tips: mysql 8.* 默认开启以下配置_
在[mysqld]下配置:
```$xslt log-bin=mysql-bin binlog-format=ROW server_id=1(任意id即可,但不能与canal中指定的id相同) ``` ================================================ FILE: SpringCloud-Canal/src/main/java/com/alibaba/canal/simple/ClientSample.java ================================================ package com.alibaba.canal.simple; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.Message; import java.net.InetSocketAddress; import java.util.List; import java.util.stream.Collectors; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/19 11:50 * @since JDK 1.8 */ public class ClientSample { public static void main(String[] args) { // 创建链接 CanalConnector connector = CanalConnectors .newSingleConnector(new InetSocketAddress("192.168.206.210", 5555), "example", "", ""); int batchSize = 1000; int emptyCount = 0; try { connector.connect(); // .*代表database,..*代表table connector.subscribe("basisdb\\..*"); connector.rollback();// int totalEmptyCount = 120; while (emptyCount < totalEmptyCount) { // 获取指定数量的数据 Message message = connector.getWithoutAck(batchSize); long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { emptyCount++; try { Thread.sleep(1000); } catch (InterruptedException e) { } } else { emptyCount = 0; printEntry(message.getEntries()); } // 提交确认 connector.ack(batchId); // connector.rollback(batchId); // 处理失败, 回滚数据 } System.out.println("empty too many times, exit"); } finally { connector.disconnect(); } } private static void printEntry(List entrys) { for (CanalEntry.Entry entry : entrys) { // 事物数据不处理 if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { continue; } if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) { continue; } System.out.println("Db name:" + entry.getHeader().getSchemaName()); System.out.println("table name: " + entry.getHeader().getTableName()); CanalEntry.RowChange rowChange = null; try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); } catch (Exception e) { throw new RuntimeException( "ERROR ## parser of eromanga-event has an error,data:" + entry.toString(), e); } //是否是ddl变更操作,比如create table/drop table if (rowChange.getIsDdl()) { //具体的ddl sql System.out.println(rowChange.getSql()); } // RowData --具体insert/update/delete的变更数据,可为多条,1个binlog event事件可对应多条变更,比如批处理 for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { //insert只有after columns, delete只有before columns,而update则会有before / after columns数据. //变更前的字段数据 List beforeColumns = rowData.getBeforeColumnsList(); //变更后的字段数据 List afterColumns = rowData.getAfterColumnsList(); switch (rowChange.getEventType()) { case INSERT: case UPDATE: System.out.print("UPSERT "); printColumns(rowData.getAfterColumnsList()); if ("retl_buffer".equals(entry.getHeader().getTableName())) { String tableName = rowData.getAfterColumns(1).getValue(); String pkValue = rowData.getAfterColumns(2).getValue(); System.out.println("SELECT * FROM " + tableName + " WHERE id = " + pkValue); } break; case DELETE: System.out.print("DELETE "); printColumns(rowData.getBeforeColumnsList()); break; case QUERY: System.out.println(rowChange.getSql()); default: break; } } } } /** * index *

* sqlType [jdbc type] *

* name [字段名] *

* isKey [是否为主键] *

* updated [是否发生过变更] *

* isNull [值是否为null] *

* value [具体的内容,注意为string文本] * * @param columns */ private static void printColumns(List columns) { // 获取操作后完整的整条记录 String line = columns.stream().map(column -> column.getName() + "=" + column.getValue()) .collect(Collectors.joining(",")); System.out.println(line); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/CanalSimpleApplication.java ================================================ package com.xiao.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * [简要描述]: Canal+Disruptor 集成到springboot 案列 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 09:00 * @since JDK 1.8 */ @SpringBootApplication public class CanalSimpleApplication { public static void main(String[] args) { SpringApplication.run(CanalSimpleApplication.class, args); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/canal/CanalClientService.java ================================================ package com.xiao.springcloud.canal; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.exception.CanalClientException; import com.xiao.springcloud.disruptor.DisruptorProducer; import com.xiao.springcloud.disruptor.DisruptorThreadFactory; import com.xiao.springcloud.disruptor.TableData; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * [简要描述]: Canal客户端服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 15:27 * @since JDK 1.8 */ @Component @Slf4j public class CanalClientService implements DisposableBean, ApplicationListener { private CanalConfig canalConfig; // disruptor异步队列 private DisruptorProducer disruptorProducer; //定时任务检查 canal连接状态 private ScheduledExecutorService scheduledExecutorService; //canal线程处理,不阻塞springboot主流程 private ExecutorService canalExecutor; private static volatile boolean start; private CanalConnector connector; //需要监听过滤的一些表 private static Set tableNames = new HashSet<>(); static { //初始化一些需要监听的表 tableNames.add("t_table_test"); } @Autowired public CanalClientService(CanalConfig canalConfig, DisruptorProducer disruptorProducer) { this.canalConfig = canalConfig; this.disruptorProducer = disruptorProducer; } @Override public void destroy() throws Exception { log.error(">>> 即将关闭Canal连接,销毁线程池....."); if (start) { try { connector.disconnect(); } catch (CanalClientException e) { log.error(">>> 关闭Canal连接异常:", e); } } canalExecutor.shutdown(); scheduledExecutorService.shutdown(); } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { log.info(">>> Canal启动......"); init(); // 执行启动 canalExecutor.execute(() -> start()); // 启动后5S开始检查connector的状态,每个2两分钟执行一次 scheduledExecutorService.scheduleAtFixedRate(() -> check(), 1, 2, TimeUnit.MINUTES); } private void start() { try { connector.connect(); connector.subscribe(canalConfig.getListenerDb() + "\\..*"); start = true; log.info(">>> Canal连接成功,订阅DB:{}下所有表信息", canalConfig.getListenerDb()); } catch (CanalClientException e) { log.error(">>> Canal服务连接失败:", e); start = false; } if (start) { processBinlog(); } } private void processBinlog() { //每一次拉取100条数据 int batchSize = 100; Message msg; while (true) { try { msg = connector.getWithoutAck(batchSize); long batchId = msg.getId(); int size = msg.getEntries().size(); //没有数据,休眠5秒 if (batchId < 0 || size == 0) { try { Thread.sleep(5000); } catch (InterruptedException e) { log.error(">>> 休眠线程中断......."); } } else { // 数据处理 processData(msg.getEntries()); } connector.ack(batchId); } catch (Exception e) { // 客户端异常,中断当前循环,等待定时任务重连操作 log.error(">>> Canal 客户端异常,中断操作,并断开现有连接,等待定时任务定期重连操作...", e); this.close(); break; } } } private void processData(List entrys) { if (log.isDebugEnabled()) { log.debug("接收到需要处理数据,总数量:{}", entrys.size()); } List tableDataList = new ArrayList<>(entrys.size()); // 表名 String tableName; TableData tableData; CanalEntry.RowChange rowChange; for (CanalEntry.Entry entry : entrys) { // 事物数据不处理 if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { continue; } if (entry.getEntryType() != CanalEntry.EntryType.ROWDATA) { continue; } //获取表名 tableName = entry.getHeader().getTableName(); //仅处理部分表接口数据 if (tableNames.contains(tableName)) { try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); // RowData --具体insert/update/delete的变更数据,可为多条,1个binlog event事件可对应多条变更,比如批处理 // insert 只有after数据,delete只有 before数据,update会有after和before数据 for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { switch (rowChange.getEventType()) { //当前仅处理Insert和更新数据 case INSERT: case UPDATE: tableData = new TableData(); tableData.setTableName(tableName); tableData.setId(parseKey(rowData.getAfterColumnsList())); tableDataList.add(tableData); break; default: break; } } } catch (Exception e) { log.error("当前行数据解析错误:", e); } } } if (CollectionUtil.isNotEmpty(tableDataList)) { //发送数据到异步队列框架 disruptorProducer.send(tableDataList); } if (log.isDebugEnabled()) { log.debug("接收到Mysql更新数据,总数量:{}", tableDataList.size()); } } /** * 解析主键KEY
* CanalEntry.Column字段:
*

* sqlType [jdbc type] *

* name [字段名] *

* isKey [是否为主键] *

* updated [是否发生过变更] *

* isNull [值是否为null] *

* value [具体的内容,注意为string文本] * * @param columns */ private Long parseKey(List columns) { // 获取操作后完整的整条记录 String line = columns.stream().map(column -> column.getName() + "=" + column.getValue()) .collect(Collectors.joining(",")); //只保留主键Key的数据 Stream columnStream = columns.stream().filter(column -> column.getIsKey()); // 返回第一个数据 Optional first = columnStream.findFirst(); return Long.parseLong(first.get().getValue()); } private void check() { if (log.isDebugEnabled()) { log.debug(">>> 开始执行检测Canal连接状态..."); } if (!start) { log.warn(">>> 开始执行重连Canal服务..."); this.start(); } } private void init() { log.info(">>> 初始化Canal连接信息......."); int threads = Runtime.getRuntime().availableProcessors(); scheduledExecutorService = new ScheduledThreadPoolExecutor(threads, DisruptorThreadFactory .create("Scheduled check Canal connector-", true)); canalExecutor = Executors.newSingleThreadExecutor(DisruptorThreadFactory.create("Canal servers-", true)); if (canalConfig.isCluster()) { log.info(">>> 建立ZK集群Canal连接信息:{}", canalConfig.getZkServers()); connector = CanalConnectors .newClusterConnector(canalConfig.getZkServers(), canalConfig.getDestination(), canalConfig .getUserName(), canalConfig.getPassword()); } else { log.info(">>> 建立单Canal连接信息:{}", canalConfig.getHostName()); connector = CanalConnectors.newSingleConnector(new InetSocketAddress(canalConfig.getHostName(), canalConfig .getPort()), canalConfig.getDestination(), canalConfig.getUserName(), canalConfig.getPassword()); } } private void close() { try { if (start) { connector.disconnect(); } } catch (CanalClientException e) { log.error("关闭Canal连接错误:", e); } finally { start = false; } } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/canal/CanalConfig.java ================================================ package com.xiao.springcloud.canal; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * [简要描述]: Canal服务配置 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 15:18 * @since JDK 1.8 */ @Configuration @Data public class CanalConfig { @Value("${spring.cache.server.cluster:false}") private boolean cluster; @Value("${spring.cache.server.hostName:localhost}") private String hostName; @Value("${spring.cache.server.port:11111}") private int port; @Value("${spring.cache.server.destination:example}") private String destination; @Value("${spring.cache.server.userName:}") private String userName; @Value("${spring.cache.server.password:}") private String password; @Value("${spring.cache.server.zkServers:}") private String zkServers; @Value("${spring.cache.server.dbName:}") private String listenerDb; } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DataEvent.java ================================================ package com.xiao.springcloud.disruptor; import lombok.Data; /** * [简要描述]: 定义事件 * [详细描述]:通过 Disruptor 进行交换的数据类型 * * @author llxiao * @version 1.0, 2019/3/21 15:12 * @since JDK 1.8 */ @Data public class DataEvent { private TableData data; } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DataEventFactory.java ================================================ package com.xiao.springcloud.disruptor; import com.lmax.disruptor.EventFactory; /** * [简要描述]: 定义事件工厂 * [详细描述]: 定义了如何实例化定义的事件(Event) * Disruptor 通过 EventFactory 在 RingBuffer 中预创建 Event 的实例

* 一个 Event 实例实际上被用作一个“数据槽”,发布者发布前,先从 RingBuffer 获得一个 Event 的实例,然后往 Event 实例中填充数据,
* 之后再发布到 RingBuffer 中,之后由 Consumer 获得该 Event 实例并从中读取数据
* * @author llxiao * @version 1.0, 2019/3/21 15:17 * @since JDK 1.8 */ public class DataEventFactory implements EventFactory { @Override public DataEvent newInstance() { return new DataEvent(); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DisruptorConsumer.java ================================================ package com.xiao.springcloud.disruptor; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.WorkHandler; import com.xiao.springcloud.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executor; /** * [简要描述]: Disruptor消费者之Handler。 * [详细描述]: *
* 单消费者:实现EventHandler接口(DataEvent dataEvent, long sequence, boolean endOfBatch)方法
* 多消费者:实现WorkHandler接口的onEvent(DataEvent dataEvent)方法
* --广播:对于多个消费者,每条信息会达到所有的消费者,被多次处理,一般每个消费者业务逻辑不通,用于同一个消息的不同业务逻辑处理
* --消费者之间无依赖关系 disruptor.handleEventsWith(handler1,handler2,handler3);
* --假设handler3必须在handler1,handler2处理完成后进行处理:disruptor.handleEventsWith(handler1,handler2).then(handler3);
* --分组:对于同一组内的多个消费者,每条信息只会被组内一个消费者处理,每个消费者业务逻辑一般相同,用于多消费者并发处理一组消息
* --假设handler1,handler2,handler3都实现了WorkHandler,则调用以下代码就可以实现分组:
* --disruptor.handleEventsWithWorkerPool(handler1, handler2, handler3);
* 广播和分组之间也是可以排列组合的
* link:http://www.importnew.com/27652.html * * @author llxiao * @version 1.0, 2019/3/21 15:20 * @since JDK 1.8 */ @Slf4j public class DisruptorConsumer implements EventHandler, WorkHandler { //具体的服务 private DisruptorService disruptorService; /** * */ private final Executor executor; public DisruptorConsumer(DisruptorService disruptorService, Executor executor) { this.disruptorService = disruptorService; this.executor = executor; } @Override public void onEvent(DataEvent dataEvent) throws Exception { if (log.isDebugEnabled()) { log.info("接受到数据更新请求 >>>" + dataEvent); } executor.execute(() -> { disruptorService.execute(dataEvent.getData()); }); } @Override public void onEvent(DataEvent dataEvent, long sequence, boolean endOfBatch) throws Exception { if (log.isDebugEnabled()) { log.info("接受到数据更新请求 >>>{}", dataEvent); log.info("Sequence:{}", sequence); log.info("End Of Batch:{}", endOfBatch); } this.onEvent(dataEvent); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DisruptorExceptionHandler.java ================================================ package com.xiao.springcloud.disruptor; import com.lmax.disruptor.ExceptionHandler; import com.xiao.springcloud.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; /** * [简要描述]: Disruptor自定义异常处理 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/21 15:42 * @since JDK 1.8 */ @Slf4j public class DisruptorExceptionHandler implements ExceptionHandler { private DisruptorService disruptorService; public DisruptorExceptionHandler(DisruptorService disruptorService) { this.disruptorService = disruptorService; } /** * 事件处理异常 */ @Override public void handleEventException(Throwable throwable, long sequence, DataEvent dataEvent) { //事件处理异常里面进行补偿执行 disruptorService.execute(dataEvent.getData()); log.error(">>> Disruptor事件处理异常,进行立即执行补偿操作.........."); log.error(">>> 异常信息如下:", throwable.getMessage()); } /** * 启动异常 * * @param throwable */ @Override public void handleOnStartException(Throwable throwable) { log.error(">>> Disruptor 启动异常:{}", throwable.getMessage()); } /** * 关闭异常 * * @param throwable */ @Override public void handleOnShutdownException(Throwable throwable) { log.error(">>> Disruptro 关闭异常:{}", throwable.getMessage()); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DisruptorProducer.java ================================================ package com.xiao.springcloud.disruptor; import cn.hutool.core.collection.CollectionUtil; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.YieldingWaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.xiao.springcloud.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.*; /** * [简要描述]: Disruptor生产者 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/21 15:37 * @since JDK 1.8 */ @Component @Slf4j public class DisruptorProducer implements DisposableBean, ApplicationListener { /** * RingBuffer 大小,必须是 2 的 N 次方; */ private int ringBufferSize = 1024 * 1024; private Disruptor disruptor; private boolean isMultiProducer; /** * 等待策略 * 例如,BlockingWaitStrategy、SleepingWaitStrategy、YieldingWaitStrategy 等,其中, * BlockingWaitStrategy 是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现; * SleepingWaitStrategy 的性能表现跟 BlockingWaitStrategy 差不多,对 CPU 的消耗也类似,但其对生产者线程的影响最小,适合用于异步日志类似的场景; * YieldingWaitStrategy 的性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于 CPU 逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。 */ private WaitStrategy waitStrategy; private RingBuffer ringBuffer; private volatile boolean isStart; private DisruptorService disruptorService; private ExecutorService executor; private int threads; @Autowired public DisruptorProducer(DisruptorService disruptorService) { threads = Runtime.getRuntime().availableProcessors(); this.disruptorService = disruptorService; //单生产者 isMultiProducer = true; ProducerType producerType = ProducerType.SINGLE; if (isMultiProducer) { //多生产者 producerType = ProducerType.MULTI; } // 等待策略 waitStrategy = new YieldingWaitStrategy(); //初始化disruptor disruptor = new Disruptor<>(new DataEventFactory(), ringBufferSize, DisruptorThreadFactory .create("Disruptor Main-", false), producerType, waitStrategy); executor = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), DisruptorThreadFactory .create("Disruptor Producer-", false), new ThreadPoolExecutor.AbortPolicy()); } /** * [简要描述]:发送事件处理
* [详细描述]:
* * @param dataList : 待处理的数据 * llxiao 2019/3/25 - 15:10 **/ public void send(List dataList) { if (log.isDebugEnabled()) { log.debug("批量发送数据给消费者,总数量:{}", CollectionUtil.isNotEmpty(dataList) ? 0 : dataList.size()); } if (CollectionUtil.isNotEmpty(dataList)) { executor.execute(() -> { for (TableData tableData : dataList) { translator(tableData); } }); } } private void translator(TableData tableData) { EventTranslatorOneArg eventTranslatorOneArg = (event, sequence, message1) -> event .setData(tableData); ringBuffer.publishEvent(eventTranslatorOneArg, tableData); } @Override public void destroy() throws Exception { if (isStart) { //关闭 disruptor,方法会堵塞,直至所有的事件都得到处理; disruptor.shutdown(); } executor.shutdown(); } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { log.info(">>> Disruptor producer 启动......"); doStart(); } private void doStart() { //CPU可使用数量 final Executor executor = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), DisruptorThreadFactory .create("Disruptor consumer-", false), new ThreadPoolExecutor.AbortPolicy()); DisruptorConsumer[] disruptorConsumers = new DisruptorConsumer[threads]; for (int i = 0; i < threads; i++) { disruptorConsumers[i] = new DisruptorConsumer(disruptorService, executor); } //设置消费者Handler // 广播 消费者之间无依赖关系 // disruptor.handleEventsWith(ArrayUtil.toArray(handlers, EventHandler.class)); // 关闭 消费间有依赖关系,假设:2号需要在0号和1号处理完才能进行 // disruptor.handleEventsWith(handlers.get(0),handlers.get(1)).then(handlers.get(2)); // 分组 List workHandlers disruptor.handleEventsWithWorkerPool(disruptorConsumers); //设置异常处理 disruptor.setDefaultExceptionHandler(new DisruptorExceptionHandler(disruptorService)); ringBuffer = disruptor.start(); this.isStart = true; } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/DisruptorThreadFactory.java ================================================ package com.xiao.springcloud.disruptor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 14:49 * @since JDK 1.8 */ public class DisruptorThreadFactory implements ThreadFactory { private static final AtomicLong THREAD_NUMBER = new AtomicLong(1); private static final ThreadGroup THREAD_GROUP = new ThreadGroup("disruptor"); private static volatile boolean daemon; private final String namePrefix; private DisruptorThreadFactory(final String namePrefix, final boolean daemon) { this.namePrefix = namePrefix; DisruptorThreadFactory.daemon = daemon; } /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(THREAD_GROUP, r, THREAD_GROUP.getName() + "-" + namePrefix + "-" + THREAD_NUMBER.getAndIncrement()); thread.setDaemon(daemon); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } return thread; } /** * 自定义线程factory * * @param namePrefix * @param daemon * @return */ public static ThreadFactory create(final String namePrefix, final boolean daemon) { return new DisruptorThreadFactory(namePrefix, daemon); } } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/TableData.java ================================================ package com.xiao.springcloud.disruptor; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 09:48 * @since JDK 1.8 */ @Data public class TableData { /** * 主键ID */ private Long id; /** * 表名 */ private String tableName; } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/service/DisruptorService.java ================================================ package com.xiao.springcloud.disruptor.service; import com.xiao.springcloud.disruptor.TableData; /** * [简要描述]: 异步队列服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 09:50 * @since JDK 1.8 */ public interface DisruptorService { /** * [简要描述]:异步处理服务
* [详细描述]:
* * @param tableData : * llxiao 2019/3/25 - 9:51 **/ void execute(TableData tableData); } ================================================ FILE: SpringCloud-Canal/src/main/java/com/xiao/springcloud/disruptor/service/impl/DisruptorServiceImpl.java ================================================ package com.xiao.springcloud.disruptor.service.impl; import com.xiao.springcloud.disruptor.TableData; import com.xiao.springcloud.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Service; /** * [简要描述]: 异步队列服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 09:51 * @since JDK 1.8 */ @Service @Slf4j public class DisruptorServiceImpl implements DisruptorService, ApplicationListener { /** * [简要描述]:异步处理服务
* [详细描述]:
* * @param tableData : * @return void * llxiao 2019/3/25 - 9:51 **/ @Override public void execute(TableData tableData) { if (log.isDebugEnabled()) { log.debug("接受数据更新请求,更新表名:{},更新的主键为:{}", tableData.getTableName(), tableData.getId()); } if (null != tableData) { //业务处理 TODO } } /** * 初始化所有的service服务 */ private void initAllCacheService() { log.info(">>> 初始化所有需要缓存的服务对象....."); //业务需要初始化一些处理 } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { initAllCacheService(); } } ================================================ FILE: SpringCloud-Canal/src/main/resources/application.properties ================================================ ### canal config #是否集群,暂且支持zk的集群 spring.canal.server.cluster=false #canal服务 spring.canal.server.hostName=192.168.206.210 spring.canal.server.port=5555 spring.canal.server.destination=example spring.canal.server.userName= spring.canal.server.password= #ZK服务地址 spring.canal.server.zkServers= # 监听的数据库名称 spring.canal.server.dbName=basisdb ================================================ FILE: SpringCloud-Common/README.md ================================================ 公共包:
提供一些公共的组件: >feign的一些公共配置和一些坑的解决:requestmapping问题、服务内部交互异常和数据传输处理
>日志记录注解
>参数校验注解
================================================ FILE: SpringCloud-Common/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-Common jar 3.4.2 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-aop org.springframework.cloud spring-cloud-starter-feign org.projectlombok lombok true cn.hutool hutool-core 4.1.12 com.alibaba fastjson 1.2.83 org.apache.commons commons-lang3 3.4 commons-collections commons-collections 3.2.2 org.redisson redisson 3.5.7 com.google.guava guava 29.0-jre org.springframework.boot spring-boot-starter-cache com.lmax disruptor ${disruptor.version} ================================================ FILE: SpringCloud-Common/script/auto_deploy.sh ================================================ #!/bin/bash ## jenkins 自动部署脚本 jekins 项目 Post Steps设置 启动该脚本,配置如下: # BUILD_ID=DONTKILLME # bash /home/omni-services/remote-deploy/auto_deploy.sh SERVICE_NAME=content-management-service JARPATH=/root/.jenkins/workspace/$SERVICE_NAME-perf/target/$SERVICE_NAME.jar echo "service name:$SERVICE_NAME" SERVICE_HOME=/home/omni-services/$SERVICE_NAME JARFILE=$SERVICE_HOME/$SERVICE_NAME.jar BOOTSTRAP_FILE=$SERVICE_HOME/bootstrap.sh echo "=====service name: $SERVICE_NAME" echo "=====service home: $SERVICE_HOME" echo "=====service resouce jar path: $JARPATH" echo "=====service bootstrap file: $BOOTSTRAP_FILE" echo "=====service jar: $JARFILE" ## 停止原来的服务 echo "-----Stop service" bash $BOOTSTRAP_FILE stop ## 备份原来的Jar包 echo "-----Bach source jar file: $JARFILE" mv $JARFILE $SERVICE_HOME/$SERVICE_NAME-`date "+%Y-%m-%d %H:%M:%S"`.jar ## 复制新包 echo "-----Copy $JARPATH to $SERVICE_HOME" cp $JARPATH $SERVICE_HOME COPYRULST=$? ## 复制OK,开始启动 if [ $COPYRULST -eq 0 ]; then echo "-----Copy success,wait start service....." bash $BOOTSTRAP_FILE start RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----$SERVICE_NAME started!" else echo "-----$SERVICE_NAME start failed!" fi else echo "-----Not exist $SERVICE_NAME jar" exit 1 fi ================================================ FILE: SpringCloud-Common/script/bootstrap.sh ================================================ #!/bin/bash ## 加载配置,避免获取不到java_home source /etc/profile SERVICE_NAME=appname SERVICE_HOME=/home/admin/services PROG=$SERVICE_HOME/$SERVICE_NAME PIDFILE=$SERVICE_HOME/$SERVICE_NAME/$SERVICE_NAME.pid cd $SERVICE_HOME/$SERVICE_NAME # skywalking探针参数 SKYWALKING_AGENT_PATH=/home/admin/agent/skywalking-agent.jar SKYWALKING_SERVCIE_NAME=$SERVICE_NAME # GC参数 LOG_TIMESTAMP=`date "+%Y%m%d%H%M%S"` LOG_HOME=/data/logs/$SERVICE_NAME OOM_FILE=$LOG_HOME/oom-$LOG_TIMESTAMP.hprof GC_FILE=$LOG_HOME/gc-$LOG_TIMESTAMP.log #堆配置:服务模式(启动慢,一次编译,运行效率高)、最小内存512m、最大内存512m、年轻代大512m HEAP_OPTIONS="-server -Xms512m -Xmx512m -Xmn256m" #栈配置:设置每个线程的栈大小,DK5.0以后每个线程栈大小为1M,依据实际情况设定。较少的值内存能生成更多的线程(受系统限制),较大的值可(如2M)能会再一定程度上降低性能。 #STACKS_OPTIONS=-Xss128K ## GC配置:gc详情、gc时间、gc日志文件位置、gc堆栈信息 GC_OPTIONS="-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:$SERVICE_HOME/$SERVICE_NAME_gc.log -XX:+PrintHeapAtGC" ## gc收集器:Java提供串行收集器、并行收集器、并发收集器三种,串行收集器只适用于小数据量的情况,并行收集器主要以到达一定的吞吐量为目标 # 并行垃圾收集器:配置仅对年轻代有效、并行线程数20(建议与处理器数目相等) GC_COLLECTOR_PARAL="-XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseParallelOldGC" ## 老年代垃圾收集器:年老代垃圾收集方式为并行收集(1.6以后支持) # CMS垃圾收集器:初始标记->并发标记->并发预清理->重新标记->并发清理->并发重置(标记-清理 算法),CMS默认在老年代空间使用68%时候启动垃圾回收。可以通过-XX:CMSinitiatingOccupancyFraction=n来设置这个阀值。 #GC_COLLECTOR_CMS="-XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=4" ## oom配置:OOM时导出堆到文件、导出OOM的路径 #-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p //p代表的是当前进程的pid ,即当程序OOM时,在D:/a.txt中将会生成线程的dump。 OOM_OPTIONS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$SERVICE_HOME/$SERVICE_NAME.dump" ## 类加载监控 #CLASS_LOAD_MONITOR="-XX:+TraceClassLoading -XX:+PrintClassHistogram" ## 其他配置:新生的转老年代GC次数、禁止程序GC、set/get使用本地方法、加快编译、锁机制的性能改善 #OTHER_OPTIONS="-XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseFastAccessorMethods -XX:+AggressiveOpts -XX:+UseBiasedLocking" ## jvm 参数配置 JAVA_OPTS="$HEAP_OPTIONS $GC_OPTIONS $GC_COLLECTOR_PARAL $OOM_OPTIONS" cd $SERVICE_HOME status() { if [ -f $PIDFILE ]; then PID=$(cat $PIDFILE) if [ ! -x /proc/${PID} ]; then return 1 else return 0 fi else return 1 fi } case "$1" in start) status RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----$PIDFILE exists, process is already running or crashed" exit 1 fi ##检测 java环境 if [ ! -n $JAVA_HOME ]; then echo "-----Please check JAVA_HOME!" echo "-----Exit" exit 1 else echo "-----Jave home: $JAVA_HOME" echo "-----Starting $PROG ..." echo "-----Java options: $JAVA_OPTS" #nohup java $JAVA_OPTS -jar $JARFILE > $LOG_FILE 2>&1 & # GC参数+skywalking探针设置 nohup java -server -Xms2048m -Xmx2048m -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$OOM_FILE -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:$GC_FILE -javaagent:$SKYWALKING_AGENT_PATH -Dskywalking.agent.service_name=$SKYWALKING_SERVCIE_NAME -jar $SERVICE_HOME/$SERVICE_NAME/$SERVICE_NAME.jar > $SERVICE_NAME.log 2>&1 & RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----$PROG is started" echo $! > $PIDFILE exit 0 else echo "-----Stopping $PROG" rm -f $PIDFILE exit 1 fi fi ;; stop) status RETVAL=$? if [ $RETVAL -eq 0 ]; then echo "-----Shutting down $PROG" kill -9 `cat $PIDFILE` RETVAL=$? if [ $RETVAL -eq 0 ]; then LS_DATE=`date +%Y%m%d` BACK_LOG=$SERVICE_NAME-$LS_DATE.log BACK_LOG_FILE=$SERVICE_HOME/$BACK_LOG ## 备份日志文件 if [ -f "$BACK_LOG_FILE" ]; then echo "-----Back log file $BACK_LOG exist and copy log to back file!" echo "--------------------------------------------------------------------------------------------------------------------" >> $BACK_LOG echo `date "+%Y-%m-%d %H:%M:%S"` >> $BACK_LOG echo "-----------------------------------------重新启动-------------------------------------------------------------------" >> $BACK_LOG cat $LOG_FILE >> $BACK_LOG ## 删除日志文件 rm -f $LOG_FILE else echo "-----Back log file to $BACK_LOG...." mv $LOG_FILE $BACK_LOG fi ## 删除Pid文件 rm -f $PIDFILE else echo "-----Failed to stopping $PROG" fi fi ;; status) status RETVAL=$? if [ $RETVAL -eq 0 ]; then PID=$(cat $PIDFILE) echo "-----$PROG is running ($PID)" else echo "-----$PROG is not running" fi ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart|status}" ;; esac ================================================ FILE: SpringCloud-Common/script/remote_deploy.sh ================================================ #!/bin/bash ## jenkins 自动部远程署脚本,需要配置两个服务器之间免密登录 ## 免登陆配置参考:https://blog.csdn.net/u011186019/article/details/51737760?utm_source=blogxgwz4 ##jekins 项目 Post Steps设置 启动该脚本,配置如下: #BUILD_ID=DONTKILLME -- 表示Jenkins执行完后不杀死该进程,否则会再jenkins执行完后杀死启动的进程 #bash /home/omni-services/remote-deploy/remote-deploy.sh SERVICE_NAME=appname REMOTE_SERVICES_HOME=/home/services REMOTE_SERVICE_IP=目标服务器IP REMOTE_SERVICE_HOME=$REMOTE_SERVICES_HOME/$SERVICE_NAME ##服务jar REMOTE_SERVICE_JAR=$REMOTE_SERVICE_HOME/$SERVICE_NAME.jar ##启动脚本 REMOTE_SERVICE_BOOTSTRAP=$REMOTE_SERVICE_HOME/bootstrap.sh ##jenkins编译完成后服务Jar包位置 LOCAL_JAR_PATH=/root/.jenkins/workspace/$SERVICE_NAME-perf/target/$SERVICE_NAME.jar echo "=============Service name: $SERVICE_NAME" echo "=============Remote services home: $REMOTE_SERVICES_HOME" echo "=============Remote server ip: $REMOTE_SERVICE_IP" echo "=============Remote service home: $REMOTE_SERVICE_HOME" echo "=============Remote service jar: $REMOTE_SERVICE_JAR" echo "=============Remote service bootstrap file: $REMOTE_SERVICE_BOOTSTRAP" ## 远程操作,停止并备份原来的服务 echo "-----Stop and Delete remote service: $SERVICE_NAME" ssh -T root@$REMOTE_SERVICE_IP << remotessh sh $REMOTE_SERVICE_BOOTSTRAP stop mv $REMOTE_SERVICE_JAR $REMOTE_SERVICE_HOME/$SERVICE_NAME-`date "+%Y-%m-%d %H:%M:%S"`.jar exit remotessh ##复制一份新的服务Jar echo "-----Scp $LOCAL_JAR_PATH to $REMOTE_SERVICE_HOME" scp $LOCAL_JAR_PATH root@$REMOTE_SERVICE_IP:$REMOTE_SERVICE_HOME ##远程启动服务 echo "-----Start remote service" ssh -T root@$REMOTE_SERVICE_IP << remotessh sh $REMOTE_SERVICE_BOOTSTRAP start exit remotessh exit ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/SkywalkingService.java ================================================ /* * Winner * 文件名 :SkywalkingService.java * 创建人 :llxiao * 创建时间:2018年3月29日 */ package com.xiao.springcloud.demo.common; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月29日 * @since 项目名称 项目版本 */ public interface SkywalkingService { String skywalking(String test); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/README.md ================================================ 1. redisson yml配置加载,支持单机、集群、云托管、sentinel模式
2. 配置文件中添加配置文件即可开启redisson的配置:
`` redisson.fileName: redission-cluster(自定义)`` 3. 提供缓存基本服务和分布式服务:
``> CacheService 提供缓存基础服务``
``> DistributedService 提供分布式**可重入公平/非公平锁**、**读写锁**、**闭锁**``
20181018 添加本地缓存 1. 引入spring cache和guava jar包 ``` com.google.guava guava 25.1-jre org.springframework.boot spring-boot-starter-cache ``` 2. 代码方法上使用 ``` @Cacheable(value = "methodName", key = "'methodName'.concat({#param1}).concat({#parma2})") ``` ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/code/FastJsonCodec.java ================================================ package com.xiao.springcloud.demo.common.cache.code; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; import lombok.extern.slf4j.Slf4j; import org.redisson.client.codec.Codec; import org.redisson.client.handler.State; import org.redisson.client.protocol.Decoder; import org.redisson.client.protocol.Encoder; import java.io.IOException; /** * [简要描述]: * [详细描述]: * * @author zhdong * @version 1.0, 2018/9/20 * @since JDK 1.8 */ @Slf4j public class FastJsonCodec implements Codec { public static final FastJsonCodec INSTANCE = new FastJsonCodec(); private Encoder encoder = null; private Decoder decoder = null; private Encoder keyEncoder = null; private Decoder keyDecoder = null; public FastJsonCodec() { this.encoder = new Encoder() { public ByteBuf encode(Object in) throws IOException { ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); try { SerializerObject serial = new SerializerObject(in); ByteBufOutputStream os = new ByteBufOutputStream(out); JSON.writeJSONString(os, serial, SerializerFeature.WriteMapNullValue, //是否输出值为null的字段,默认为false SerializerFeature.DisableCircularReferenceDetect, //消除循环引用 SerializerFeature.WriteClassName,//写入类名便于序列化解析 SerializerFeature.WriteNullStringAsEmpty); return os.buffer(); } catch (IOException var4) { out.release(); throw var4; } } }; this.decoder = new Decoder() { public Object decode(ByteBuf buf, State state) throws IOException { SerializerObject serial = JSON.parseObject(new ByteBufInputStream(buf), SerializerObject.class); return serial.getValue(); } }; this.keyEncoder = new Encoder() { public ByteBuf encode(Object in) throws IOException { ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); try { ByteBufOutputStream os = new ByteBufOutputStream(out); JSON.writeJSONString(os, in); return os.buffer(); } catch (IOException var4) { out.release(); throw var4; } } }; this.keyDecoder = new Decoder() { public Object decode(ByteBuf buf, State state) throws IOException { return JSON.parseObject(new ByteBufInputStream(buf), String.class); } }; } @Override public Decoder getMapValueDecoder() { return decoder; } @Override public Encoder getMapValueEncoder() { return encoder; } @Override public Decoder getMapKeyDecoder() { return keyDecoder; } @Override public Encoder getMapKeyEncoder() { return keyEncoder; } @Override public Decoder getValueDecoder() { return decoder; } @Override public Encoder getValueEncoder() { return encoder; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/code/SerializerObject.java ================================================ package com.xiao.springcloud.demo.common.cache.code; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * [简要描述]: 使用fastjson序列化的类 * [详细描述]: * * @author zhdong * @version 1.0, 2018/9/21 * @since JDK 1.8 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class SerializerObject implements Serializable { private Object value; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/conf/RedissonConfig.java ================================================ package com.xiao.springcloud.demo.common.cache.conf; import com.xiao.springcloud.demo.common.cache.code.FastJsonCodec; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.connection.balancer.RoundRobinLoadBalancer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; @Configuration @Slf4j // 属性值存在该配置才会生效 @ConditionalOnProperty(name = RedissonConfig.REDISSON_FILE_NAME) public class RedissonConfig { public static final String REDISSON_FILE_NAME = "redisson.fileName"; @Value("${spring.cloud.config.uri:}") private String configUrl; @Value("${spring.cloud.config.label:}") private String label; @Value("${spring.cloud.config.profile:}") private String profile; /* redissoin配置文件名称 */ @Value("${redisson.fileName:redission}") private String redissionFileName; // 配置中心读取ression.yml文件 private String getYamlFromConfig() throws IOException { String uri = configUrl + label + "/" + redissionFileName + "-" + profile + ".yml"; log.info("redission配置文件uri:" + uri); URL url = new URL(uri); URLConnection connection = url.openConnection(); StringBuilder buffer = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { String msg = null; while ((msg = br.readLine()) != null) { buffer.append(msg).append("\r\n"); } } return buffer.toString(); } @Bean public Config config() throws IOException { Config config = Config.fromYAML(getYamlFromConfig()); if (config.isClusterConfig()) { config.useClusterServers().setLoadBalancer(new RoundRobinLoadBalancer()); } return config; } @Bean(destroyMethod = "shutdown") public RedissonClient redissonClient(Config config) throws IOException { log.info("create RedissonClient, config is : {}", config.toJSON()); //序列化统一使用fastjson config.setCodec(FastJsonCodec.INSTANCE); return Redisson.create(config); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/dto/EntryDto.java ================================================ package com.xiao.springcloud.demo.common.cache.dto; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/15 17:18 * @since JDK 1.8 */ @Data public class EntryDto { private String key; private T value; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/local/SpringGuavaCacheConfig.java ================================================ package com.xiao.springcloud.demo.common.cache.local; import com.google.common.cache.CacheBuilder; import org.springframework.cache.CacheManager; import org.springframework.cache.guava.GuavaCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; /** * [简要描述]: guava+springcache实现本地缓存 * [详细描述]: * * @author llxiao * @version 1.0, 2018/9/29 17:28 * @since JDK 1.8 */ @Configuration public class SpringGuavaCacheConfig { @Bean public CacheManager cacheManager() { GuavaCacheManager cacheManager = new GuavaCacheManager(); cacheManager // 3S过期时间,初始容量1000个,最大10000个 .setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).initialCapacity(1000) .maximumSize(10000)); return cacheManager; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/service/CacheService.java ================================================ package com.xiao.springcloud.demo.common.cache.service; import com.xiao.springcloud.demo.common.cache.dto.EntryDto; import java.util.List; import java.util.Map; import java.util.Set; /** * [简要描述]: 缓存服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/11 13:45 * @since JDK 1.8 */ public interface CacheService { /** * [简要描述]:缓存中获取一个string
* [详细描述]:
* * @param key : K * @return java.lang.String * llxiao 2018/10/11 - 16:02 **/ String get(String key); /** * [简要描述]:添加一个string到缓存
* [详细描述]:
* * @param key : K * @param value : V * llxiao 2018/10/11 - 16:02 **/ void set(String key, String value); /** * [简要描述]:批量设置string到缓存
* [详细描述]:
* * @param list : 批量string数据 * llxiao 2018/10/15 - 10:07 **/ void batchSet(List> list); /** * [简要描述]:添加一个string到缓存
* [详细描述]:
* * @param key : K * @param value : V * @param leaseTime : 存活时间,单位秒 * llxiao 2018/10/11 - 16:02 **/ void set(String key, String value, long leaseTime); /** * [简要描述]:获取一个缓存对象
* [详细描述]:
* * @param key : * @return java.lang.Object * llxiao 2018/10/11 - 15:41 **/ T getObject(String key); /** * [简要描述]:添加一个对象到缓存
* [详细描述]:
* * @param key : key * @param value : 值 * llxiao 2018/10/11 - 15:59 **/ void setObject(String key, T value); /** * [简要描述]:批量设置Object到缓存
* [详细描述]:
* * @param objs : 数据集合 * llxiao 2018/10/15 - 10:08 **/ void batchSetObj(List> objs); /** * [简要描述]:添加一个对象到缓存
* [详细描述]:
* * @param key : key * @param value : 值 * @param leaseTime : 存活时间,单位秒 * llxiao 2018/10/11 - 15:59 **/ void setObject(String key, T value, long leaseTime); /** * [简要描述]:从map中获取指定key的值
* [详细描述]:
* * @param key : K * @param field : 属性名 * @return java.lang.Object * llxiao 2018/10/11 - 16:23 **/ T hget(String key, String field); /** * [简要描述]:设置一个值到map中
* [详细描述]:
* * @param key : K * @param field : 属性名 * @param value : 属性值 * llxiao 2018/10/11 - 16:32 **/ void hset(String key, String field, T value); /** * [简要描述]:指定key的map
* [详细描述]:
* * @param key : K * @return java.util.Map * llxiao 2018/10/11 - 19:45 **/ Map hgetAll(String key); /** * [简要描述]:设置map集
* [详细描述]:
* * @param key :key * @param maps : 集合 * llxiao 2018/10/11 - 19:47 **/ void hsetAll(String key, Map maps); /** * [简要描述]:批量hset
* [详细描述]:
* * @param maps : KEY,MAP集合 * llxiao 2018/10/15 - 11:16 **/ void batchHset(List>> maps); /** * [简要描述]:Set中添加一个元素
* [详细描述]:
* * @param key : K * @param value : 值 * llxiao 2018/10/11 - 19:57 **/ void addSet(String key, T value); /** * [简要描述]:获取一个set集合
* [详细描述]:
* * @param key : Key * @return Set * llxiao 2018/10/11 - 19:59 **/ Set getSet(String key); /** * [简要描述]:Set中移除一个元素
* [详细描述]:
* * @param key : K * @param value : 元素 * llxiao 2018/10/11 - 20:00 **/ void removeSet(String key, T value); /** * [简要描述]:批量从缓存获取对象集合
* [详细描述]:
* * @param keys : KEY * @return java.util.List * llxiao 2018/10/15 - 10:00 **/ List batchGet(List keys); /** * [简要描述]:批量从缓存获取MAP集合
* [详细描述]:
* * @param keys : KEY * @return java.util.List * llxiao 2018/10/15 - 10:01 **/ List> batchGetMap(List keys); /** * [简要描述]:删除一个对象
* [详细描述]:
* * @param key : key * llxiao 2018/10/15 - 14:40 **/ void delObject(String key); /** * [简要描述]:清空一个map
* [详细描述]:
* * @param key : KEY * llxiao 2018/10/15 - 14:41 **/ void delMap(String key); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/service/DistributedService.java ================================================ package com.xiao.springcloud.demo.common.cache.service; import org.redisson.api.RCountDownLatch; import org.redisson.api.RLock; import java.util.concurrent.TimeUnit; /** * [简要描述]: 分布式服务:
* [详细描述]: 分布式锁、分布式同步器等
* 公平锁:排队获取锁,保证先来先服务 * 非公平锁:类似随机,即新来的线程可能会优先于已经在队列中的线程获取到锁,但只要进入了队列就会保证公平。ReentrantLock默认实现为非公平锁 * 读写锁:允许多个多锁,但只能又一个写锁。读写、写写互斥 * 闭锁:CountDownLatch * * @author llxiao * @version 1.0, 2018/10/11 09:04 * @since JDK 1.8 */ public interface DistributedService { /** * [简要描述]:获取分布式非公平可重入锁
* [详细描述]:阻塞式获取,直到获取锁
*

* Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。
* 默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定
* * @param lockKey : key * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:06 **/ RLock getRLock(String lockKey); /** * [简要描述]:非公平可重入锁,并在指定单位时间内自动释放锁,无需手动释放
* [详细描述]:阻塞式,直到获取锁
* * @param lockKey : key * @param leaseTime : 存货时间 * @param unit : 时间单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:37 **/ RLock autoReleaseRLock(String lockKey, long leaseTime, TimeUnit unit); /** * [简要描述]:指定时间内尝试获取非公平可重入锁
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ RLock tryLock(String lockKey, long timeout); /** * [简要描述]:指定时间内尝试获取非公平可重入锁,获取成功并设置锁自动失效时间
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ RLock tryLockAutoRelease(String lockKey, long timeout, long leaseTime, TimeUnit unit); /** * [简要描述]:获取分布式可重入公平锁
* [详细描述]:阻塞式获取,直到获取锁
* * @param lockKey : key * @return org.redisson.api.RLock * llxiao 2018/10/11 - 10:40 **/ RLock getFairRLock(String lockKey); /** * [简要描述]:获取可重入公平锁,并在指定单位时间内自动释放锁,无需手动释放
* [详细描述]:阻塞式,直到获取锁
* * @param lockKey : key * @param leaseTime : 存货时间 * @param unit : 时间单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:37 **/ RLock autoReleaseRFairLock(String lockKey, long leaseTime, TimeUnit unit); /** * [简要描述]:指定时间内尝试获取公平可重入锁
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ RLock tryFairLock(String lockKey, long timeout); /** * [简要描述]:指定时间内尝试获取公平可重入锁,获取成功并设置锁自动失效时间
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ RLock tryFairLockAutoRelease(String lockKey, long timeout, long leaseTime, TimeUnit unit); /** * [简要描述]:获取读写锁
* [详细描述]:
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @return org.redisson.api.RReadWriteLock * llxiao 2018/10/11 - 10:54 **/ RLock getReadWriteLock(String lockKey, boolean isWrite); /** * [简要描述]:获取读写锁,指定单位时间内自动过期
* [详细描述]:
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param lease : 存活时间 * @param unit : 时间单位 * @return org.redisson.api.RReadWriteLock * llxiao 2018/10/11 - 10:54 **/ RLock autoReleaseReadWriteLock(String lockKey, boolean isWrite, Long lease, TimeUnit unit); /** * [简要描述]:指定时间内尝试获取读写锁
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ RLock tryReadWriteLock(String lockKey, boolean isWrite, long timeout); /** * [简要描述]:指定时间内尝试获取读写锁,获取成功并设置锁自动失效时间
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ RLock tryReadWriteLockAutoRelease(String lockKey, boolean isWrite, long timeout, long leaseTime, TimeUnit unit); /** * [简要描述]:获取一点数量的分布式闭锁(CountDownLatch)
* [详细描述]:
* * @param key : key * @param count : 数量 * @return org.redisson.api.RCountDownLatch * llxiao 2018/10/11 - 11:49 **/ RCountDownLatch getRCountDownLatch(String key, int count); /** * [简要描述]:每次消耗一个分布式闭锁
* [详细描述]:
* * @param key : key * llxiao 2018/10/11 - 11:53 **/ void countDown(String key); /** * [简要描述]:可重入锁解锁
* [详细描述]:
* * @param lockKey : key * llxiao 2018/10/11 - 9:42 **/ void unRLock(String lockKey); /** * [简要描述]:可重入锁解锁
* [详细描述]:
* * @param rLock : RLock * llxiao 2018/10/11 - 9:42 **/ void unRLock(RLock rLock); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/service/impl/CacheServiceRedisImpl.java ================================================ package com.xiao.springcloud.demo.common.cache.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.xiao.springcloud.demo.common.cache.conf.RedissonConfig; import com.xiao.springcloud.demo.common.cache.dto.EntryDto; import com.xiao.springcloud.demo.common.cache.service.CacheService; import org.apache.commons.lang3.StringUtils; import org.redisson.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * [简要描述]: 缓存服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/11 15:42 * @since JDK 1.8 */ @Service //仅仅在当前上下文中存在某个对象时,才会实例化一个Bean @ConditionalOnBean(RedissonConfig.class) //如果存在它修饰的类的bean,则不需要再创建这个bean //@ConditionalOnMissingBean public class CacheServiceRedisImpl implements CacheService { @Autowired private RedissonClient redissonClient; /** * [简要描述]:缓存中获取一个string
* [详细描述]:
* * @param key : K * @return java.lang.String * llxiao 2018/10/11 - 16:02 **/ @Override public String get(String key) { if (null != redissonClient && StringUtils.isNotBlank(key)) { RBucket bucket = redissonClient.getBucket(key); return bucket.get(); } return ""; } /** * [简要描述]:添加一个string到缓存
* [详细描述]:
* * @param key : K * @param value : V * @return void * llxiao 2018/10/11 - 16:02 **/ @Override public void set(String key, String value) { if (redissonClient != null && StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) { RBucket bucket = redissonClient.getBucket(key); bucket.set(value); } } /** * [简要描述]:批量设置string到缓存
* [详细描述]:
* * @param list : 批量string数据 * llxiao 2018/10/15 - 10:07 **/ @Override public void batchSet(List> list) { if (redissonClient != null && CollectionUtil.isNotEmpty(list)) { RBatch batch = redissonClient.createBatch(); for (EntryDto entry : list) { batch.getBucket(entry.getKey()).setAsync(entry.getValue()); } batch.execute(); } } /** * [简要描述]:添加一个string到缓存
* [详细描述]:
* * @param key : K * @param value : V * @param leaseTime : 存活时间,单位秒 * llxiao 2018/10/11 - 16:02 **/ @Override public void set(String key, String value, long leaseTime) { RBucket bucket = redissonClient.getBucket(key); bucket.set(value, leaseTime, TimeUnit.SECONDS); } /** * [简要描述]:获取一个缓存对象
* [详细描述]:
* * @param key : * @return java.lang.Object * llxiao 2018/10/11 - 15:41 **/ @Override public T getObject(String key) { RBucket bucket = redissonClient.getBucket(key); return null != bucket ? bucket.get() : null; } /** * [简要描述]:添加一个对象到缓存
* [详细描述]:
* * @param key : key * @param value : 值 * llxiao 2018/10/11 - 15:59 **/ @Override public void setObject(String key, T value) { RBucket bucket = redissonClient.getBucket(key); bucket.set(value); } /** * [简要描述]:批量设置Object到缓存
* [详细描述]:
* * @param objs : 数据集合 * llxiao 2018/10/15 - 10:08 **/ @Override public void batchSetObj(List> objs) { if (redissonClient != null && CollectionUtil.isNotEmpty(objs)) { RBatch batch = redissonClient.createBatch(); for (EntryDto entry : objs) { batch.getBucket(entry.getKey()).setAsync(entry.getValue()); } batch.execute(); } } /** * [简要描述]:添加一个对象到缓存
* [详细描述]:
* * @param key : key * @param value : 值 * @param leaseTime : 存活时间,单位秒 * llxiao 2018/10/11 - 15:59 **/ @Override public void setObject(String key, T value, long leaseTime) { RBucket bucket = redissonClient.getBucket(key); bucket.set(value, leaseTime, TimeUnit.SECONDS); } /** * [简要描述]:
* [详细描述]:
* * @param key : * @param field : * @return java.lang.Object * llxiao 2018/10/11 - 16:23 **/ @Override public T hget(String key, String field) { RMap map = redissonClient.getMap(key); return map.get(field); } /** * [简要描述]:设置一个值到map中
* [详细描述]:
* * @param key : K * @param field : 属性名 * @param value : 属性值 * llxiao 2018/10/11 - 16:32 **/ @Override public void hset(String key, String field, T value) { RMap map = redissonClient.getMap(key); map.put(field, value); } /** * [简要描述]:指定key的map
* [详细描述]:
* * @param key : K * @return Map * llxiao 2018/10/11 - 19:45 **/ @Override public Map hgetAll(String key) { return (Map) redissonClient.getMap(key).readAllMap(); } /** * [简要描述]:设置map集
* [详细描述]:
* * @param key :key * @param maps : 集合 * llxiao 2018/10/11 - 19:47 **/ @Override public void hsetAll(String key, Map maps) { RMap map = redissonClient.getMap(key); map.putAll(maps); } /** * [简要描述]:批量hset
* [详细描述]:
* * @param maps : KEY,MAP集合 * llxiao 2018/10/15 - 11:16 **/ @Override public void batchHset(List>> maps) { if (null != redissonClient && CollectionUtil.isNotEmpty(maps)) { RBatch batch = redissonClient.createBatch(); for (EntryDto> entry : maps) { batch.getMap(entry.getKey()).putAllAsync(entry.getValue()); } batch.execute(); } } /** * [简要描述]:Set中添加一个元素
* [详细描述]:
* * @param key : K * @param value : 值 * llxiao 2018/10/11 - 19:57 **/ @Override public void addSet(String key, T value) { RSet set = redissonClient.getSet(key); set.add(value); } /** * [简要描述]:获取一个set集合
* [详细描述]:
* * @param key : Key * @return T * llxiao 2018/10/11 - 19:59 **/ @Override public Set getSet(String key) { return (Set) redissonClient.getSet(key).readAll(); } /** * [简要描述]:Set中移除一个元素
* [详细描述]:
* * @param key : K * @param value : 元素 **/ @Override public void removeSet(String key, T value) { redissonClient.getSet(key).remove(value); } /** * [简要描述]:批量从缓存获取对象集合
* [详细描述]:
* * @param keys : KEY * @return java.util.List * llxiao 2018/10/15 - 10:00 **/ @Override public List batchGet(List keys) { RBatch batch = redissonClient.createBatch(); for (String key : keys) { batch.getBucket(key).getAsync(); } BatchResult result = batch.execute(); return result.getResponses(); } /** * [简要描述]:批量从缓存获取MAP集合
* [详细描述]:
* * @param keys : KEY * @return java.util.List * llxiao 2018/10/15 - 10:01 **/ @Override public List> batchGetMap(List keys) { RBatch batch = redissonClient.createBatch(); for (String key : keys) { batch.getMap(key).readAllMapAsync(); } BatchResult result = batch.execute(); return result.getResponses(); } /** * [简要描述]:删除一个对象
* [详细描述]:
* * @param key : key * llxiao 2018/10/15 - 14:40 **/ @Override public void delObject(String key) { RBucket bucket = redissonClient.getBucket(key); bucket.deleteAsync(); } /** * [简要描述]:清空一个map
* [详细描述]:
* * @param key : KEY * llxiao 2018/10/15 - 14:41 **/ @Override public void delMap(String key) { RMap map = redissonClient.getMap(key); map.deleteAsync(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/cache/service/impl/DistributedServiceRedissonImpl.java ================================================ package com.xiao.springcloud.demo.common.cache.service.impl; import com.xiao.springcloud.demo.common.cache.conf.RedissonConfig; import com.xiao.springcloud.demo.common.cache.service.DistributedService; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RCountDownLatch; import org.redisson.api.RLock; import org.redisson.api.RReadWriteLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; /** * [简要描述]: redisson分布式服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/11 09:19 * @since JDK 1.8 */ @Service @Slf4j //仅仅在当前上下文中存在某个对象时,才会实例化一个Bean @ConditionalOnBean(RedissonConfig.class) //如果存在它修饰的类的bean,则不需要再创建这个bean public class DistributedServiceRedissonImpl implements DistributedService { @Autowired private RedissonClient redissonClient; /** * [简要描述]:获取分布式非公平可重入锁(加锁)
* [详细描述]:阻塞式获取
*

* Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。
* 默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定
* * @param lockKey : key * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:06 **/ @Override public RLock getRLock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * [简要描述]:非公平可重入锁,并在指定单位时间内自动释放锁,无需手动释放
* [详细描述]:
* * @param lockKey : key * @param leaseTime : 存货时间 * @param unit : 时间单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:37 **/ @Override public RLock autoReleaseRLock(String lockKey, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, unit); return lock; } /** * [简要描述]:指定时间内尝试获取非公平可重入锁(加锁)
* [详细描述]:
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ @Override public RLock tryLock(String lockKey, long timeout) { RLock lock = redissonClient.getLock(lockKey); boolean flag = false; //锁可用立即返回 true,锁不可用立即返回false //boolean flag = lock.tryLock(); try { // 获取不到锁等待一段时间,如果获取到返回true,获取不到返回false flag = lock.tryLock(timeout, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); lock.unlock(); } return flag ? lock : null; } /** * [简要描述]:指定时间内尝试获取非公平可重入锁,获取成功并设置锁自动失效时间(加锁)
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ @Override public RLock tryLockAutoRelease(String lockKey, long timeout, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); boolean flag = false; try { flag = lock.tryLock(timeout, leaseTime, unit); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); lock.unlock(); } return flag ? lock : null; } /** * [简要描述]:获取分布式可重入公平锁(加锁)
* [详细描述]:阻塞式获取,直到获取锁
* * @param lockKey : key * @return org.redisson.api.RLock * llxiao 2018/10/11 - 10:40 **/ @Override public RLock getFairRLock(String lockKey) { RLock fairLock = redissonClient.getFairLock(lockKey); fairLock.lock(); return fairLock; } /** * [简要描述]:获取可重入公平锁,并在指定单位时间内自动释放锁,无需手动释放
* [详细描述]:阻塞式,直到获取锁
* * @param lockKey : key * @param leaseTime : 存货时间 * @param unit : 时间单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:37 **/ @Override public RLock autoReleaseRFairLock(String lockKey, long leaseTime, TimeUnit unit) { RLock fairLock = redissonClient.getFairLock(lockKey); fairLock.lock(leaseTime, unit); return fairLock; } /** * [简要描述]:指定时间内尝试获取公平可重入锁
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ @Override public RLock tryFairLock(String lockKey, long timeout) { RLock lock = redissonClient.getFairLock(lockKey); boolean flag = false; //锁可用立即返回 true,锁不可用立即返回false //boolean flag = lock.tryLock(); try { // 获取不到锁等待一段时间,如果获取到返回true,获取不到返回false flag = lock.tryLock(timeout, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); lock.unlock(); } return flag ? lock : null; } /** * [简要描述]:指定时间内尝试获取公平可重入锁,获取成功并设置锁自动失效时间
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ @Override public RLock tryFairLockAutoRelease(String lockKey, long timeout, long leaseTime, TimeUnit unit) { RLock lock = redissonClient.getLock(lockKey); boolean flag = false; try { // 尝试加锁,最多等待timeout秒,上锁以后leaseTime(unit)自动解锁 flag = lock.tryLock(timeout, leaseTime, unit); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); lock.unlock(); } return flag ? lock : null; } /** * [简要描述]:获取读写锁
* [详细描述]:
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @return org.redisson.api.RReadWriteLock * llxiao 2018/10/11 - 10:54 **/ @Override public RLock getReadWriteLock(String lockKey, boolean isWrite) { RReadWriteLock rwlock = redissonClient.getReadWriteLock(lockKey); RLock rLock; if (isWrite) { rLock = rwlock.writeLock(); } else { rLock = rwlock.readLock(); } rLock.lock(); return rLock; } /** * [简要描述]:获取读写锁,指定单位时间内自动过期
* [详细描述]:
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param lease : 存活时间 * @param unit : 时间单位 * @return org.redisson.api.RReadWriteLock * llxiao 2018/10/11 - 10:54 **/ @Override public RLock autoReleaseReadWriteLock(String lockKey, boolean isWrite, Long lease, TimeUnit unit) { RReadWriteLock rwlock = redissonClient.getReadWriteLock(lockKey); RLock rLock; if (isWrite) { rLock = rwlock.writeLock(); } else { rLock = rwlock.readLock(); } rLock.lock(lease, unit); return rLock; } /** * [简要描述]:指定时间内尝试获取读写锁
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param timeout : 超时时间,单位秒 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:51 **/ @Override public RLock tryReadWriteLock(String lockKey, boolean isWrite, long timeout) { RReadWriteLock rwlock = redissonClient.getReadWriteLock(lockKey); RLock rLock; if (isWrite) { rLock = rwlock.writeLock(); } else { rLock = rwlock.readLock(); } try { rLock.tryLock(timeout, TimeUnit.SECONDS); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); rLock.unlock(); } return rLock; } /** * [简要描述]:指定时间内尝试获取读写锁,获取成功并设置锁自动失效时间
* [详细描述]:获取不到锁返回null
* * @param lockKey : key * @param isWrite : true写锁,false读锁 * @param timeout : 超时时间,单位秒 * @param leaseTime : 锁失效时间 * @param unit : 锁失效单位 * @return org.redisson.api.RLock * llxiao 2018/10/11 - 9:52 **/ @Override public RLock tryReadWriteLockAutoRelease(String lockKey, boolean isWrite, long timeout, long leaseTime, TimeUnit unit) { RLock rLock = this.getReadWriteLock(lockKey, isWrite); boolean flag = false; try { // 尝试加锁,最多等待timeout秒,上锁以后leaseTime(unit)自动解锁 flag = rLock.tryLock(timeout, leaseTime, unit); } catch (InterruptedException e) { log.warn("尝试获取锁失败,线程中断!", e); rLock.unlock(); } return flag ? rLock : null; } /** * [简要描述]:闭锁(CountDownLatch)
* [详细描述]:
* * @param key : key * @param count : 数量 * @return org.redisson.api.RCountDownLatch * llxiao 2018/10/11 - 11:49 **/ @Override public RCountDownLatch getRCountDownLatch(String key, int count) { RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch(key); rCountDownLatch.trySetCount(count); return rCountDownLatch; } /** * [简要描述]:每次消耗一个分布式闭锁
* [详细描述]:
* * @param key : key * llxiao 2018/10/11 - 11:53 **/ @Override public void countDown(String key) { RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch(key); rCountDownLatch.countDown(); } /** * [简要描述]:解锁
* [详细描述]:
* * @param lockKey : key * llxiao 2018/10/11 - 9:42 **/ @Override public void unRLock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } /** * [简要描述]:解锁
* [详细描述]:
* * @param rLock : RLock * llxiao 2018/10/11 - 9:42 **/ @Override public void unRLock(RLock rLock) { if (null != rLock) { rLock.unlock(); } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/conf/FeignConfiguration.java ================================================ package com.xiao.springcloud.demo.common.conf; import feign.Feign; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.web.WebMvcRegistrations; import org.springframework.boot.autoconfigure.web.WebMvcRegistrationsAdapter; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * [简要描述]: 解决feginclient 调用时使用requestmapping注解被springmvc加载的问题,springcloud2.0的版本据说已经解决这个问题
* [详细描述]: 可能产生的问题:
* 1.由于服务消费者并不提供这些接口,对于开发者来说容易造成误解
* 2.由于加载了一些外部服务的接口定义,还存在与自身接口定义冲突的潜在风险
* @author llxiao * @version 1.0, 2018/9/30 09:58 * @since JDK 1.8 */ @Configuration @ConditionalOnClass({ Feign.class }) public class FeignConfiguration { @Bean public WebMvcRegistrations feignWebRegistrations() { return new WebMvcRegistrationsAdapter() { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new FeignRequestMappingHandlerMapping(); } }; } private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected boolean isHandler(Class beanType) { // 不能被@FeignClient注解修饰的类才会进行解析加载 return super.isHandler(beanType) && !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class); } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/DataEventFactory.java ================================================ package com.xiao.springcloud.demo.common.disruptor; import com.lmax.disruptor.EventFactory; import com.xiao.springcloud.demo.common.disruptor.data.DataEvent; /** * [简要描述]: 定义事件工厂 * [详细描述]: 定义了如何实例化定义的事件(Event) * Disruptor 通过 EventFactory 在 RingBuffer 中预创建 Event 的实例

* 一个 Event 实例实际上被用作一个“数据槽”,发布者发布前,先从 RingBuffer 获得一个 Event 的实例,然后往 Event 实例中填充数据,
* 之后再发布到 RingBuffer 中,之后由 Consumer 获得该 Event 实例并从中读取数据
* * @author llxiao * @version 1.0, 2019/3/21 15:17 * @since JDK 1.8 */ public class DataEventFactory implements EventFactory { @Override public DataEvent newInstance() { return new DataEvent(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/DisruptorConsumer.java ================================================ package com.xiao.springcloud.demo.common.disruptor; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.WorkHandler; import com.xiao.springcloud.demo.common.disruptor.data.DataEvent; import com.xiao.springcloud.demo.common.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executor; /** * [简要描述]: Disruptor消费者之Handler。 * [详细描述]: *
* 单消费者:实现EventHandler接口(DataEvent dataEvent, long sequence, boolean endOfBatch)方法
* 多消费者:实现WorkHandler接口的onEvent(DataEvent dataEvent)方法
* --广播:对于多个消费者,每条信息会达到所有的消费者,被多次处理,一般每个消费者业务逻辑不通,用于同一个消息的不同业务逻辑处理
* --消费者之间无依赖关系 disruptor.handleEventsWith(handler1,handler2,handler3);
* --假设handler3必须在handler1,handler2处理完成后进行处理:disruptor.handleEventsWith(handler1,handler2).then(handler3);
* --分组:对于同一组内的多个消费者,每条信息只会被组内一个消费者处理,每个消费者业务逻辑一般相同,用于多消费者并发处理一组消息
* --假设handler1,handler2,handler3都实现了WorkHandler,则调用以下代码就可以实现分组:
* --disruptor.handleEventsWithWorkerPool(handler1, handler2, handler3);
* 广播和分组之间也是可以排列组合的
* link:http://www.importnew.com/27652.html * * @author llxiao * @version 1.0, 2019/3/21 15:20 * @since JDK 1.8 */ @Slf4j public class DisruptorConsumer implements EventHandler, WorkHandler { //具体的服务 private DisruptorService disruptorService; /** * */ private final Executor executor; public DisruptorConsumer(DisruptorService disruptorService, Executor executor) { this.disruptorService = disruptorService; this.executor = executor; } @Override public void onEvent(DataEvent dataEvent) throws Exception { if (log.isDebugEnabled()) { log.info("接受到数据更新请求 >>>" + dataEvent); } executor.execute(() -> { disruptorService.execute(dataEvent.getBasisData()); }); } @Override public void onEvent(DataEvent dataEvent, long sequence, boolean endOfBatch) throws Exception { if (log.isDebugEnabled()) { log.info("接受到数据更新请求 >>>{}", dataEvent); log.info("Sequence:{}", sequence); log.info("End Of Batch:{}", endOfBatch); } this.onEvent(dataEvent); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/DisruptorExceptionHandler.java ================================================ package com.xiao.springcloud.demo.common.disruptor; import com.lmax.disruptor.ExceptionHandler; import com.xiao.springcloud.demo.common.disruptor.data.DataEvent; import com.xiao.springcloud.demo.common.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; /** * [简要描述]: Disruptor自定义异常处理 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/21 15:42 * @since JDK 1.8 */ @Slf4j public class DisruptorExceptionHandler implements ExceptionHandler { private DisruptorService disruptorService; public DisruptorExceptionHandler(DisruptorService disruptorService) { this.disruptorService = disruptorService; } /** * 事件处理异常 */ @Override public void handleEventException(Throwable throwable, long sequence, DataEvent dataEvent) { //事件处理异常里面进行补偿执行 disruptorService.execute(dataEvent.getBasisData()); log.error(">>> Disruptor事件处理异常,进行立即执行补偿操作.........."); log.error(">>> 异常信息如下:", throwable.getMessage()); } /** * 启动异常 * * @param throwable */ @Override public void handleOnStartException(Throwable throwable) { log.error(">>> Disruptor 启动异常:{}", throwable.getMessage()); } /** * 关闭异常 * * @param throwable */ @Override public void handleOnShutdownException(Throwable throwable) { log.error(">>> Disruptro 关闭异常:{}", throwable.getMessage()); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/DisruptorProducer.java ================================================ package com.xiao.springcloud.demo.common.disruptor; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventTranslatorOneArg; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.WaitStrategy; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.dsl.ProducerType; import com.xiao.springcloud.demo.common.disruptor.data.BasisData; import com.xiao.springcloud.demo.common.disruptor.data.DataEvent; import com.xiao.springcloud.demo.common.disruptor.service.DisruptorService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.util.concurrent.*; /** * [简要描述]: Disruptor生产者 * [详细描述]: * 严重警告:ringBufferSize的值设定需要保持理性的数量,环形队列是等着被替换而不是被回收,设置太大会撑爆你设定的JVM内存,最终导致OLD占满,接下来就是无限循环的FGC * * @author llxiao * @version 1.0, 2019/3/21 15:37 * @since JDK 1.8 */ @Component @Slf4j public class DisruptorProducer implements DisposableBean, ApplicationListener { /** * RingBuffer 大小,必须是 2 的 N 次方; */ private int ringBufferSize = 1024 * 1024; private Disruptor disruptor; private boolean isMultiProducer; /** * 等待策略 * 例如,BlockingWaitStrategy、SleepingWaitStrategy、YieldingWaitStrategy 等,其中, * BlockingWaitStrategy 是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现; * SleepingWaitStrategy 的性能表现跟 BlockingWaitStrategy 差不多,对 CPU 的消耗也类似,但其对生产者线程的影响最小,适合用于异步日志类似的场景; * YieldingWaitStrategy 的性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于 CPU 逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。 */ private WaitStrategy waitStrategy; private RingBuffer ringBuffer; public volatile boolean isStart; private DisruptorService disruptorService; private ExecutorService executor; private int threads; @Autowired public DisruptorProducer(DisruptorService disruptorService) { threads = Runtime.getRuntime().availableProcessors(); this.disruptorService = disruptorService; //单生产者 isMultiProducer = true; ProducerType producerType = ProducerType.SINGLE; if (isMultiProducer) { //多生产者 producerType = ProducerType.MULTI; } // 等待策略 // waitStrategy = new YieldingWaitStrategy(); waitStrategy = new BlockingWaitStrategy(); //初始化disruptor disruptor = new Disruptor<>(new DataEventFactory(), ringBufferSize, DisruptorThreadFactory .create("Disruptor Main-", false), producerType, waitStrategy); executor = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), DisruptorThreadFactory .create("Disruptor Producer-", false), new ThreadPoolExecutor.AbortPolicy()); } /** * [简要描述]:发送事件处理
* [详细描述]:
* * @param basisData : 待处理的数据 * llxiao 2019/3/25 - 15:10 **/ public void send(BasisData basisData) { if (log.isDebugEnabled()) { log.debug("发送数据给消费者"); } if (null != basisData) { executor.execute(() -> { translator(basisData); }); } } private void translator(BasisData basisData) { EventTranslatorOneArg eventTranslatorOneArg = (event, sequence, message1) -> event .setBasisData(basisData); if (!this.isStart) { ringBuffer = disruptor.start(); this.isStart = true; } ringBuffer.publishEvent(eventTranslatorOneArg, basisData); } @Override public void destroy() throws Exception { if (isStart) { //关闭 disruptor,方法会堵塞,直至所有的事件都得到处理; disruptor.shutdown(); } executor.shutdown(); } public void doStart() { //CPU可使用数量 final Executor executor = new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), DisruptorThreadFactory .create("Disruptor consumer-", false), new ThreadPoolExecutor.AbortPolicy()); DisruptorConsumer[] disruptorConsumers = new DisruptorConsumer[threads]; for (int i = 0; i < threads; i++) { disruptorConsumers[i] = new DisruptorConsumer(disruptorService, executor); } //设置消费者Handler // 广播 消费者之间无依赖关系 // disruptor.handleEventsWith(ArrayUtil.toArray(handlers, EventHandler.class)); // 关闭 消费间有依赖关系,假设:2号需要在0号和1号处理完才能进行 // disruptor.handleEventsWith(handlers.get(0),handlers.get(1)).then(handlers.get(2)); // 分组 List workHandlers disruptor.handleEventsWithWorkerPool(disruptorConsumers); //设置异常处理 disruptor.setDefaultExceptionHandler(new DisruptorExceptionHandler(disruptorService)); ringBuffer = disruptor.start(); this.isStart = true; } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { log.info("Start disruptor.........."); doStart(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/DisruptorThreadFactory.java ================================================ package com.xiao.springcloud.demo.common.disruptor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 14:49 * @since JDK 1.8 */ public class DisruptorThreadFactory implements ThreadFactory { private static final AtomicLong THREAD_NUMBER = new AtomicLong(1); private static final ThreadGroup THREAD_GROUP = new ThreadGroup("disruptor"); private static volatile boolean daemon; private final String namePrefix; private DisruptorThreadFactory(final String namePrefix, final boolean daemon) { this.namePrefix = namePrefix; DisruptorThreadFactory.daemon = daemon; } /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(THREAD_GROUP, r, THREAD_GROUP.getName() + "-" + namePrefix + "-" + THREAD_NUMBER.getAndIncrement()); thread.setDaemon(daemon); if (thread.getPriority() != Thread.NORM_PRIORITY) { thread.setPriority(Thread.NORM_PRIORITY); } return thread; } /** * 自定义线程factory * * @param namePrefix * @param daemon * @return */ public static ThreadFactory create(final String namePrefix, final boolean daemon) { return new DisruptorThreadFactory(namePrefix, daemon); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/data/BasisData.java ================================================ package com.xiao.springcloud.demo.common.disruptor.data; import lombok.Getter; import lombok.Setter; /** * [简要描述]: 数据基础类 * [详细描述]: * * @author llxiao * @version 1.0, 2019/6/4 08:54 * @since JDK 1.8 */ @Setter @Getter public class BasisData { /** * 事件名称 */ protected String event; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/data/DataEvent.java ================================================ package com.xiao.springcloud.demo.common.disruptor.data; import lombok.Data; /** * [简要描述]: 定义事件 * [详细描述]:通过 Disruptor 进行交换的数据类型 * * @author llxiao * @version 1.0, 2019/3/21 15:12 * @since JDK 1.8 */ @Data public class DataEvent { /** * 具体数据 */ private BasisData basisData; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/data/EventEnum.java ================================================ package com.xiao.springcloud.demo.common.disruptor.data; /** * [简要描述]: 事件类型 * [详细描述]: * * @author llxiao * @version 1.0, 2019/6/4 08:43 * @since JDK 1.8 */ public enum EventEnum { /** * 日志事件 */ LOG_EVENT("log"); private String event; EventEnum(String event) { this.event = event; } public String getEvent() { return event; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/event/ServiceEvent.java ================================================ package com.xiao.springcloud.demo.common.disruptor.event; import lombok.Getter; import lombok.Setter; import org.springframework.context.ApplicationEvent; /** * [简要描述]: Springboot 的Event数据事件 * [详细描述]: * * @author llxiao * @version 1.0, 2019/6/4 08:39 * @since JDK 1.8 */ @Setter @Getter public class ServiceEvent extends ApplicationEvent { /** * 事件类型 */ private String event; /** * Create a new ApplicationEvent. */ public ServiceEvent(Object source, String event) { super(source); this.event = event; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/readme.md ================================================ 1. Disruptor: 开源的并发框架,能够在无锁的情况下实现网络的Queue并发操作,其他更多[详情介绍](http://ifeve.com/disruptor/) 2. 本common包封装的``Disruptor``与``Spring的Event``事件组合,实现业务在JVM内解耦。
3. 引入disruptor pom依赖:
``` 3.4.2 com.lmax disruptor ${disruptor.version} ``` 4. 启动disruptor:
```html // spring容器初始化时,启动disruptor @Component public class ConfigInit implements InitializingBean { @Autowired private DisruptorProducer disruptorProducer; @Override public void afterPropertiesSet() { disruptorProducer.doStart(); } } ``` 5. 生产者调用:
``DisruptorProducer.send(BasisData data)``方法
```html // 数据数据定义 public ServiceData extends BasisData{ // field // setter and getter } // 注入DisruptorProducer @Autowired private DisruptorProducer disruptorProducer; @Test public void testSend(){ ServiceData data = new ServiceData(); // 设置事件类型,可预定义在EventEnum枚举类中,一个String类型 data.setEvent(EventEnum.LOG_EVENT.getEvent()); // 发送数据 disruptorProducer.send(data); } ``` 6. 业务消费者:
实现``org.springframework.context.ApplicationListener``的``onApplicationEvent(ServiceEvent serviceEvent)``方法
```html // event消费者实现 @Component public class LogEventEvent implements ApplicationListener{ // 可以使用spring的异步实现@Async注解,需要配合启动类中添加 @EnableAsync注解 开启异步的支持 @Async @Override public void onApplicationEvent(ServiceEvent event) { // 这个event事件名称要跟发送的时候事件名称一样的 if (EventEnum.LOG_EVENT.getEvent().equals(event.getEvent())) { ServiceData serviceData = (ServiceData) event.getSource(); // 进一步业务逻辑处理 todo } } } ``` ``严重警告:ringBufferSize的值设定需要保持理性的数量,环形队列是等着被替换而不是被回收,设置太大会撑爆你设定的JVM内存,最终导致OLD占满,接下来就是无限循环的FGC`` ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/service/DisruptorService.java ================================================ package com.xiao.springcloud.demo.common.disruptor.service; import com.xiao.springcloud.demo.common.disruptor.data.BasisData; /** * [简要描述]: 异步队列服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/25 09:50 * @since JDK 1.8 */ public interface DisruptorService { /** * [简要描述]:异步处理服务
* [详细描述]:
* * @param data : 数据处理 * llxiao 2019/3/25 - 9:51 **/ void execute(BasisData data); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/disruptor/service/impl/DisruptorServiceImpl.java ================================================ package com.xiao.springcloud.demo.common.disruptor.service.impl; import com.xiao.springcloud.demo.common.disruptor.data.BasisData; import com.xiao.springcloud.demo.common.disruptor.event.ServiceEvent; import com.xiao.springcloud.demo.common.disruptor.service.DisruptorService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; /** * [简要描述]: 异步处理日志 * [详细描述]: * * @author mjye * @version 1.0, 2019-04-24 15:31 * @since JDK 1.8 */ @Service public class DisruptorServiceImpl implements DisruptorService { @Autowired ApplicationContext applicationContext; /** * [简要描述]: 具体业务处理 * [详细描述]:
* * @param basisData : 待处理数据 * mjye 2019-04-24 - 15:32 **/ @Override public void execute(BasisData basisData) { ServiceEvent event = new ServiceEvent(basisData, basisData.getEvent()); applicationContext.publishEvent(event); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/eureka/LoadBalancerAspect.java ================================================ package com.xiao.springcloud.demo.common.eureka; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerStats; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.stereotype.Component; /** * [简要描述]: 快速标记服务down * [详细描述]: * Eureka注册中心的设计思想基于满足CAP分布式理论中的AP,某一个service宕机,Eureka server有自我保护机制,不会实时踢掉service,
* 默认会等到3个心跳周期也就是90秒,注册中心才会标记该service下线,所以该service的调用方才能被感知到。
* 以下方式可以解决客户端在调用某个服务的provider时实时踢掉该服务,并通知Eureka Server,
* 避免LB机制在Hystirx断路器起作用前尽量避免调用已经宕机的服务提供方
* [参考](https://www.jianshu.com/p/f75d66f6d2cc)
* * @author llxiao * @version 1.0, 2018/12/10 15:48 * @since JDK 1.8 */ @Slf4j @Aspect @Component public class LoadBalancerAspect { @Autowired private SpringClientFactory springClientFactory; @Around(value = "execution (* org.springframework.cloud.client.loadbalancer.LoadBalancerClient.reconstructURI(..)))") public Object reconstructURIAround(final ProceedingJoinPoint joinPoint) throws Throwable { Object[] objects = joinPoint.getArgs(); ServiceInstance instance = (ServiceInstance) objects[0]; Server server = new Server(instance.getHost(), instance.getPort()); RibbonLoadBalancerContext context = springClientFactory.getLoadBalancerContext(instance.getServiceId()); ServerStats serverStats = context.getServerStats(server); Object obj = joinPoint.proceed(); if (log.isDebugEnabled()) { log.debug("======================================================================="); log.debug(serverStats.toString()); log.debug("======================================================================="); } /** * 连续网络链接失败2次以上,迅速标记该provider下线 */ int n = serverStats.getSuccessiveConnectionFailureCount(); if (n > 1) { if (log.isDebugEnabled()) { log.debug("==================================================================="); log.debug("Mark server:{}-{} to down!!!", server.getHost(), server.getPort()); log.debug("==================================================================="); } context.getLoadBalancer().markServerDown(server); } return obj; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/exception/AbstractServiceException.java ================================================ package com.xiao.springcloud.demo.common.exception; /** * 异常规范接口 * @author zhdong * */ public interface AbstractServiceException { /** * 获取异常的状态码 */ Integer getCode(); /** * 获取异常的提示信息 */ String getMessage(); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/exception/CommonException.java ================================================ package com.xiao.springcloud.demo.common.exception; /** * 异常公共类 * * @author zhdong * @date 2018/8/1 */ public class CommonException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; /** * 缺少必填参数,三位错误码后缀,需要结合前缀的业务编码组装成完整的错误码信息 */ public static final String REQUIRED_PARAM_SUFFIX = "000"; /** * 参数非法,三位错误码后缀,需要结合前缀的业务编码组装成完整的错误码信息 */ public static final String ILLEGAL_PARAM_SUFFIX = "001"; private Integer code; private String errorMessage; public CommonException(Integer code, String errorMessage) { super(errorMessage); this.code = code; this.errorMessage = errorMessage; } public CommonException(AbstractServiceException exception) { super(exception.getMessage()); this.code = exception.getCode(); this.errorMessage = exception.getMessage(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } public static CommonException throwEx(AbstractServiceException e) { throw new CommonException(e); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/exception/CommonExceptionEnum.java ================================================ package com.xiao.springcloud.demo.common.exception; /** * 异常公共类 * * @author zhdong */ public enum CommonExceptionEnum implements AbstractServiceException { SYSTEM_ERROR(10000, "系统错误,请联系管理员"), TOKEN_HAS_EXPIRED(10010, "token已过期"), REMOTE_SERVICE_NULL(10020, "远程调用错误"), REMOTE_SERVICE_TIMEOUT(10021, "请求超时"), SERVICE_ERROR(10030, "服务内部错误"), IO_ERROR(10050, "读取IO异常"), NO_FOUNT(10031, "访问路径不存在"), NO_LOGIN(10060, "没有登录或者登录超时"); CommonExceptionEnum(Integer code, String message) { this.code = code; this.message = message; } private Integer code; private String message; @Override public Integer getCode() { return code; } @Override public String getMessage() { return message; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/forkjoin/ForkjoinConfiguration.java ================================================ package com.xiao.springcloud.demo.common.forkjoin; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ForkJoinPool; /** * [简要描述]: fork join 配置 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/14 16:14 * @since JDK 1.8 */ @Configuration public class ForkjoinConfiguration implements DisposableBean { private ForkJoinPool forkJoinPool; @Bean public ForkJoinPool forkJoinPool() { ForkJoinPool forkJoinPool = new ForkJoinPool(); this.forkJoinPool = forkJoinPool; return forkJoinPool; } /** * Invoked by a BeanFactory on destruction of a singleton. * * @exception Exception in case of shutdown errors. * Exceptions will get logged but not rethrown to allow * other beans to release their resources too. */ @Override public void destroy() throws Exception { if (null != forkJoinPool) { forkJoinPool.shutdown(); } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/forkjoin/ForkjoinService.java ================================================ package com.xiao.springcloud.demo.common.forkjoin; import com.xiao.springcloud.demo.common.forkjoin.task.ForkjoinTask; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.ForkJoinPool; /** * [简要描述]: fork join执行数组加操作 demo * [详细描述]: * * @author llxiao * @version 1.0, 2019/6/11 20:02 * @since JDK 1.8 */ @Service public class ForkjoinService { @Autowired private ForkJoinPool forkJoinPool; public Integer addNumbers(List numbers) { ForkjoinTask forkjoinTask = new ForkjoinTask(numbers, 0, numbers.size()); Integer count = forkJoinPool.invoke(forkjoinTask); return count; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/forkjoin/task/ForkjoinTask.java ================================================ package com.xiao.springcloud.demo.common.forkjoin.task; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.RecursiveTask; /** * [简要描述]: fork join demo操作 * [详细描述]: * * @author llxiao * @version 1.0, 2019/6/11 19:55 * @since JDK 1.8 */ public class ForkjoinTask extends RecursiveTask { /** * 待处理的平台商品数据 */ private CopyOnWriteArrayList arrayList; /** * 拆分条件 */ private static int THRESHOLD = 500; private int start; private int end; public ForkjoinTask(List arrayList, int start, int end) { this.arrayList = new CopyOnWriteArrayList<>(arrayList); this.start = start; this.end = end; } /** * The main computation performed by this task. * * @return the result of the computation */ @Override protected Integer compute() { if (end - start <= THRESHOLD) { return add(arrayList); } else { int middle = (end + start) / 2; List arrayList1 = arrayList.subList(start, middle); List arrayList2 = arrayList.subList(middle, end); ForkjoinTask forkjoinTask1 = new ForkjoinTask(arrayList1, 0, arrayList1.size()); ForkjoinTask forkjoinTask2 = new ForkjoinTask(arrayList2, 0, arrayList2.size()); invokeAll(forkjoinTask1, forkjoinTask2); return forkjoinTask1.join() + forkjoinTask2.join(); } } private Integer add(CopyOnWriteArrayList arrayList) { int status = 0; for (Integer integer : arrayList) { status += integer; } return status; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/README.md ================================================ springcloud fegin内部交互异常统一处理 原理: 1. 服务端抛出统一异常,由DefaultControllerAdvice方法捕捉封装,期需要将http response的响应头设置为500 2. 客户端自定义解码器,对异常进行解码,由CommonFeignErrorDecoder进行对异常处理 服务端客户端交互部分对象传输为空: 自定义httpmessageconvert,由FastjsonConfig类处理,添加fastjson转换 ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/advice/DefaultControllerAdvice.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.advice; import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.springcloud.demo.common.exception.CommonExceptionEnum; import com.xiao.springcloud.demo.common.gloab.response.ErrorResponseData; import com.xiao.springcloud.demo.common.gloab.response.ResponseData; import feign.RetryableException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import javax.servlet.http.HttpServletResponse; /** * 全局的的异常拦截器(拦截所有的控制器) * (带有@RequestMapping注解的方法上都会拦截) * * @author zhdong */ @Slf4j @ControllerAdvice public class DefaultControllerAdvice { @Autowired private ObjectMapper objectMapper; /** * 拦截common异常 */ @ExceptionHandler(CommonException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public ResponseData serviceException(CommonException e) { log.error("系统异常:", e); return new ErrorResponseData(e.getCode(), e.getErrorMessage()); } /** * 拦截common异常 */ @ExceptionHandler(HystrixRuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public ResponseData hystrixRuntimeException(HystrixRuntimeException e) { log.error("系统异常:", e); Throwable cause = e.getCause(); //return new ErrorResponseData(e.getCode(), e.getErrorMessage()); if (cause instanceof CommonException) { return serviceException((CommonException) cause); } cause = e.getFallbackException(); if (null != cause) { log.error("服务调用熔断异常:", cause); // 解决服务之间调用,自定义熔断内抛出的异常处理 if (null != cause.getCause()) { Throwable e1 = cause.getCause().getCause(); if (null != e1 && e1 instanceof CommonException) { return serviceException((CommonException) e1); } } } return new ErrorResponseData(CommonExceptionEnum.REMOTE_SERVICE_NULL .getCode(), CommonExceptionEnum.REMOTE_SERVICE_NULL.getMessage()); } /** * 拦截RetryableException异常 */ @ExceptionHandler(RetryableException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public ResponseData retryableException(RetryableException e) { log.error("系统异常:", e); return new ErrorResponseData(CommonExceptionEnum.REMOTE_SERVICE_TIMEOUT .getCode(), CommonExceptionEnum.REMOTE_SERVICE_TIMEOUT.getMessage()); } /** * 拦截未知的运行时异常 */ @ExceptionHandler(Exception.class) @ResponseBody public ResponseData notFount(Exception e, HttpServletResponse reponse) { if (HttpStatus.NOT_FOUND.value() == reponse.getStatus()) { log.error(CommonExceptionEnum.NO_FOUNT.getMessage(), e); return new ErrorResponseData(CommonExceptionEnum.NO_FOUNT.getCode(), CommonExceptionEnum.NO_FOUNT .getMessage()); } log.error(CommonExceptionEnum.SYSTEM_ERROR.getMessage(), e); // 设置httpresponse头为500信息 reponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); return new ErrorResponseData(CommonExceptionEnum.SYSTEM_ERROR.getCode(), CommonExceptionEnum.SYSTEM_ERROR .getMessage()); } // @Override // public boolean supports(MethodParameter methodParameter, Class aClass) { // return true; // } // // /** // * 封装返回结果 // * @param returnValue // * @param methodParameter // * @param mediaType // * @param aClass // * @param serverHttpRequest // * @param serverHttpResponse // * @return // */ // @Override // public Object beforeBodyWrite(Object returnValue, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { // // //如果已经是ResponseData,直接返回 // if ( returnValue instanceof ResponseData) { // return returnValue; // } // else if ( returnValue instanceof String ){ // try { // return objectMapper.writeValueAsString(SuccessResponseData.success(returnValue)); // }catch (Exception e){ // log.error("返回结果转换json异常",e); // return ErrorResponseData.error("返回结果转换json异常"); // } // } // // return SuccessResponseData.success(returnValue); // } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/config/FastjsonConfig.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.config; import com.alibaba.fastjson.parser.ParserConfig; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * fastjson配置类 * * @author zhdong */ @Configuration("defaultFastjsonConfig") @ConditionalOnClass(com.alibaba.fastjson.JSON.class) @ConditionalOnMissingBean(FastJsonHttpMessageConverter.class) @ConditionalOnWebApplication public class FastjsonConfig { @Bean public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); converter.setFastJsonConfig(fastjsonConfig()); converter.setSupportedMediaTypes(getSupportedMediaType()); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); return converter; } /** * fastjson的配置 */ public FastJsonConfig fastjsonConfig() { FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures( //SerializerFeature.PrettyFormat, //格式化输出 ,仅限调试使用 SerializerFeature.WriteMapNullValue, //是否输出值为null的字段,默认为false SerializerFeature.DisableCircularReferenceDetect, //消除循环引用 SerializerFeature.WriteNullListAsEmpty //List字段如果为null,输出为[],而非null ); fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastJsonConfig.setCharset(Charset.forName("utf-8")); //不需要将null转换为空字符 // ValueFilter valueFilter = (object, name, value) -> { // return null == value ? "" : value; // }; // fastJsonConfig.setSerializeFilters(valueFilter); return fastJsonConfig; } /** * 支持的mediaType类型 */ public List getSupportedMediaType() { ArrayList mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.APPLICATION_JSON); mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); mediaTypes.add(MediaType.APPLICATION_ATOM_XML); mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); mediaTypes.add(MediaType.APPLICATION_PDF); mediaTypes.add(MediaType.APPLICATION_RSS_XML); mediaTypes.add(MediaType.APPLICATION_XHTML_XML); mediaTypes.add(MediaType.APPLICATION_XML); mediaTypes.add(MediaType.IMAGE_GIF); mediaTypes.add(MediaType.IMAGE_JPEG); mediaTypes.add(MediaType.IMAGE_PNG); mediaTypes.add(MediaType.TEXT_EVENT_STREAM); mediaTypes.add(MediaType.TEXT_HTML); mediaTypes.add(MediaType.TEXT_MARKDOWN); mediaTypes.add(MediaType.TEXT_PLAIN); mediaTypes.add(MediaType.TEXT_XML); //增加解析spring boot actuator结果的解析 mediaTypes.add(MediaType.valueOf("application/vnd.spring-boot.actuator.v2+json")); return mediaTypes; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/config/FeignConfig.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.config; import com.xiao.springcloud.demo.common.gloab.interceptor.fegin.CommonFeignErrorDecoder; import com.xiao.springcloud.demo.common.gloab.interceptor.fegin.CommonFeignHeaderProcessInterceptor; import com.xiao.springcloud.demo.common.gloab.interceptor.fegin.DefaultCommonErrorAttributes; import feign.RequestInterceptor; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * feign的错误编码配置(为了feign接收到错误的返回,转化成roses可识别的ServiceException) * * @author zhdong * @Date 2018/8/30 11:11 */ @Configuration public class FeignConfig { /** * roses自定义错误解码器 */ // @Bean // @Scope("prototype") // public Feign.Builder feignHystrixBuilder() { // return HystrixFeign.builder().errorDecoder(new CommonFeignErrorDecoder()); // } @Bean public ErrorDecoder errorDecoder(){ return new CommonFeignErrorDecoder(); } /** * feign请求加上当前请求接口的headers */ @Bean public RequestInterceptor requestInterceptor() { return new CommonFeignHeaderProcessInterceptor(); } /** * 覆盖spring默认的响应消息格式 */ @Bean public DefaultCommonErrorAttributes defaultRosesErrorAttributes() { return new DefaultCommonErrorAttributes(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/fegin/CommonFeignErrorDecoder.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.fegin; import cn.hutool.core.io.IoUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.springcloud.demo.common.exception.CommonExceptionEnum; import feign.Response; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; import java.io.IOException; /** * 错误解码器 * * @author zhdong */ @Slf4j public class CommonFeignErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { log.error("调用服务返回Response:" + response); String responseBody; try { if (response == null || response.body() == null) { log.error("未得到服务端的响应结果..."); if (response != null && response.status() == 404) { return new CommonException(CommonExceptionEnum.REMOTE_SERVICE_NULL); } else { return new CommonException(CommonExceptionEnum.SERVICE_ERROR); } } responseBody = IoUtil.read(response.body().asInputStream(), "UTF-8"); } catch (IOException e) { log.error("读取Response响应io错误", e); return new CommonException(CommonExceptionEnum.IO_ERROR); } log.error("服务提供方返回异常结果为:" + responseBody); JSONObject parse = JSON.parseObject(responseBody); Integer code = parse.getInteger("code"); String message = parse.getString("message"); if (message == null) { message = CommonExceptionEnum.SERVICE_ERROR.getMessage(); } if (code == null) { //status为spring默认返回的数据 Integer status = parse.getInteger("status"); if (status == null) { return new CommonException(CommonExceptionEnum.SERVICE_ERROR.getCode(), message); } else { return new CommonException(status, message); } } else { return new CommonException(code, message); } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/fegin/CommonFeignHeaderProcessInterceptor.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.fegin; import feign.RequestInterceptor; import feign.RequestTemplate; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; /** * feign远程调用添加header的过滤器 * * @author zhdong */ public class CommonFeignHeaderProcessInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { //当前feign远程调用环境不是由http接口发起,例如test单元测试中的feign调用或者项目启动后的feign调用 HttpServletRequest request = null; try { request = HttpContext.getRequest(); } catch (NullPointerException e) { //被调环境中不存在request对象,则不往header里添加当前请求环境的header return; } if (request != null) { Enumeration headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String values = request.getHeader(name); requestTemplate.header(name, values); } } } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/fegin/DefaultCommonErrorAttributes.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.fegin; import cn.hutool.core.bean.BeanUtil; import com.xiao.springcloud.demo.common.exception.CommonExceptionEnum; import com.xiao.springcloud.demo.common.gloab.response.ResponseData; import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes; import org.springframework.web.context.request.RequestAttributes; import java.util.Map; /** * 重写spring得默认响应提示信息 * * @author zhdong * @Date 2018/8/30 23:14 */ public class DefaultCommonErrorAttributes extends DefaultErrorAttributes { @Override public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { return BeanUtil.beanToMap(ResponseData.error(CommonExceptionEnum.SERVICE_ERROR.getMessage())); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/fegin/FeignBeanFactoryPostProcessor.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.fegin; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; import java.util.Arrays; /** * [简要描述]: 解决junit测试完毕销毁FeignContext 异常问题 * [详细描述]: 异常信息:BeanCreationNotAllowedException: Error creating bean with name 'eurekaAutoServic'.... * git 参考地址:https://github.com/spring-cloud/spring-cloud-netflix/issues/1952 * * @author llxiao * @version 1.0, 2018/9/11 15:19 * @since JDK 1.8 */ @Component public class FeignBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (containsBeanDefinition(beanFactory, "feignContext", "eurekaAutoServiceRegistration")) { BeanDefinition bd = beanFactory.getBeanDefinition("feignContext"); bd.setDependsOn("eurekaAutoServiceRegistration"); } } private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String... beans) { return Arrays.stream(beans).allMatch(b -> beanFactory.containsBeanDefinition(b)); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/interceptor/fegin/HttpContext.java ================================================ package com.xiao.springcloud.demo.common.gloab.interceptor.fegin; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 快捷获取HttpServletRequest,HttpServletResponse * * @author zhdong */ public class HttpContext { public static String getIp() { return HttpContext.getRequest().getRemoteHost(); } public static HttpServletResponse getResponse() throws NullPointerException { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); } public static HttpServletRequest getRequest() throws NullPointerException { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); return request; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/response/ErrorResponseData.java ================================================ package com.xiao.springcloud.demo.common.gloab.response; /** * 请求失败的返回 * * @author zhdong * @Date 2018/8/1 */ public class ErrorResponseData extends ResponseData { public ErrorResponseData() { } public ErrorResponseData(String message) { super(false, DEFAULT_ERROR_CODE, message, null); } public ErrorResponseData(Integer code, String message) { super(false, code, message, null); } public ErrorResponseData(Integer code, String message, Object object) { super(false, code, message, object); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/response/ResponseData.java ================================================ package com.xiao.springcloud.demo.common.gloab.response; import java.io.Serializable; /** * 返回给前台的通用包装 * * @author zhdong * @Date 2018/8/1 */ public class ResponseData implements Serializable { public static final String DEFAULT_SUCCESS_MESSAGE = "请求成功"; public static final String DEFAULT_ERROR_MESSAGE = "网络异常"; public static final Integer DEFAULT_SUCCESS_CODE = 200; public static final Integer DEFAULT_ERROR_CODE = 500; /** * 请求是否成功 */ private Boolean success; /** * 响应状态码 */ private Integer code; /** * 响应信息 */ private String message; /** * 响应对象 */ private Object data; public ResponseData() { } public ResponseData(Boolean success, Integer code, String message, Object data) { this.success = success; this.code = code; this.message = message; this.data = data; } public Boolean getSuccess() { return success; } public void setSuccess(Boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public static SuccessResponseData success() { return new SuccessResponseData(null); } public static SuccessResponseData success(Object data) { return new SuccessResponseData(data); } public static SuccessResponseData success(Integer code, String message, Object data) { return new SuccessResponseData(code, message, data); } public static ErrorResponseData error(String message) { return new ErrorResponseData(message); } public static ErrorResponseData error(Integer code, String message) { return new ErrorResponseData(code, message); } public static ErrorResponseData error(Integer code, String message, Object object) { return new ErrorResponseData(code, message, object); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/gloab/response/SuccessResponseData.java ================================================ package com.xiao.springcloud.demo.common.gloab.response; /** * 请求成功的返回 * * @author zhdong * @Date 2018/8/1 */ public class SuccessResponseData extends ResponseData { public SuccessResponseData() { } public SuccessResponseData(Object object) { super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, object); } public SuccessResponseData(Integer code, String message, Object object) { super(true, code, message, object); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/LogAnnotation.java ================================================ package com.xiao.springcloud.demo.common.logaspect; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * [简要描述]: 自定义日志注解
* [详细描述]: 既可以在方法上注解,也可以在类上进行注解
* * @author llxiao * @version 1.0, 2018/9/2 16:46 * @since JDK 1.8 */ @Retention(RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Inherited//说明子类可以继承父类中的该注解 @Target(ElementType.METHOD)//既可以在方法上,也可以在类上 @Documented//说明该注解将被包含在javadoc中 public @interface LogAnnotation { String value() default ""; /** * [简要描述]:自定义注解,需要添加一些额外的日志记录
* [详细描述]:
* * @return java.lang.String * llxiao 2018/9/2 - 16:50 **/ String customer() default ""; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/LogAspect.java ================================================ package com.xiao.springcloud.demo.common.logaspect; import com.alibaba.fastjson.JSON; import com.xiao.springcloud.demo.common.exception.CommonException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.StopWatch; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.Timestamp; /** * [简要描述]: 切面日志处理类
* [详细描述]: * * @author llxiao * @version 1.0, 2018/9/2 16:52 * @since JDK 1.8 */ @Aspect @Component public class LogAspect { /** * 日志服务 */ @Autowired private LogService logService; /** * [简要描述]:定义一个annotation切入点
* [详细描述]:切入点
* llxiao 2018/9/2 - 17:02 **/ @Pointcut("@annotation(com.xiao.springcloud.demo.common.logaspect.LogAnnotation)") public void logAnnotatison() { } // around 切面强化 @Around("logAnnotatison()") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { LogInfo logInfo = new LogInfo(); logInfo.setRequestTime(new Timestamp(System.currentTimeMillis())); //通知签名 Signature signature = joinPoint.getSignature(); //代理的是哪一个方法 String methodName = signature.getName(); //AOP代理类的名字 String className = joinPoint.getTarget().getClass().getName(); logInfo.setClsName(className); logInfo.setMethodName(methodName); //获取方法参数 Object[] args = joinPoint.getArgs(); logInfo.setParams(JSON.toJSONString(args)); Object retrunobj = null; //计时工具 StopWatch clock = new StopWatch(); Throwable tempE = null; clock.start(); try { // 注意和finally中的执行顺序 finally是在return中的计算结束返回前执行 retrunobj = joinPoint.proceed(args); } catch (Throwable e) { tempE = e; throw e; } finally { boolean isError = setResultAndError(joinPoint, logInfo, retrunobj, clock, tempE); logInfo.setResponseTime(new Timestamp(System.currentTimeMillis())); setHostInfo(logInfo); if (isError) { //记录日志 logService.error(JSON.toJSONString(logInfo), tempE); } else { logService.info(JSON.toJSONString(logInfo)); } } return retrunobj; } /** * [简要描述]:设置一些网络参数设置
* [详细描述]:
* * @param logInfo : * @return void * llxiao 2018/9/20 - 9:30 **/ private void setHostInfo(LogInfo logInfo) { // 本机IP信息 try { InetAddress local = InetAddress.getLocalHost(); if (null != local) { logInfo.setServerHost(local.getHostName()); logInfo.setServerIp(local.getHostAddress()); } } catch (UnknownHostException e) { logService.warn("UnknownHostException"); } // 请求IP信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); if (null != request) { logInfo.setClientIp(request.getRemoteAddr()); logInfo.setClientHost(request.getRemoteHost()); logInfo.setRequestUri(request.getRequestURI()); logInfo.setClientPort(request.getRemotePort()); logInfo.setServerPort(request.getLocalPort()); } } /** * [简要描述]:记录日志
* [详细描述]:
* * @param joinPoint : * @param logInfo : * @param retrunobj : * @param clock : * @param tempE : * @return true 未知异常 * llxiao 2018/9/8 - 9:53 **/ private boolean setResultAndError(ProceedingJoinPoint joinPoint, LogInfo logInfo, Object retrunobj, StopWatch clock, Throwable tempE) { clock.stop(); logInfo.setResult(JSON.toJSONString(retrunobj)); logInfo.setCostTime(clock.getTime()); MethodSignature ms = (MethodSignature) joinPoint.getSignature(); //从切面中获取当前方法 Method method = ms.getMethod(); //得到了方,提取出他的注解 LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class); String customerInfo = logAnnotation.customer(); //自定义的一些消息 if (StringUtils.isNotBlank(customerInfo)) { logInfo.setRemark(customerInfo); } if (null != tempE) { logInfo.setStatus(LogInfo.FAILED); // 业务异常处理不需要打印堆栈信息 if (tempE instanceof CommonException) { CommonException ce = (CommonException) tempE; logInfo.setErrorCode(ce.getCode()); logInfo.setErrorMsg(ce.getErrorMessage()); } else { // 未知异常打印堆栈信息 logInfo.setErrorMsg(tempE.getMessage()); return true; } } return false; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/LogInfo.java ================================================ package com.xiao.springcloud.demo.common.logaspect; import com.alibaba.fastjson.annotation.JSONField; import lombok.Data; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.sql.Timestamp; /** * [简要描述]: * [详细描述]: fast.JSON.tostring时timestamp会转成long类型的,需要指定JSONField格式 * * @author llxiao * @version 1.0, 2018/9/20 09:53 * @since JDK 1.8 */ @Data public class LogInfo { //失败 public static final int FAILED = 1; //类名 private String clsName; //方法名 private String methodName; //请求参数 private String params; //返回值 private String result; //调用花费时间 ms private Long costTime; //其他信息 private String remark; //请求时间 @JSONField(format = "yyyy-MM-dd HH:mm:ss") private Timestamp requestTime; //响应时间 @JSONField(format = "yyyy-MM-dd HH:mm:ss") private Timestamp responseTime; //成功状态。0成功,1失败 private int status; //错误码 private int errorCode; //错误消息 private String errorMsg; //网络请求信息 private String serverIp; private String serverHost; private int serverPort; private String clientIp; private String clientHost; private int clientPort; private String requestUri; @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/LogService.java ================================================ package com.xiao.springcloud.demo.common.logaspect; /** * [简要描述]: 日志服务
* [详细描述]: * * @author llxiao * @version 1.0, 2018/9/2 16:53 * @since JDK 1.8 */ public interface LogService { /** * [简要描述]:debug日志记录
* [详细描述]:
* * @param debugMsg : debug日志 * @return void * llxiao 2018/9/2 - 16:57 **/ void debug(String debugMsg); /** * [简要描述]:info 日志记录
* [详细描述]:
* * @param message : 日志信息 * @return void * llxiao 2018/9/2 - 16:55 **/ void info(String message); /** * [简要描述]:error 日志记录
* [详细描述]:
* * @param errorMsg : 错误消息 * @param e : 异常 * @return void * llxiao 2018/9/2 - 16:55 **/ void error(String errorMsg, Throwable e); /** * 警告日志 * * @param message */ void warn(String message); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/README.md ================================================ 1. 在需要记录日志的方法上添加 ``@LogAnnotation`` 注解即可 2. 实现原理:
> 主要利用annotation注解+aop方式
> 具体实现见:``LogAspect``类
3. 日志记录采用``LogService`` 接口,当前底层实现为slf4j,可自定义扩展 4. 结合ekl+kafka案例,[参考](https://github.com/Xlinlin/spring-cloud-demo/tree/master/SpringCloud-Kafka-Elk) ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/logaspect/Slf4jLogService.java ================================================ package com.xiao.springcloud.demo.common.logaspect; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * [简要描述]: slf4j日志记录 * [详细描述]: * * @author llxiao * @version 1.0, 2018/9/2 16:58 * @since JDK 1.8 */ @Service @Slf4j public class Slf4jLogService implements LogService { /** * [简要描述]:debug日志记录
* [详细描述]:
* * @param debugMsg : debug日志 * llxiao 2018/9/2 - 16:57 **/ @Override public void debug(String debugMsg) { if (log.isDebugEnabled()) { log.debug(debugMsg); } } /** * [简要描述]:info 日志记录
* [详细描述]:
* * @param message : 日志信息 * llxiao 2018/9/2 - 16:55 **/ @Override public void info(String message) { if (log.isInfoEnabled()) { log.info(message); } } /** * 警告日志 * * @param message */ @Override public void warn(String message) { log.warn(message); } /** * [简要描述]:error 日志记录
* [详细描述]:
* * @param errorMsg : 错误消息 * @param e : 异常 * llxiao 2018/9/2 - 16:55 **/ @Override public void error(String errorMsg, Throwable e) { log.error(errorMsg, e); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/SignConstants.java ================================================ package com.xiao.springcloud.demo.common.sign; /** * [简要描述]: * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 18:22 * @since JDK 1.8 */ public interface SignConstants { String GET_METHOD = "GET"; String POST_METHOD = "POST"; String ISO_8859_1 = "ISO-8859-1"; String UTF_8 = "UTF-8"; String JSON_TYPE = "application/json"; /*******header params********/ String SIGN_NAME = "sign"; String APP_ID = "appid"; /** * 服务端计算出来的签名参数 */ String SEVER_SIGN = "serverSign"; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/annotation/DisposeSign.java ================================================ package com.xiao.springcloud.demo.common.sign.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * [简要描述]: * [详细描述]: * * @author mjye * @version 1.0, 2020/2/17 23:35 * @since JDK 1.8 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface DisposeSign { /** * 是否验签 * * @return 默认验签 */ boolean isVerifySign() default true; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/annotation/DisposeSignService.java ================================================ package com.xiao.springcloud.demo.common.sign.annotation; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.springcloud.demo.common.sign.SignConstants; import com.xiao.springcloud.demo.common.sign.service.AppManagerService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * [简要描述]: * [详细描述]: * * @author mjye * @version 1.0, 2020/2/17 23:39 * @since JDK 1.8 */ @Slf4j @Aspect @Component public class DisposeSignService { @Autowired private AppManagerService appManagerService; @Pointcut("@annotation(omni.purcotton.omni.inface.center.common.sign.annotation.DisposeSign)") public void requestAnnotation() { } @Around("requestAnnotation() && @annotation(disposeSign)") public Object execute(ProceedingJoinPoint joinPoint, DisposeSign disposeSign) throws Throwable { Object[] args = joinPoint.getArgs(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); final HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); if (null == request) { // 无reqeust直接放行 return joinPoint.proceed(args); } // 签名校验 if (disposeSign.isVerifySign()) { String uri = request.getRequestURI(); // 获取当前的请求头中的签名 Map header = this.assembleHeader(request); String clientSign = String.valueOf(header.get(SignConstants.SIGN_NAME)); if (StringUtils.isBlank(clientSign)) { log.error("请求服务器URL:{}缺少签名参数!", uri); throw new CommonException(1002, "缺少签名参数"); } String appId = String.valueOf(header.get(SignConstants.APP_ID)); if (StringUtils.isBlank(appId)) { log.error("请求服务器URL:{}缺少AppId参数!", uri); throw new CommonException(1002, "缺少appid参数!"); } // 暂时保留 ///Long timestamp = (Long) header.get(TIMESTAMP); if (appManagerService.exist(appId)) { String serverSign = (String) request.getAttribute(SignConstants.SEVER_SIGN); if (!clientSign.equals(serverSign)) { log.error("Appid:{} 请求服务器URL: {},请求签名错误!", appId, uri); log.error("客户端签名:{},服务端签名:{}", clientSign, serverSign); throw new CommonException(1000, "签名错误!"); } } else { log.error("请求服务器URL:{},App ID: {}非法请求", uri, appId); throw new CommonException(1001, "请求非法,无appid"); } return joinPoint.proceed(args); } else { // 不需要签名校验,放行 return joinPoint.proceed(args); } } private Map assembleHeader(HttpServletRequest request) { Map header = new HashMap<>(16); Enumeration enumeration = request.getHeaderNames(); while (enumeration.hasMoreElements()) { String name = enumeration.nextElement(); String value = request.getHeader(name); header.put(name, value); } return header; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/filter/WrapperRequestFilter.java ================================================ package com.xiao.springcloud.demo.common.sign.filter; import com.xiao.springcloud.demo.common.sign.SignConstants; import com.xiao.springcloud.demo.common.sign.request.BodyReaderHttpServletRequestWrapper; import com.xiao.springcloud.demo.common.sign.service.AppManagerService; import com.xiao.springcloud.demo.common.sign.util.HttpRequestUtils; import com.xiao.springcloud.demo.common.sign.util.SignUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * [简要描述]: 包装reqeust使用 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 16:49 * @since JDK 1.8 */ @Component @Slf4j @Order(1) public class WrapperRequestFilter implements Filter { @Autowired private AppManagerService appManagerService; /** * Called by the web container to indicate to a filter that it is being * placed into service. The servlet container calls the init method exactly * once after instantiating the filter. The init method must complete * successfully before the filter is asked to do any filtering work. *

* The web container cannot place the filter into service if the init method * either: *

    *
  • Throws a ServletException
  • *
  • Does not return within a time period defined by the web * container
  • *
* * @param filterConfig The configuration information associated with the * filter instance being initialised * @exception ServletException if the initialisation fails */ @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * The doFilter method of the Filter is called by the container * each time a request/response pair is passed through the chain due to a * client request for a resource at the end of the chain. The FilterChain * passed in to this method allows the Filter to pass on the request and * response to the next entity in the chain. *

* A typical implementation of this method would follow the following * pattern:-
* 1. Examine the request
* 2. Optionally wrap the request object with a custom implementation to * filter content or headers for input filtering
* 3. Optionally wrap the response object with a custom implementation to * filter content or headers for output filtering
* 4. a) Either invoke the next entity in the chain using * the FilterChain object (chain.doFilter()),
* 4. b) or not pass on the request/response pair to the * next entity in the filter chain to block the request processing
* 5. Directly set headers on the response after invocation of the next * entity in the filter chain. * * @param request The request to process * @param response The response associated with the request * @param chain Provides access to the next filter in the chain for this * filter to pass the request and response to for further * processing * @exception IOException if an I/O error occurs during this filter's * processing of the request * @exception ServletException if the processing fails for any other reason */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 防止流读取一次后就没有了, 所以需要将流继续写出去 HttpServletRequest req = (HttpServletRequest) request; String appid = req.getHeader(SignConstants.APP_ID); String sign = req.getHeader(SignConstants.SIGN_NAME); if (StringUtils.isNotBlank(appid) && StringUtils.isNotBlank(sign)) { String appKey = appManagerService.getAppKey(appid); if (StringUtils.isNotEmpty(appKey)) { // 计算服务端的签名 HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(req); String params = HttpRequestUtils.getAllParams(requestWrapper); log.info("获取到的参数是===> 【{}】", params); log.info("获取到的KEY ====> 【{}】", appKey); if (StringUtils.isNotBlank(params)) { String serverSign = SignUtil.generateSign(params, appKey); requestWrapper.setAttribute(SignConstants.SEVER_SIGN, serverSign); chain.doFilter(requestWrapper, response); return; } } } chain.doFilter(request, response); } /** * Called by the web container to indicate to a filter that it is being * taken out of service. This method is only called once all threads within * the filter's doFilter method have exited or after a timeout period has * passed. After the web container calls this method, it will not call the * doFilter method again on this instance of the filter.
*
*

* This method gives the filter an opportunity to clean up any resources * that are being held (for example, memory, file handles, threads) and make * sure that any persistent state is synchronized with the filter's current * state in memory. */ @Override public void destroy() { } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/readme.md ================================================ 提供生成签名串工具类
依赖包: ```html commons-lang3 commons-collections commons-codec slf4j-api fastjson ``` 使用方法方法:
```html SignUtil.generateSign(Map kvParams, String key) SignUtil.generateSign(Map kvParams, String jsonParams, String key) SignUtil.generateSignByJson(String jsonParams,String key) SignUtil.generateSign(String content,String key) ``` 签名说明: 1. KV参数拼接 2. JSON请求参数,内部会做JSONObject转换,转成json字符串 3. 将kv参数和json参数拼在一起,按ascii排序规则进行排序 4. 对排序后的字符串做HmacSHA1加密 Springboot web应用签名:
1. 包装一个request读取request流数据费 2. 自定义一个filter,处理request中header的sign和appid以及请求参数,通过appid找到对应的appkey通过签名工具生成服务端签名,放到reqeust周往下传 3. 自定义一个注解@DisposeSign,需要签名的接口加上该注解即可 4. AppManagerService管理appid和appKey对应的关系 ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/request/BodyReaderHttpServletRequestWrapper.java ================================================ package com.xiao.springcloud.demo.common.sign.request; import com.xiao.springcloud.demo.common.sign.SignConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; /** * [简要描述]: 包装reqeust 复制下request流,防止在controller层获取不到reqeust body情况 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 16:38 * @since JDK 1.8 */ @Slf4j public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; /** * Constructs a request object wrapping the given request. * * @param request The request to wrap * @exception IllegalArgumentException if the request is null */ public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) { super(request); String sessionStream = getBodyString(request); // 使用utf-8编码格式 body = sessionStream.getBytes(Charset.forName(SignConstants.UTF_8)); } /** * 获取请求Body * * @param request * @return */ public String getBodyString(final ServletRequest request) { String encode = request.getCharacterEncoding(); if (StringUtils.isBlank(encode)) { encode = SignConstants.UTF_8; } StringBuilder sb = new StringBuilder(); try (InputStream inputStream = cloneInputStream(request.getInputStream()); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(encode)))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { log.error("Request流包装发生错误,", e); } return sb.toString(); } /** * Description: 复制输入流
* * @param inputStream */ public InputStream cloneInputStream(ServletInputStream inputStream) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; try { while ((len = inputStream.read(buffer)) > -1) { byteArrayOutputStream.write(buffer, 0, len); } byteArrayOutputStream.flush(); } catch (IOException e) { log.error("Request流复制发生错误,", e); } return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); } @Override public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/service/AppManagerService.java ================================================ package com.xiao.springcloud.demo.common.sign.service; /** * [简要描述]: 第三方App权限管理 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 09:23 * @since JDK 1.8 */ public interface AppManagerService { /** * [简要描述]:appId是否存在
* [详细描述]:
* * @param appId : * @return boolean * xiaolinlin 2020/2/21 - 9:24 **/ boolean exist(String appId); /** * [简要描述]:appId获取对应的appKey
* [详细描述]:
* * @param appId : * @return java.lang.String * xiaolinlin 2020/2/21 - 9:24 **/ String getAppKey(String appId); /** * [简要描述]:添加一个app应用信息
* [详细描述]:
* * @param appId : * @param appKey : * @return boolean * xiaolinlin 2020/2/21 - 9:25 **/ boolean addApp(String appId, String appKey); } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/service/impl/AppManagerServiceConfigImpl.java ================================================ package com.xiao.springcloud.demo.common.sign.service.impl; import com.xiao.springcloud.demo.common.sign.service.AppManagerService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.concurrent.ConcurrentHashMap; /** * [简要描述]: * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 09:27 * @since JDK 1.8 */ @Service public class AppManagerServiceConfigImpl implements AppManagerService, InitializingBean { /** * 本地内存APP info实现 */ private ConcurrentHashMap appInfoMap = new ConcurrentHashMap<>(); /** * appInfo配置实现
* 格式:appId:appKey,appId2:appKey2 */ @Value("${interface.auth.app.info:1001:abcdefg111}") private String appInfo; /** * [简要描述]:appId是否存在
* [详细描述]:
* * @param appId : * @return boolean * xiaolinlin 2020/2/21 - 9:24 **/ @Override public boolean exist(String appId) { return appInfoMap.containsKey(appId); } /** * [简要描述]:appId获取对应的appKey
* [详细描述]:
* * @param appId : * @return java.lang.String * xiaolinlin 2020/2/21 - 9:24 **/ @Override public String getAppKey(String appId) { return appInfoMap.get(appId); } /** * [简要描述]:添加一个app应用信息
* [详细描述]:
* * @param appId : * @param appKey : * @return boolean * xiaolinlin 2020/2/21 - 9:25 **/ @Override public boolean addApp(String appId, String appKey) { appInfoMap.put(appKey, appKey); return true; } /** * Invoked by a BeanFactory after it has set all bean properties supplied * (and satisfied BeanFactoryAware and ApplicationContextAware). *

This method allows the bean instance to perform initialization only * possible when all bean properties have been set and to throw an * exception in the event of misconfiguration. * * @exception Exception in the event of misconfiguration (such * as failure to set an essential property) or if initialization fails. */ @Override public void afterPropertiesSet() throws Exception { if (StringUtils.isNotBlank(appInfo)) { // 初始化appiId KV配置 String[] appInfos = appInfo.split(","); for (String app : appInfos) { String[] appKv = app.split(":"); if (appKv.length == 2) { appInfoMap.put(appKv[0], appKv[1]); } } } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/util/AsciiSortUtil.java ================================================ package com.xiao.springcloud.demo.common.sign.util; import org.apache.commons.lang3.StringUtils; /** * [简要描述]: ascii码排序工具 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 10:56 * @since JDK 1.8 */ public class AsciiSortUtil { /** * [简要描述]:对String进行ASCII码排序
* [详细描述]:
* * @param str : * @return java.lang.String * xiaolinlin 2020/2/21 - 11:00 **/ public static String sort(String str) { if (StringUtils.isNotBlank(str)) { return new String(sort(str.toCharArray())); } return ""; } /** * [简要描述]:对char[] 进行ascii码排序
* [详细描述]:
* * @param arr : * @return char[] * xiaolinlin 2020/2/21 - 11:01 **/ public static char[] sort(char[] arr) { //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间 char[] temp = new char[arr.length]; sort(arr, 0, arr.length - 1, temp); return temp; } private static void sort(char[] arr, int left, int right, char[] temp) { if (left < right) { int mid = (left + right) / 2; //递归的方法 //左边归并排序,使得左子序列有序 sort(arr, left, mid, temp); //右边归并排序,使得右子序列有序 sort(arr, mid + 1, right, temp); //将两个有序子数组合并操作 merge(arr, left, mid, right, temp); } } private static void merge(char[] arr, int left, int mid, int right, char[] temp) { //左序列指针 int i = left; //右序列指针 int j = mid + 1; //临时数组指针 int t = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[t++] = arr[i++]; } else { temp[t++] = arr[j++]; } } while (i <= mid) { //将左边剩余元素填充进temp中 temp[t++] = arr[i++]; } while (j <= right) { //将右序列剩余元素填充进temp中 temp[t++] = arr[j++]; } t = 0; //将temp中的元素全部拷贝到原数组中 while (left <= right) { arr[left++] = temp[t++]; } } public static void main(String[] args) { String str = "98uihsadfend.;lpsdf[{};sk]['"; System.out.println(sort(str)); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/util/HttpRequestUtils.java ================================================ package com.xiao.springcloud.demo.common.sign.util; import com.alibaba.fastjson.JSONObject; import com.xiao.springcloud.demo.common.sign.SignConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpMethod; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.Enumeration; /** * [简要描述]: * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 18:35 * @since JDK 1.8 */ @Slf4j public class HttpRequestUtils { /** * [简要描述]:获取request中所有参数,get,post,json,form
* [详细描述]:
* * @param request : * @return java.lang.String * xiaolinlin 2020/2/21 - 18:59 **/ public static String getAllParams(HttpServletRequest request) throws IOException { //获取URL上的参数 String urlParams = ""; // get请求不需要拿body参数 if (!HttpMethod.GET.name().equals(request.getMethod())) { String contentType = request.getContentType(); if (SignConstants.JSON_TYPE.equals(contentType)) { urlParams += getJsonParams(request); } String formParams = getFormParams(request); if (StringUtils.isNotBlank(formParams)) { urlParams += getFormParams(request); } } else { urlParams = getUrlParams(request); } return urlParams; } /** * [简要描述]:获取表单参数
* [详细描述]:
* * @param request : * @return java.lang.String * xiaolinlin 2020/2/21 - 18:59 **/ private static String getFormParams(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String pName = parameterNames.nextElement(); String pValue = request.getParameter(pName); sb.append(pName); sb.append(pValue); } return sb.toString(); } /** * [简要描述]:获取JSON消息体参数
* [详细描述]:
* * @param request : * @return java.lang.String * xiaolinlin 2020/2/21 - 19:00 **/ public static String getJsonParams(final HttpServletRequest request) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); String str = ""; StringBuilder jsonSb = new StringBuilder(); //一行一行的读取body体里面的内容; while ((str = reader.readLine()) != null) { jsonSb.append(str); } return JSONObject.parseObject(jsonSb.toString()).toJSONString(); } /** * [简要描述]:获取url参数
* [详细描述]:
* * @param request : * @return java.lang.String * xiaolinlin 2020/2/21 - 19:00 **/ public static String getUrlParams(HttpServletRequest request) { String characterEncoding = request.getCharacterEncoding(); if (StringUtils.isBlank(characterEncoding)) { characterEncoding = SignConstants.UTF_8; } String param = ""; try { param = URLDecoder.decode(request.getQueryString(), characterEncoding); } catch (UnsupportedEncodingException e) { log.error("不支持的编码格式!"); } StringBuilder urlParams = new StringBuilder(); String[] params = param.split("&"); for (String s : params) { int index = s.indexOf("="); urlParams.append(s.substring(0, index)); urlParams.append(s.substring(index + 1)); } return urlParams.toString(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/sign/util/SignUtil.java ================================================ package com.xiao.springcloud.demo.common.sign.util; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; /** * [简要描述]: 签名工具 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/2/21 11:04 * @since JDK 1.8 */ public class SignUtil { private static final Logger logger = LoggerFactory.getLogger(SignUtil.class); /** * HmacSHA1 */ private static final String HMAC_SHA1 = "HmacSHA1"; /** * [简要描述]:键值对参数生产签名串
* [详细描述]:
* * @param kvParams : 兼职对参数 * @param key : * @return java.lang.String * xiaolinlin 2020/2/24 - 18:48 **/ public static String generateSign(Map kvParams, String key) { if (MapUtils.isNotEmpty(kvParams) && StringUtils.isNotBlank(key)) { StringBuilder paramSb = new StringBuilder(); kvParams.forEach((k, v) -> paramSb.append(k + v)); return generateSign(paramSb.toString(), key); } return ""; } /** * [简要描述]:JSON和键值对参数合并
* [详细描述]:
* * @param kvParams : * @param jsonParams : * @return java.lang.String * xiaolinlin 2020/2/24 - 18:50 **/ public static String generateSign(Map kvParams, String jsonParams, String key) { if (StringUtils.isNotBlank(key)) { StringBuilder paramSb = new StringBuilder(); if (MapUtils.isNotEmpty(kvParams)) { kvParams.forEach((k, v) -> paramSb.append(k + v)); } if (StringUtils.isNotBlank(jsonParams)) { try { final JSONObject jsonObject = JSONObject.parseObject(jsonParams); paramSb.append(jsonObject.toJSONString()); } catch (JSONException e) { logger.error("生成签名串的JSON请求参数错误,不能转成JSON格式!", e); } } return generateSign(paramSb.toString(), key); } return ""; } /** * [简要描述]:json参数处理
* [详细描述]:
* * @param jsonParams : * @param key : * @return java.lang.String * xiaolinlin 2020/2/24 - 18:50 **/ public static String generateSignByJson(String jsonParams, String key) { if (StringUtils.isNotBlank(jsonParams)) { try { final JSONObject jsonObject = JSONObject.parseObject(jsonParams); return generateSign(jsonObject.toJSONString(), key); } catch (JSONException e) { logger.error("生成签名串的JSON请求参数错误,不能转成JSON格式!", e); } } return ""; } /** * [简要描述]:生成签名串
* [详细描述]:
* * @param content : 原始内容 * @param key : 签名key * @return java.lang.String * xiaolinlin 2020/2/21 - 11:05 **/ public static String generateSign(String content, String key) { if (StringUtils.isBlank(content) || StringUtils.isBlank(key)) { return ""; } final String sort = AsciiSortUtil.sort(content); return hmacEncode(sort, key); } /** * [简要描述]:hmac加密算法
* [详细描述]:
* * @param plainText 明文 * @param key 密钥 * @return 加密后的密文为 apache base64字符串 */ private static String hmacEncode(String plainText, String key) { Charset charset = StandardCharsets.UTF_8; Mac mac; String sign = ""; try { mac = Mac.getInstance(HMAC_SHA1); mac.init(new SecretKeySpec(key.getBytes(charset), HMAC_SHA1)); byte[] bytes = mac.doFinal(plainText.getBytes(charset)); sign = new String(Base64.encodeBase64(bytes), charset); } catch (NoSuchAlgorithmException | InvalidKeyException e) { logger.error("macSignature error"); } return sign; } public static void main(String[] args) { String json = "{\t\"storeNo\":\"1001\",\t\"productDtoList\":[\t{\t\"id\":\"12\",\t\"stock\":12,\t\"shopCode\":\"J001\",\t\"productCode\":\"\"\t},\t{\t\"id\":\"12\",\t\"shopCode\":\"J001\",\t\"productCode\":\"\"\t}\t]}"; Map kvParmas = new HashMap<>(); kvParmas.put("productName", "test11111"); String key = "abcdefg111"; System.out.println(generateSign(kvParmas, key)); System.out.println(generateSign(kvParmas, json, key)); System.out.println(generateSignByJson(json, key)); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/CodeFormatConstants.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :CodeForamtConstants.java * 创建人 :xiaolinlin * 创建时间:2017年2月21日 */ package com.xiao.springcloud.demo.common.util; /** * [简要描述]:常用编码格式常量池
* [详细描述]:常用编码格式常量池
* * @author xiaolinlin * @version 1.0, 2017年2月21日 * @since smile V100R001C00 */ public interface CodeFormatConstants { /** * GBK编码方式 */ String CODE_FORMAT_GBK = "GBK"; /** * UTF_8编码方式 */ String CODE_FORMAT_UTF_8 = "UTF-8"; /** * ISO8859-1编码方式 */ String CODE_FORMAT_ISO = "ISO8859-1"; /** * GBK2312编码方式 */ String CODE_FORMAT_GBK2312 = "GBK2312"; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/DateUtils.java ================================================ package com.xiao.springcloud.demo.common.util; import java.util.Calendar; import java.util.Date; /** * [简要描述]: 日期工具类 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/6 16:52 * @since JDK 1.8 */ public class DateUtils { /** * [简要描述]:获取下一天的0点时间·
* [详细描述]:
* * @return long * llxiao 2019/11/5 - 23:46 **/ public static Date getNextDay() { Calendar calendar = Calendar.getInstance(); calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar .get(Calendar.DAY_OF_MONTH), 0, 0, 0); calendar.add(Calendar.DAY_OF_MONTH, 1); return calendar.getTime(); } /** * [简要描述]:获取当天最后的时间:23:59:59
* [详细描述]:
* * @return java.util.Date * llxiao 2019/11/7 - 19:49 **/ public static Date getDayEnd() { Calendar calendar = Calendar.getInstance(); calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar .get(Calendar.DAY_OF_MONTH), 23, 59, 59); return calendar.getTime(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/ListPageUtil.java ================================================ package com.xiao.springcloud.demo.common.util; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * [简要描述]: List 分页工具 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/1/15 09:38 * @since JDK 1.8 */ public final class ListPageUtil { private CopyOnWriteArrayList list; private int pageSize = 0; private int pageCount = 0; private int listSize = 0; private int index = 0; /** * [简要描述]:List分页工具初始化
* [详细描述]:
* * @param list : 待分页的数据集 * @param pageSize : 每页数 * @return xiaolinlin 2020/1/15 - 10:02 **/ public ListPageUtil(List list, int pageSize) { if (null == list || 0 == list.size()) { throw new UnsupportedOperationException("List can not null!"); } if (0 == pageSize) { throw new UnsupportedOperationException("Page size can not 0"); } this.list = new CopyOnWriteArrayList<>(list); this.listSize = list.size(); if (pageSize > listSize) { this.pageSize = listSize; } else { this.pageSize = pageSize; } this.pageCount = listSize / pageSize; this.pageCount = listSize % pageSize == 0 ? pageCount : pageCount + 1; this.index = 0; } /** * [简要描述]:获取首页
* [详细描述]:
* * @return java.util.List * xiaolinlin 2020/1/15 - 10:02 **/ public List getFistPage() { return list.subList(0, pageSize); } /** * [简要描述]:获取最后一页
* [详细描述]:
* * @return java.util.List * xiaolinlin 2020/1/15 - 10:03 **/ public List getLastPage() { return list.subList((pageCount - 1) * pageSize, listSize); } /** * [简要描述]:获取下一页
* [详细描述]:
* * @return java.util.List * xiaolinlin 2020/1/15 - 10:03 **/ public List nextPage() { List subList = null; if (index == pageCount - 1) { subList = list.subList(index * pageSize, listSize); } else { subList = list.subList(index * pageSize, (index + 1) * pageSize); } index++; return subList; } /** * [简要描述]:是否有下一页
* [详细描述]:
* * @return boolean * xiaolinlin 2020/1/15 - 10:03 **/ public boolean hasNext() { return index < pageCount; } /** * [简要描述]:获取总页数
* [详细描述]:
* * @return int * xiaolinlin 2020/1/15 - 10:03 **/ public int getPageCount() { return pageCount; } /** * [简要描述]:获取每页数
* [详细描述]:
* * @return int * xiaolinlin 2020/1/15 - 10:03 **/ public int getPageSize() { return pageSize; } /** * [简要描述]:获取当前页数
* [详细描述]:
* * @return int * xiaolinlin 2020/1/15 - 10:03 **/ public int getIndex() { return index + 1; } /** * [简要描述]:获取带分页的数据总数
* [详细描述]:
* * @return int * xiaolinlin 2020/1/15 - 10:04 **/ public int getListSize() { return listSize; } public static void main(String[] args) { List list = new ArrayList<>(); int length = 23; for (int i = 0; i < length; i++) { list.add(i); } int pageSize = 30; System.out.println(list); ListPageUtil listPageUtil = new ListPageUtil<>(list, pageSize); System.out.println("List size: " + listPageUtil.getListSize()); System.out.println("Page count: " + listPageUtil.getPageCount()); System.out.println("Page size: " + listPageUtil.getPageSize()); System.out.println("First page: " + listPageUtil.getFistPage()); System.out.println("Last page: " + listPageUtil.getLastPage()); while (listPageUtil.hasNext()) { System.out.println("Page index: " + listPageUtil.getIndex()); System.out.println("Next page: " + listPageUtil.nextPage()); } } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/StringLengthUtils.java ================================================ package com.xiao.springcloud.demo.common.util; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.UnsupportedEncodingException; /** * [简要描述]: 字符串长度计算 * [详细描述]: * * @author xiaolinlin * @version 1.0, 2020/1/14 15:50 * @since JDK 1.8 */ @Slf4j public class StringLengthUtils { private static final int UNICODE_LENGTH = 3; private static final int GBK_LENGTH = 2; private static final String GBK = "GBK"; private static final String UTF_8 = "UTF-8"; /** * [简要描述]:获取字符长度
* [详细描述]:
* 1.如果字符串为空null或""返回0
* 2.如果code为空,返回string.length
* 3.如果code编码格式不对,返回string.length
* 4.否则返回 string.getbyte(code).length
* * @param st : 字符串 * @param code : 编码格式 * @return int * xiaolinlin 2020/1/14 - 15:54 **/ public static int getLength(String st, String code) { int length = 0; if (StringUtils.isNotBlank(st)) { if (StringUtils.isNotBlank(code)) { try { length = st.getBytes(code).length; } catch (UnsupportedEncodingException e) { log.error("编码格式不对:", e); } } if (0 == length) { length = st.length(); } } return length; } /** * [简要描述]:获取utf-8格式下的长度
* [详细描述]:
* * @param st : * @return int * xiaolinlin 2020/1/14 - 15:59 **/ public static int getLengthByUtf(String st) { return getLength(st, UTF_8); } /** * [简要描述]:获取gbk编码格式下的长度
* [详细描述]:
* * @param str : * @return int * xiaolinlin 2020/1/14 - 15:59 **/ public static int getLengthByGbk(String str) { return getLength(str, GBK); } /** * [简要描述]:utf8截取指定长度字符串
* [详细描述]:
* 1. str为null或"",返回""
* 2. length==0,返回str
* 3. length > str.getBytes(UTF-8).length,返回str
* 4. 正常截取
* * @param str : 带截取的字符串 * @param length : 截取长度 * @return java.lang.String * xiaolinlin 2020/1/14 - 16:38 **/ public static String subByUtf8(String str, int length) { if (StringUtils.isBlank(str)) { return ""; } if (0 == length) { return str; } String subStr = ""; try { byte[] buf = str.getBytes(UTF_8); if (length > buf.length) { return str; } int count = 0; int i = 0; for (i = length - 1; i >= 0; i--) { if (buf[i] < 0) { count++; } } //因為UTF-8三個字節表示一個漢字 if (count % UNICODE_LENGTH == 0) { subStr = new String(buf, 0, length, UTF_8); } else if (count % UNICODE_LENGTH == 1) { subStr = new String(buf, 0, length - 1, UTF_8); } else if (count % UNICODE_LENGTH == GBK_LENGTH) { subStr = new String(buf, 0, length - 2, UTF_8); } } catch (UnsupportedEncodingException e) { log.error("UTF-8截取字符串长度失败,不支持的编码格式!"); } return subStr; } /** * [简要描述]:utf8截取指定长度字符串
* [详细描述]:
* 1. str为null或"",返回""
* 2. length==0,返回str
* 3. length > str.getBytes(GBK).length,返回str
* 4. 正常截取
* * @param str : 带截取的字符串 * @param length : 截取长度 * @return java.lang.String * xiaolinlin 2020/1/14 - 16:38 **/ public static String subByGbk(String str, int length) { if (StringUtils.isBlank(str)) { return ""; } if (0 == length) { return str; } String subStr = ""; try { byte[] buf = str.getBytes(GBK); if (length > buf.length) { return str; } int count = 0; int i = 0; for (i = length - 1; i >= 0; i--) { if (buf[i] < 0) { count++; } else { break; } } //因為GBK用兩個字節表示一個漢字 if (count % GBK_LENGTH == 0) { subStr = new String(buf, 0, length, GBK); } else { subStr = new String(buf, 0, length - 1, GBK); } } catch (UnsupportedEncodingException e) { log.error("不支持的编码格式!"); } return subStr; } public static void main(String[] args) throws Exception { String str = "新春礼盒(新生礼盒)2 459x394x60mm 组合套装"; String gbkStr = subByGbk(str, 40); System.out.println(gbkStr); String utfStr = subByUtf8(str, 40); System.out.println(utfStr); System.out.println(StringLengthUtils.getLengthByGbk(gbkStr)); System.out.println(StringLengthUtils.getLengthByUtf(utfStr)); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/AESEncryption.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :AESEncryption.java * 创建人 :xiaolinlin * 创建时间:2017年3月13日 */ package com.xiao.springcloud.demo.common.util.encode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; /** * [简要描述]:AES加解密类
* [详细描述]:美国软件出口限制,JDK默认使用的AES算法最高只能支持128位。如需要更高的支持需要从oracle官网下载更换JAVA_HOME/jre/lib/ * security目录下的: local_policy.jar和US_export_policy.jar。
* 采用补码方式以及base64双重加密,依赖commons-codec-1.x.jar包中的base64加密
* * @author xiaolinlin * @version 1.0, 2017年3月13日 * @since smile V100R001C00 */ @Slf4j public class AESEncryption { /** * 加密器 */ private static Cipher cipher; /** * 初始化向量 */ private static IvParameterSpec iv; static { try { byte[] vi = Hex.decodeHex("12345678123456781234567812345678".toCharArray()); iv = new IvParameterSpec(vi); // "算法/模式/补码方式" cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); } catch (NoSuchAlgorithmException e) { log.error("NoSuchAlgorithmException"); } catch (NoSuchPaddingException e) { log.error("NoSuchPaddingException"); } catch (DecoderException e) { log.error("DecoderException"); } } /** * 生成密钥 * * @param type AES长度 * @return 密钥 */ public static String createAESKey(AESType type) { try { KeyGenerator key = KeyGenerator.getInstance("AES"); key.init(type.value); SecretKey ckey = key.generateKey(); byte[] keyByte = ckey.getEncoded(); return ByteUtils.parseByte2HexStr(keyByte); } catch (NoSuchAlgorithmException e) { log.error("Create aes Key error!"); } return ""; } /** * AES加密 * * @param key 密钥 * @param plaintext 明文 * @return 秘文 */ public static String encryptAES(String key, String plaintext) { String ciphertext = ""; try { byte[] keyByte = ByteUtils.parseHexStr2Byte(key); SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES"); // 使用CBC模式,需要一个向量iv,可增加加密算法的强度 cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte[] pbyte = plaintext.getBytes("utf-8"); byte[] result = cipher.doFinal(pbyte); ciphertext = ByteUtils.parseByte2HexStr(result); } catch (Exception e) { log.error("Aes encrypt error", e); } return ciphertext; } /** * AES解密 * * @param key 密钥 * @param ciphertext 秘文 * @return 明文 */ public static String decryptAES(String key, String ciphertext) { String plaintext = ""; try { byte[] keyByte = ByteUtils.parseHexStr2Byte(key); byte[] cbyte = ByteUtils.parseHexStr2Byte(ciphertext); SecretKeySpec skeySpec = new SecretKeySpec(keyByte, "AES"); // 使用CBC模式,需要一个向量iv,可增加加密算法的强度 cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] pbyte = cipher.doFinal(cbyte); plaintext = new String(pbyte, "utf-8"); } catch (Exception e) { log.error("AES decrypt error!", e); } return plaintext; } public static void main(String[] args) { String key = AESEncryption.createAESKey(AESType.AES_128); System.out.println(key); String ciphertext = AESEncryption.encryptAES(key, "Basisuser123"); System.out.println(ciphertext); System.out.println(AESEncryption.decryptAES(key, ciphertext)); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/AESType.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :AESType.java * 创建人 :xiaolinlin * 创建时间:2017年3月13日 */ package com.xiao.springcloud.demo.common.util.encode; /** * [简要描述]:AES加密类型枚举
* [详细描述]:
* * @author xiaolinlin * @version 1.0, 2017年3月13日 * @since smile V100R001C00 */ public enum AESType { AES_128(128), AES_192(192), AES_256(256); /** * 初始化值 */ public int value; AESType(int value) { this.value = value; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/BinaryHelper.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :BinaryHelper.java * 创建人 :xiaolinlin * 创建时间:2017年2月20日 */ package com.xiao.springcloud.demo.common.util.encode; import org.apache.commons.codec.binary.Base64; /** * [简要描述]:二进制转换工具类
* [详细描述]:二进制转16进制字符串、二进制转apache的base64字符串,字符串转二进制
* * @author xiaolinlin * @version 1.0, 2017年2月20日 * @since smile V100R001C00 */ public class BinaryHelper { /** * [简要描述]:二进制转字符串
* [详细描述]:二进制转字符串
* * @author xiaolinlin * @param array 二进制数组 * @return 字符串 */ public static String array2Str(byte[] array) { if (null == array) { return ""; } StringBuffer sb = new StringBuffer(32); for (int i = 0; i < array.length; i++) { sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3)); } return sb.toString(); } /** * [简要描述]:二进制转base64字符串
* [详细描述]:二进制转base64字符串
* * @author xiaolinlin * @param array 二进制数组 * @return base64字符串 */ public static String array2Base64Str(byte[] array) { return Base64.encodeBase64String(array); } /** * [简要描述]:符合apache commons 格式的base64字符串转数组
* [详细描述]:
* * @author xiaolinlin * @param base64Str 符合apache commons 格式的base64字符串 * @return 二进制数组 */ public static byte[] base642Array(String base64Str) { return Base64.decodeBase64(base64Str); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/ByteUtils.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :ByteUtils.java * 创建人 :xiaolinlin * 创建时间:2017年6月9日 */ package com.xiao.springcloud.demo.common.util.encode; /** * [简要描述]:
* [详细描述]:
* * @author xiaolinlin * @version 1.0, 2017年6月9日 * @since smile V100R001C00 */ public class ByteUtils { /** * 将16进制转换为二进制 * * @param hexStr * @return */ public static byte[] parseHexStr2Byte(String hexStr) { if (hexStr.length() < 1) { return null; } byte[] result = new byte[hexStr.length() / 2]; for (int i = 0; i < hexStr.length() / 2; i++) { int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); result[i] = (byte) (high * 16 + low); } return result; } /** * 将二进制转换成16进制 * * @param buf * @return */ public static String parseByte2HexStr(byte buf[]) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < buf.length; i++) { String hex = Integer.toHexString(buf[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex.toUpperCase()); } return sb.toString(); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/HMACUtil.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :HMACUtil.java * 创建人 :xiaolinlin * 创建时间:2017年4月20日 */ package com.xiao.springcloud.demo.common.util.encode; import com.xiao.springcloud.demo.common.util.CodeFormatConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; /** * [简要描述]:hamc加密算法
* [详细描述]:
* * @author xiaolinlin * @version 1.0, 2017年4月20日 * @since smile V100R001C00 */ @Slf4j public class HMACUtil { /** * HmacSHA1 */ private static final String HMAC_SHA1 = "HmacSHA1"; /** * HmacSHA256 */ private static final String HMAC_SHA256 = "HmacSHA256"; /** * [简要描述]:hmacSHA1加密算法,加密后的密文为 apache base64字符串
* [详细描述]:
* * @param plainText 明文 * @param key 密钥 * @return 密文 */ public static String hmacSHA1Encode(String plainText, String key) { return hmacEncode(plainText, key, HMAC_SHA1); } /** * [简要描述]:hmacSHA1加密算法,加密后的密文为 apache base64字符串
* [详细描述]:
* * @param plainText 明文 * @param key 密钥 * @return 密文 */ public static String hmacSHA256Encode(String plainText, String key) { return hmacEncode(plainText, key, HMAC_SHA256); } /** * [简要描述]:hmac加密算法
* [详细描述]:
* * @param plainText 明文 * @param key 密钥 * @param shaByte 加密算法,如HmacSHA1、HmacSHA256 * @return 加密后的密文为 apache base64字符串 */ private static String hmacEncode(String plainText, String key, String shaByte) { String ciphertext = ""; try { SecretKeySpec signingKey = new SecretKeySpec(key .getBytes(CodeFormatConstants.CODE_FORMAT_UTF_8), HMAC_SHA1); Mac mac = Mac.getInstance(shaByte); mac.init(signingKey); byte[] rawHmac = mac.doFinal(plainText.getBytes(CodeFormatConstants.CODE_FORMAT_UTF_8)); ciphertext = Base64.encodeBase64String(rawHmac); } catch (UnsupportedEncodingException e) { log.error("UnsupportedEncodingException"); } catch (NoSuchAlgorithmException e) { log.error("NoSuchAlgorithmException"); } catch (InvalidKeyException e) { log.error("InvalidKeyException", e); } return ciphertext; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/encode/Md5DigestUtil.java ================================================ /* * Smile Software Technologies Co., Ltd. Copyright 2015-2017, All rights reserved. * 文件名 :Md5DigestUtil.java * 创建人 :xiaolinlin * 创建时间:2017年2月20日 */ package com.xiao.springcloud.demo.common.util.encode; import com.xiao.springcloud.demo.common.util.CodeFormatConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * [简要描述]:md5工具类
* [详细描述]:
* * @author xiaolinlin * @version 1.0, 2017年2月20日 * @since smile V100R001C00 */ @Slf4j public class Md5DigestUtil { /** * MD5摘要算法 */ private static final String MD5 = "MD5"; /** * [简要描述]:md5加密算法,生成base64字符串
* [详细描述]:
* * @param plaintext 明文 * @return md5密文 */ public static String md5Encoding2Base64(String plaintext) { return md5Encoding(plaintext, true); } /** * [简要描述]:md5加密算法,生成普通字符串
* [详细描述]:
* * @param plaintext 明文 * @return md5密文 */ public static String md5Encoding(String plaintext) { return md5Encoding(plaintext, false); } /** * [简要描述]:MD5摘要生成工具
* [详细描述]:MD5摘要生成工具
* * @param plaintext 明文 * @param isBase64 是否生成base64字符串 * @return md5密文 */ public static String md5Encoding(String plaintext, boolean isBase64) { if (StringUtils.isBlank(plaintext)) { return ""; } String ciphertext = ""; MessageDigest md; try { md = MessageDigest.getInstance(MD5); byte[] array = md.digest(plaintext.getBytes(CodeFormatConstants.CODE_FORMAT_UTF_8)); if (isBase64) { ciphertext = BinaryHelper.array2Base64Str(array); } else { ciphertext = BinaryHelper.array2Str(array); } } catch (NoSuchAlgorithmException e) { log.error("MD5 encoding error and error msg :NoSuchAlgorithmException!"); } catch (UnsupportedEncodingException e) { log.error("MD5 encoding error and error msg :UnsupportedEncodingException!"); } return ciphertext; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/image/ImageDHashUtil.java ================================================ package com.xiao.springcloud.demo.common.util.image; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * [简要描述]: 差异值哈希算法 * [详细描述]: * 1.图片缩放为9*8大小 * 2.将图片灰度化 * 3.差异值计算(每行相邻像素的差值,这样会生成8*8的差值,前一个像素大于后一个像素则为1,否则为0) * 4.生成哈希值 * 5.计算海明距离,越小越相似 * https://blog.csdn.net/lx83350475/article/details/84189848 * * @author llxiao * @version 1.0, 2019/9/29 10:55 * @since JDK 1.8 */ public class ImageDHashUtil { /** * 计算dHash方法 * * @param file 文件 * @return hash */ private static String getDHash(File file) { //读取文件 BufferedImage srcImage; try { srcImage = ImageIO.read(file); } catch (IOException e) { e.printStackTrace(); return null; } //文件转成9*8像素,为算法比较通用的长宽 BufferedImage buffImg = new BufferedImage(9, 8, BufferedImage.TYPE_INT_RGB); buffImg.getGraphics().drawImage(srcImage.getScaledInstance(9, 8, Image.SCALE_SMOOTH), 0, 0, null); int width = buffImg.getWidth(); int height = buffImg.getHeight(); int[][] grayPix = new int[width][height]; StringBuffer figure = new StringBuffer(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { //图片灰度化 int rgb = buffImg.getRGB(x, y); int r = rgb >> 16 & 0xff; int g = rgb >> 8 & 0xff; int b = rgb & 0xff; int gray = (r * 30 + g * 59 + b * 11) / 100; grayPix[x][y] = gray; //开始计算dHash 总共有9*8像素 每行相对有8个差异值 总共有 8*8=64 个 if (x != 0) { long bit = grayPix[x - 1][y] > grayPix[x][y] ? 1 : 0; figure.append(bit); } } } return figure.toString(); } /** * 计算海明距离 *

* 原本用于编码的检错和纠错的一个算法 * 现在拿来计算相似度,如果差异值小于一定阈值则相似,一般经验值小于5为同一张图片 * * @param str1 * @param str2 * @return 距离 */ private static long getHammingDistance(String str1, String str2) { int distance; if (str1 == null || str2 == null || str1.length() != str2.length()) { distance = -1; } else { distance = 0; for (int i = 0; i < str1.length(); i++) { if (str1.charAt(i) != str2.charAt(i)) { distance++; } } } return distance; } //DHashUtil 参数值为待处理文件夹 public static void main(String[] args) { File image0 = new File("D:/image/4200014361-000/_m_01.jpg"); File image1 = new File("D:/image/4200014361-000/_m_01-1.jpg"); File image2 = new File("D:/image/4200014361-000/_m_02.jpg"); System.out.println("图片1hash值:" + getDHash(image0)); System.out.println("图片2hash值:" + getDHash(image1)); System.out.println("图片3hash值:" + getDHash(image2)); System.out.println("图1-2海明距离为:" + getHammingDistance(getDHash(image0), getDHash(image1))); System.out.println("图2-3海明距离为:" + getHammingDistance(getDHash(image1), getDHash(image2))); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/util/image/ImagePHashUtil.java ================================================ package com.xiao.springcloud.demo.common.util.image; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.io.FileInputStream; import java.io.InputStream; import java.net.URL; /** * [简要描述]: 基于感知哈希算法的pHash图像配准算法 * [详细描述]: * pHash的工作过程如下 * (1)缩小尺寸:pHash以小图片开始,但图片大于8*8,32*32是最好的。这样做的目的是简化了DCT的计算,而不是减小频率。 * (2)简化色彩:将图片转化成灰度图像,进一步简化计算量。 * (3)计算DCT:计算图片的DCT变换,得到32*32的DCT系数矩阵。 * (4)缩小DCT:虽然DCT的结果是32*32大小的矩阵,但我们只要保留左上角的8*8的矩阵,这部分呈现了图片中的最低频率。 * (5)计算平均值:如同均值哈希一样,计算DCT的均值。 * (6)计算hash值:这是最主要的一步,根据8*8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1”,小于DCT均值的设为“0”。组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。 * ———————————————— * 版权声明:本文为CSDN博主「No Silver Bullet」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 * 原文链接:https://blog.csdn.net/sunhuaqiang1/article/details/70232679 * * @author llxiao * @version 1.0, 2019/9/29 10:58 * @since JDK 1.8 */ @Slf4j public class ImagePHashUtil { private int size = 32; private int smallerSize = 8; /** * 匹配度阀值,两者图片的海明值低于指定值则说明为同一张图片 */ private int matchThreshold = 5; /** * 是否需要校验图片,默认需要校验 */ private boolean check = true; private ColorConvertOp colorConvert = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); public ImagePHashUtil() { initCoefficients(); } /** * [简要描述]:
* [详细描述]:
* * @param matchThreshold : 匹配度阀值,两者图片的海明值低于指定值则说明为同一张图片 * @param check : 是否需要校验图片 * @return llxiao 2019/9/29 - 15:18 **/ public ImagePHashUtil(int matchThreshold, boolean check) { this.matchThreshold = matchThreshold; this.check = check; this.initCoefficients(); } public static void main(String[] args) { ImagePHashUtil p = new ImagePHashUtil(10, false); String basicPath = "D:/image/4200014361-000/"; String imagePath1 = basicPath + "_m_01.jpg"; String imagePath2 = basicPath + "_m_01-1.jpg"; String imagePath3 = basicPath + "_m_02.jpg"; System.out.println("本地文件对比1:" + p.matchImage4Path(imagePath1, imagePath2)); System.out.println("本地文件对比2:" + p.matchImage4Path(imagePath2, imagePath3)); String imageUrl1 = ""; String imageUrl2 = ""; String imageUrl3 = ""; System.out.println("=====URL图片对比1:" + p.matchImage4Url(imageUrl1, imageUrl2)); System.out.println("=====URL图片对比2:" + p.matchImage4Url(imageUrl3, imageUrl2)); } /** * 判断网络的两张图片相似度比较 * * @param img1Url: 图片1下载地址 * @param img2Url:图片2下载地址 * @return boolean */ public boolean matchImage4Url(String img1Url, String img2Url) { if (StringUtils.isBlank(img1Url) || StringUtils.isBlank(img2Url)) { log.error("对比的图片路径不能为空!"); return false; } try { return this.matchImage4Stream(new URL(img1Url).openConnection().getInputStream(), new URL(img2Url) .openConnection().getInputStream()); } catch (Exception e) { log.error("图片对比出现异常,", e); } return false; } /** * 两张图片相似度比较 * * @param img1Path: 图片1地址 * @param img2Path:图片2地址 * @return boolean */ public boolean matchImage4Path(String img1Path, String img2Path) { if (StringUtils.isBlank(img1Path) || StringUtils.isBlank(img2Path)) { log.error("对比的图片路径不能为空!"); return false; } try { return this.matchImage4Stream(new FileInputStream(img1Path), new FileInputStream(img2Path)); } catch (Exception e) { log.error("图片对比出现异常,", e); } return false; } /** * [简要描述]:比较两个图片文件流是否一样
* [详细描述]:
* * @param image1 : 图片1 * @param image2 : 图片2 * @return boolean * llxiao 2019/9/29 - 14:09 **/ public boolean matchImage4Stream(InputStream image1, InputStream image2) { if (null == image1 || null == image2) { log.error("图片参数不能为空!"); return false; } try { if (check) { // 校验图片 if (isImage(image1) && isImage(image2)) { return this.distance(this.getHash(image1), this.getHash(image2)) >= matchThreshold; } else { log.error("文件流不是图片类型"); } } else { return this.distance(this.getHash(image1), this.getHash(image2)) >= matchThreshold; } } catch (Exception e) { log.error("图片对比出现位置异常,", e); } return false; } /** * [简要描述]:判断是否图片
* [详细描述]:
* * @param inputStream : * @return boolean * llxiao 2019/9/29 - 14:19 **/ private static boolean isImage(InputStream inputStream) { if (inputStream == null) { return false; } Image img; try { img = ImageIO.read(inputStream); return !(img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0); } catch (Exception e) { log.error("判断是否图片异常,", e); return false; } } private int distance(String s1, String s2) { int counter = 0; for (int k = 0; k < s1.length(); k++) { if (s1.charAt(k) != s2.charAt(k)) { counter++; } } return counter; } /** * 计算hash值 * * @param is * @return * @exception Exception */ private String getHash(InputStream is) throws Exception { BufferedImage img = ImageIO.read(is); //1. 缩小尺寸. img = resize(img, size, size); //2. 简化色彩 img = grayscale(img); double[][] vals = new double[size][size]; for (int x = 0; x < img.getWidth(); x++) { for (int y = 0; y < img.getHeight(); y++) { vals[x][y] = getBlue(img, x, y); } } //3. 计算DCT(离散余弦变换) double[][] dctVals = applyDCT(vals); //4. 缩小DCT // 5. 计算平均值 double total = 0; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { total += dctVals[x][y]; } } total -= dctVals[0][0]; double avg = total / (double) ((smallerSize * smallerSize) - 1); //6. 计算hash值. /* 根据8*8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1”,小于DCT均值的设为“0”。组合在一起, 就构成了一个64位的整数,这就是这张图片的指纹 */ String hash = ""; for (int x = 0; x < smallerSize; x++) { for (int y = 0; y < smallerSize; y++) { if (x != 0 && y != 0) { hash += (dctVals[x][y] > avg ? "1" : "0"); } } } return hash; } private BufferedImage resize(BufferedImage image, int width, int height) { BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = resizedImage.createGraphics(); g.drawImage(image, 0, 0, width, height, null); g.dispose(); return resizedImage; } private BufferedImage grayscale(BufferedImage img) { colorConvert.filter(img, img); return img; } private static int getBlue(BufferedImage img, int x, int y) { return (img.getRGB(x, y)) & 0xff; } // DCT function stolen from http://stackoverflow.com/questions/4240490/problems-with-dct-and-idct-algorithm-in-java private double[] c; private void initCoefficients() { c = new double[size]; for (int i = 1; i < size; i++) { c[i] = 1; } c[0] = 1 / Math.sqrt(2.0); } private double[][] applyDCT(double[][] f) { int N = size; double[][] F = new double[N][N]; for (int u = 0; u < N; u++) { for (int v = 0; v < N; v++) { double sum = 0.0; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { sum += Math.cos(((2 * i + 1) / (2.0 * N)) * u * Math.PI) * Math .cos(((2 * j + 1) / (2.0 * N)) * v * Math.PI) * (f[i][j]); } } sum *= ((c[u] * c[v]) / 4.0); F[u][v] = sum; } } return F; } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/CodePrefix.java ================================================ package com.xiao.springcloud.demo.common.validator; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * [简要描述]: 从类注解中获取业务编码的开头信息 * [详细描述]: * * @author llxiao * @version 1.0, 2018/8/31 10:54 * @since JDK 1.8 */ @Retention(RUNTIME) @Target(TYPE) public @interface CodePrefix { /** * 业务开头编码 */ int moduleCode() default 0; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/ParamAspect.java ================================================ package com.xiao.springcloud.demo.common.validator; 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.springframework.stereotype.Component; /** * [简要描述]: 查询校验切面 * [详细描述]: * * @author llxiao * @version 1.0, 2018/9/6 15:19 * @since JDK 1.8 */ @Aspect @Component public class ParamAspect { /** * [简要描述]:定义一个annotation切入点
* [详细描述]:切入点
* llxiao 2018/9/2 - 17:02 **/ @Pointcut("@annotation(com.purcotton.omni.common.annotation.param.aop.Validator)") public void paramValidator() { } // around 切面强化 @Around("paramValidator()") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { //获取方法参数 Object[] args = joinPoint.getArgs(); for (Object arg : args) { ParamValidator.validator(arg); } return joinPoint.proceed(args); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/ParamValidator.java ================================================ /* * Winner * 文件名 :Validator.java * 创建人 :llxiao * 创建时间:2017年12月27日 */ package com.xiao.springcloud.demo.common.validator; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.springcloud.demo.common.exception.CommonExceptionEnum; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Field; /** * [简要描述]:参数校验器
* [详细描述]:
* * @author llxiao * @version 1.0, 2017年12月27日 * @since JDK1.8 */ @Slf4j public class ParamValidator { /** * [简要描述]:校验对象属性
* [详细描述]:对象的属性用ParamVerify注解校验
* * @param object 校验对象 */ public static void validator(Object object) { if (null == object) { return; } final Class objClass = object.getClass(); //获取业务编码前缀 CodePrefix codePrefix = objClass.getAnnotation(CodePrefix.class); int moduleCode = 0; if (null != codePrefix) { moduleCode = codePrefix.moduleCode(); } try { // 获取所有字段 包括private 不包括父类字段 Field[] farray = objClass.getDeclaredFields(); // 初始化校验注解 Class verify = ParamVerify.class; String fieldName; ParamVerify paramVerify; String reg; String fValue; for (Field field : farray) { // 获取其中字段 fieldName = field.getName(); // 判断是否被chkstring注解所标识 if (field.isAnnotationPresent(verify)) { // 返回这个类所标识的注解对象 paramVerify = field.getAnnotation(verify); // 可以为空 boolean canBlank = paramVerify.canBlank(); // 设置权限 field.setAccessible(true); // 获取属性值 fValue = String.valueOf(field.get(object)); if (!canBlank) { // 必填校验 if (StringUtils.isBlank(fValue)) { log.info("缺少:{}必填参数", fieldName); // 如果字段是为空 throw new CommonException(generateCode(moduleCode, CommonException.REQUIRED_PARAM_SUFFIX), String .format("缺少:%s必填参数", fieldName)); } int maxLeng = paramVerify.maxLeng(); // 参数最大长度校验 if (0 != maxLeng && fValue.length() > maxLeng) { log.info("求参数:{}过长", fieldName); throw new CommonException(generateCode(moduleCode, CommonException.ILLEGAL_PARAM_SUFFIX), String .format("参数:%s非法", fieldName)); } // 正则校验 reg = paramVerify.regex(); if (StringUtils.isNotBlank(reg) && !fValue.matches(reg)) { log.info("请求参数:{}非法", fieldName); throw new CommonException(generateCode(moduleCode, CommonException.ILLEGAL_PARAM_SUFFIX), String .format("参数:%s非法", fieldName)); } } } } } catch (Exception e) { log.error("系统内部错误", e); throw new CommonException(CommonExceptionEnum.SYSTEM_ERROR); } } private static int generateCode(int moduleCode, String serviceCode) { String errorCode = moduleCode + serviceCode; return Integer.parseInt(errorCode); } } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/ParamVerify.java ================================================ /* * Winner * 文件名 :ParamVerify.java * 创建人 :llxiao * 创建时间:2017年12月27日 */ package com.xiao.springcloud.demo.common.validator; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * [简要描述]:参数校验注解
* [详细描述]:需要结合@CodePrefix注解来获取业务错误码的开头信息
* * @author llxiao * @version 1.0, 2017年12月27日 * @since JDK1.8 */ @Retention(RUNTIME) @Target(FIELD) public @interface ParamVerify { /** * 检验的正则表达式 */ String regex() default ""; /** * 可以为空 */ boolean canBlank() default true; /** * 最大长度校验 */ int maxLeng() default 0; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/Validator.java ================================================ package com.xiao.springcloud.demo.common.validator; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * [简要描述]: 参数校验器注解 * [详细描述]: 结合ParamValidator注解配合使用,ParamValidator定义到DTO数据的属性中 * * @author llxiao * @version 1.0, 2018/9/6 15:12 * @since JDK 1.8 */ @Retention(RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Inherited//说明子类可以继承父类中的该注解 @Target(ElementType.METHOD)//方法注解 @Documented//说明该注解将被包含在javadoc中 public @interface Validator { String value() default ""; } ================================================ FILE: SpringCloud-Common/src/main/java/com/xiao/springcloud/demo/common/validator/VerifyConstants.java ================================================ /* * Winner * 文件名 :VerifyConstants.java * 创建人 :llxiao * 创建时间:2017年12月27日 */ package com.xiao.springcloud.demo.common.validator; /** * [简要描述]:验证正则表达式
* [详细描述]:
* * @author llxiao * @version 1.0, 2017年12月27日 * @since JDK1.8 */ public interface VerifyConstants { /** * 电话校验 */ String TELL_REG = "^((0|\\+)?\\d{2})?1[356789]\\d{9}|([0\\+]\\d{2,3}-)?(0\\d{2,3}-)(\\d{7,8})(-(\\d{3,}))?$"; /** * 营业时间校验 */ String BUSINESS_HOURS = "^((20|21|22|23|[0-1]*\\d)\\:[0-5][0-9])$"; /** * ID校验 */ String ID_REG = "^[0-9]*$"; /** * 纬度校验
*
*/ String LAT_REG = "^-?((0|[1-8]?[0-9]?)(([.][0-9]{1,8})?)|90(([.][0]{1,8})?))$"; /** * 经度校验
*
*/ String LON_REG = "^-?((0|1?[0-7]?[0-9]?)(([.][0-9]{1,8})?)|180(([.][0]{1,8})?))$"; /** * 1位数字 */ String ONE_NUMBER_REG = "^\\d{1}$"; /** * 数字校验 */ String NUMBER_REG = "^\\d{1,2}$"; /** * 中文、英文、数字但不包括下划线等特殊符号。2-64位长度 */ String NAME_REG = "^[\\u4E00-\\u9FA5A-Za-z0-9]{2,64}$"; /** * 中文、英文、数字但不包括下划线等特殊符号。2-128位长度 */ String NAME_REG_128 = "^[\\u4E00-\\u9FA5A-Za-z0-9]{2,128}$"; } ================================================ FILE: SpringCloud-ConfigCenter/Readme.txt ================================================ 配置中心 仓库中的配置文件会被转换成web接口,访问可以参照以下的规则: /{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties 启动后访问: http://localhost:1110/springcloud/dev 返回结果: {"name":"springcloud", "profiles":["dev"], "label":null, "version":"8b9b92ac572f1ae18d8fa52b5c7d1dc489b6cb18", "state":null, "propertySources":[ {"name":"https://github.com/Xlinlin/spring-cloud-demo.git/SpringCloud-Configure/consumer/springcloud-dev.properties", "source":{"springcloud.configure.test":"hello-dev"}} ]} ================================================ FILE: SpringCloud-ConfigCenter/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-ConfigCenter 配置中心 org.springframework.boot spring-boot-starter-test test org.springframework.cloud spring-cloud-config-server ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-ConfigCenter/src/main/java/com/xiao/springcloud/configure/ConfigureApplication.java ================================================ /* * Winner * 文件名 :ConfigureApplication.java * 创建人 :llxiao * 创建时间:2018年8月9日 */ package com.xiao.springcloud.configure; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年8月9日 * @since JDK 1.8 */ @SpringBootApplication // 配置中心 @EnableConfigServer public class ConfigureApplication { /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @param args */ public static void main(String[] args) { SpringApplication.run(ConfigureApplication.class, args); } } ================================================ FILE: SpringCloud-ConfigCenter/src/main/resources/application.yml ================================================ server: port: 1110 spring: application: name: config-server #数据库配置 #datasource: #username: root #password: admin123 #url: jdbc:mysql://127.0.0.1:3306/config?useUnicode=true&characterEncoding=UTF-8 #driver-class-name: com.mysql.jdbc.Driver #profiles: #配置中心激活从数据库中读取 #active: jdbc #激活本地配置 #active: native #spring cloud config cloud: config: label: master server: # jdbc mysql实现 #jdbc: #sql: SELECT `KEY`, `VALUE` from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=? #本地文件配置 #native: #searchLocations: file://d:/config-repo #git 配置 git: uri: https://github.com/Xlinlin/spring-cloud-demo.git # git仓库地址下的相对地址,可以配置多个,用,分割 search-paths: SpringCloud-Configure/consumer,SpringCloud-Configure/euraka-server,SpringCloud-Configure/redisson # 公开的项目可以不需要账号密码 #username: uname #password: passwd ================================================ FILE: SpringCloud-Configure/README.txt ================================================ 配置中心配置文件放置 可按模块建文件夹放置 demo中放了consumer的配置和eureka的配置 ================================================ FILE: SpringCloud-Configure/consumer/springcloud-dev.properties ================================================ springcloud.configure.test=hello-dev ================================================ FILE: SpringCloud-Configure/consumer/springcloud-dev.yml ================================================ ================================================ FILE: SpringCloud-Configure/consumer/springcloud-prod.properties ================================================ springcloud.configure.test=hello-prod ================================================ FILE: SpringCloud-Configure/consumer/springcloud-prod.yml ================================================ ================================================ FILE: SpringCloud-Configure/consumer/springcloud-test.properties ================================================ ================================================ FILE: SpringCloud-Configure/consumer/springcloud-test.yml ================================================ springcloud.configure.test=hello-test ================================================ FILE: SpringCloud-Configure/eureka-server/eureka-server-dev.properties ================================================ ================================================ FILE: SpringCloud-Configure/eureka-server/eureka-server-dev.yml ================================================ eureka: #集群配置 #cluster: #node1: localhost:1110 #node2: localhost:1109 #node3: localhost:1108 #单机配置 instance: # eureka实例的主机名 hostname: center #prefer-ip-address: true #instance-id: ${spring.cloud.client.ipAddress}:${server.port} client: #由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己 register-with-eureka: false #由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false fetch-registry: false serviceUrl: #集群配置 #defaultZone: http://${eureka.cluster.node1}/eureka/,http://${eureka.cluster.node2}/eureka/,http://${eureka.cluster.node3}/eureka/ defaultZone: http://localhost:1111/eureka/ ================================================ FILE: SpringCloud-Configure/eureka-server/eureka-server-test.properties ================================================ ================================================ FILE: SpringCloud-Configure/eureka-server/eureka-server-test.yml ================================================ ================================================ FILE: SpringCloud-Configure/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-Configure pom ================================================ FILE: SpringCloud-Configure/redisson/redission-dev.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: # TransportMode.NIO, # TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) # TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # 单节点模式 singleServerConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 reconnectionTimeout: 3000 failedAttempts: 3 subscriptionsPerConnection: 5 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 connectionMinimumIdleSize: 32 connectionPoolSize: 64 database: 0 address: redis://localhost:6379 dnsMonitoringInterval: 5000 ================================================ FILE: SpringCloud-Configure/redisson/redission-sentinel-dev.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: # TransportMode.NIO, # TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) # TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # sentinel模式 sentinelServersConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 subscriptionsPerConnection: 5 clientName: null subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 slaveConnectionMinimumIdleSize: 32 slaveConnectionPoolSize: 64 masterConnectionMinimumIdleSize: 32 masterConnectionPoolSize: 64 readMode: "SLAVE" subscriptionMode: "SLAVE" sentinelAddresses: - "redis://localhost:26379" masterName: "mymaster" database: 0 ================================================ FILE: SpringCloud-Configure/redisson/redisson-cloud.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: #TransportMode.NIO, #TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) #TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # 云托管模式-支持:Azure Redis、Alibaba Reidis、AWS Redis replicatedServersConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 reconnectionTimeout: 3000 failedAttempts: 3 password: null subscriptionsPerConnection: 5 clientName: null #loadBalancer: ! {} slaveSubscriptionConnectionMinimumIdleSize: 1 slaveSubscriptionConnectionPoolSize: 50 slaveConnectionMinimumIdleSize: 32 slaveConnectionPoolSize: 64 masterConnectionMinimumIdleSize: 32 masterConnectionPoolSize: 64 readMode: "SLAVE" nodeAddresses: - "redis://127.0.0.1:2812" - "redis://127.0.0.1:2815" - "redis://127.0.0.1:2813" scanInterval: 1000 ================================================ FILE: SpringCloud-Configure/redisson/redisson-cluster-dev.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: # TransportMode.NIO, # TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) # TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # 集群模式 clusterServersConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 reconnectionTimeout: 3000 #执行失败最大次数。默认值:3 failedAttempts: 3 # 单个连接最大订阅数量,默认值5 subscriptionsPerConnection: 5 clientName: null #负载均衡算法类的选择 3.5.7不支持 #默认值: org.redisson.connection.balancer.RoundRobinLoadBalancer #在多Redis服务节点的环境里,可以选用以下几种负载均衡方式选择一个节点: #org.redisson.connection.balancer.WeightedRoundRobinBalancer - 权重轮询调度算法 #org.redisson.connection.balancer.RoundRobinLoadBalancer - 轮询调度算法 #org.redisson.connection.balancer.RandomLoadBalancer - 随机调度算法 #loadBalancer: ! {} slaveSubscriptionConnectionMinimumIdleSize: 1 slaveSubscriptionConnectionPoolSize: 50 slaveConnectionMinimumIdleSize: 32 slaveConnectionPoolSize: 64 masterConnectionMinimumIdleSize: 32 masterConnectionPoolSize: 64 #读取操作的负载均衡模式,默认值: SLAVE(只在从服务节点里读取) #可用值为: SLAVE - 只在从服务节点里读取。 MASTER - 只在主服务节点里读取。 MASTER_SLAVE - 在主从服务节点里都可以读取。 readMode: "SLAVE" #订阅操作的负载均衡模式,默认值:SLAVE(只在从服务节点里订阅) #可用值为: SLAVE - 只在从服务节点里订阅。 MASTER - 只在主服务节点里订阅 subscriptionMode: "SLAVE" #启用SSL终端识别 默认值:true #sslEnableEndpointIdentification: false nodeAddresses: - "redis://localhost:7379" - "redis://localhost:7380" - "redis://localhost:7381" - "redis://localhost1:7379" - "redis://localhost1:7380" - "redis://localhost1:7381" #集群扫描间隔时间,默认1000ms scanInterval: 1000 ================================================ FILE: SpringCloud-Configure/redisson/redisson-cluster-test.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: # TransportMode.NIO, # TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) # TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # 集群模式 clusterServersConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 reconnectionTimeout: 3000 #执行失败最大次数。默认值:3 failedAttempts: 3 # 单个连接最大订阅数量,默认值5 subscriptionsPerConnection: 5 clientName: null #负载均衡算法类的选择 3.5.7不支持 #默认值: org.redisson.connection.balancer.RoundRobinLoadBalancer #在多Redis服务节点的环境里,可以选用以下几种负载均衡方式选择一个节点: #org.redisson.connection.balancer.WeightedRoundRobinBalancer - 权重轮询调度算法 #org.redisson.connection.balancer.RoundRobinLoadBalancer - 轮询调度算法 #org.redisson.connection.balancer.RandomLoadBalancer - 随机调度算法 #loadBalancer: ! {} slaveSubscriptionConnectionMinimumIdleSize: 1 slaveSubscriptionConnectionPoolSize: 50 slaveConnectionMinimumIdleSize: 32 slaveConnectionPoolSize: 64 masterConnectionMinimumIdleSize: 32 masterConnectionPoolSize: 64 #读取操作的负载均衡模式,默认值: SLAVE(只在从服务节点里读取) #可用值为: SLAVE - 只在从服务节点里读取。 MASTER - 只在主服务节点里读取。 MASTER_SLAVE - 在主从服务节点里都可以读取。 readMode: "SLAVE" #订阅操作的负载均衡模式,默认值:SLAVE(只在从服务节点里订阅) #可用值为: SLAVE - 只在从服务节点里订阅。 MASTER - 只在主服务节点里订阅 subscriptionMode: "SLAVE" #启用SSL终端识别 默认值:true #sslEnableEndpointIdentification: false nodeAddresses: - "redis://localhost:7379" - "redis://localhost:7380" - "redis://localhost:7381" - "redis://localhost1:7379" - "redis://localhost1:7380" - "redis://localhost1:7381" #集群扫描间隔时间,默认1000ms scanInterval: 1000 ================================================ FILE: SpringCloud-Consumer/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-Consumer org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-ribbon org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-hystrix org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-stream-binder-kafka org.springframework.cloud spring-cloud-sleuth-stream org.springframework.boot spring-boot-starter-test test org.projectlombok lombok 1.16.18 true ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/ConsumerApp.java ================================================ /* * Winner * 文件名 :ConsumerApp.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @SpringBootApplication // 自动注册发现 @EnableDiscoveryClient // fegin客户端 @EnableFeignClients // 开启熔断功能 @EnableCircuitBreaker public class ConsumerApp { /** * 实例化RestTemplate使用@LoadBalanced开启负载均衡 * [简要描述]:
* [详细描述]:
* * @author llxiao * @return */ @Bean @LoadBalanced public RestTemplate resTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApp.class, args); } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/CommonConstants.java ================================================ package com.xiao.skywalking.consumer.common; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/1 11:16 * @since JDK 1.8 */ public interface CommonConstants { /** * yyyy-MM-dd */ String YYYYMMDD_DATE_FORMAT = "yyyy-MM-dd"; /** * 投放使用 */ int ACTIVITY_USED = 1; /** * 投放使用 */ int ACTIVITY_UN_USED = 0; } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/CommonException.java ================================================ package com.xiao.skywalking.consumer.common; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/1 11:08 * @since JDK 1.8 */ public class CommonException extends RuntimeException { private Integer code; private String errorMessage; public CommonException(Integer code, String errorMessage) { super(errorMessage); this.code = code; this.errorMessage = errorMessage; } public CommonException(ExceptionEnum exceptionEnum) { super(exceptionEnum.getErrorMsg()); this.code = exceptionEnum.getErrorCode(); this.errorMessage = exceptionEnum.getErrorMsg(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/ExceptionEnum.java ================================================ package com.xiao.skywalking.consumer.common;/** * [简要描述]: * [详细描述]: * * @since JDK 1.8 */ /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/1 11:10 * @since JDK 1.8 */ public enum ExceptionEnum { /** * 非法参数 */ INVALID_PARAMETER(6000, "非法参数"), /** * 当天当前活动用户已经中奖 */ USER_PRIZE_EXIST(6001, "当天当前活动用户已经中奖"), /** * 系统未知异常 */ SYSTEM_ERROR(9999, "系统异常"); /** * 错误状态码 */ private int errorCode; /** * 错误消息 */ private String errorMsg; ExceptionEnum(int errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } public int getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/advice/GlobalExceptionAdvice.java ================================================ package com.xiao.skywalking.consumer.common.advice; import com.xiao.skywalking.consumer.common.CommonException; import com.xiao.skywalking.consumer.common.ExceptionEnum; import com.xiao.skywalking.consumer.common.response.ErrorResponseData; import com.xiao.skywalking.consumer.common.response.ResponseData; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/2 09:54 * @since JDK 1.8 */ @ControllerAdvice @Slf4j public class GlobalExceptionAdvice { /** * 拦截未知的运行时异常 */ @ExceptionHandler(CommonException.class) @ResponseBody public ResponseData serviceException(CommonException e) { log.error("服务端业务异常:", e.getErrorMessage()); return new ErrorResponseData(e.getCode(), e.getErrorMessage()); } /** * 拦截未知的运行时异常 */ @ExceptionHandler(Exception.class) @ResponseBody public ResponseData notFount(Exception e) { printRequestUrl(); log.error("请求出现系统异常,异常信息:", e); return new ErrorResponseData(ExceptionEnum.SYSTEM_ERROR.getErrorCode(), ExceptionEnum.SYSTEM_ERROR .getErrorMsg()); } /** * 打印请求异常 */ private void printRequestUrl() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); if (null != requestAttributes) { HttpServletRequest request = requestAttributes.getRequest(); if (null != request) { log.error("客户端IP:{}发起请求URL:{}出现未知异常", request.getRemoteAddr(), request.getRequestURL()); } } } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/advice/UnifiedReturnAdvice.java ================================================ package com.xiao.skywalking.consumer.common.advice; import com.xiao.skywalking.consumer.common.response.ResponseData; import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * [简要描述]: 统一结果返回处理 * [详细描述]: * * @author llxiao * @version 1.0, 2019/11/1 23:40 * @since JDK 1.8 */ @Configuration @EnableWebMvc public class UnifiedReturnAdvice { @RestControllerAdvice static class CommonResultAdvice implements ResponseBodyAdvice { /** * Whether this component supports the given controller method return type * and the selected {@code HttpMessageConverter} type. * * @param returnType the return type * @param converterType the selected converter type * @return {@code true} if {@link #beforeBodyWrite} should be invoked; * {@code false} otherwise */ @Override public boolean supports(MethodParameter returnType, Class> converterType) { return true; } /** * Invoked after an {@code HttpMessageConverter} is selected and just before * its write method is invoked. * * @param body the body to be written * @param returnType the return type of the controller method * @param selectedContentType the content type selected through content negotiation * @param selectedConverterType the converter type selected to write to the response * @param request the current request * @param response the current response * @return the body that was passed in or a modified (possibly new) instance */ @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ResponseData) { return body; } // 针对特殊DTO 不处理,比如mybatis的分页类 // if (body instanceof PageInfo) // { // return body; // } return new ResponseData<>(body); } } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/response/ErrorResponseData.java ================================================ package com.xiao.skywalking.consumer.common.response; import lombok.NoArgsConstructor; /** * 请求失败的返回 */ @NoArgsConstructor public class ErrorResponseData extends ResponseData { public ErrorResponseData(String message) { super(false, DEFAULT_ERROR_CODE, message, null); } public ErrorResponseData(Integer code, String message) { super(false, code, message, null); } public ErrorResponseData(Integer code, String message, T data) { super(false, code, message, data); } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/response/ResponseData.java ================================================ package com.xiao.skywalking.consumer.common.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.xiao.skywalking.consumer.common.ExceptionEnum; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * 返回给前台的通用包装。 */ @Data @NoArgsConstructor @AllArgsConstructor @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) public class ResponseData implements Serializable { public static final String DEFAULT_SUCCESS_MESSAGE = "请求成功"; public static final String DEFAULT_ERROR_MESSAGE = "网络异常"; public static final Integer DEFAULT_SUCCESS_CODE = 200; public static final Integer DEFAULT_ERROR_CODE = 500; private static final long serialVersionUID = 276410732109056930L; /** * 请求是否成功 */ private Boolean success; /** * 响应状态码 */ private Integer code; /** * 响应信息 */ private String message; /** * 响应对象 */ private T data; public ResponseData(T data) { this.success = true; this.code = 0; this.data = data; } public ResponseData(int code, String message) { this.success = false; this.code = code; this.message = message; } public static SuccessResponseData success() { return new SuccessResponseData<>(null); } public static SuccessResponseData success(Object data) { return new SuccessResponseData<>(data); } public static SuccessResponseData success(Integer code, String message, Object data) { return new SuccessResponseData<>(code, message, data); } public static ErrorResponseData error(String message) { return new ErrorResponseData(message); } public static ErrorResponseData error(Integer code, String message) { return new ErrorResponseData(code, message); } public static ErrorResponseData error(Integer code, String message, Object object) { return new ErrorResponseData<>(code, message, object); } public static ErrorResponseData error(ExceptionEnum exceptionEnum) { return new ErrorResponseData(exceptionEnum.getErrorCode(), exceptionEnum.getErrorMsg()); } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/common/response/SuccessResponseData.java ================================================ package com.xiao.skywalking.consumer.common.response; import lombok.NoArgsConstructor; /** * 请求成功的返回 */ @NoArgsConstructor public class SuccessResponseData extends ResponseData { public SuccessResponseData(T data) { super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, data); } public SuccessResponseData(Integer code, String message, T data) { super(true, code, message, data); } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/controller/FeignContoller.java ================================================ /* * Winner * 文件名 :FeignContoller.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.xiao.skywalking.consumer.feign.FeignService; import com.xiao.skywalking.consumer.ribbon.RibbonService; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @RestController @RefreshScope public class FeignContoller { @Autowired FeignService feignService; @Autowired RibbonService ribbonService; // @Value("${profile}") // private String profile; @RequestMapping(path = "/feign") public String feign(String hello) { // return feignService.helloSkywalking(hello) + ' ' + this.profile; return feignService.helloSkywalking(hello); } @RequestMapping(path = "/ribbon") public String ribbon(String hello) { return ribbonService.helloSkywalking(hello); } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/feign/FeignService.java ================================================ /* * Winner * 文件名 :FeignService.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.consumer.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.xiao.skywalking.consumer.feign.impl.FeignServiceImpl; /** * [简要描述]:feign客户端,以及fallback熔断
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @FeignClient(value = "provider-1112", fallback = FeignServiceImpl.class) public interface FeignService { @RequestMapping(value = "/skywalking") String helloSkywalking(@RequestParam(value = "hello") String hello); } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/feign/impl/FQA ================================================ Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'xxx' method 异常问题: 错误原因是这里继承的接口类是一个controller接口,继承时会继承到父类的 @RequestMapping("/api") SpringMvc在做mapping映射的时候发现ConsumerFeignService和ConsumerFeignServiceFallBack的mapping重复了,所以抛出异常,如何解决呢? 解决的方法有2个: 一是更改ConsumerFeignServiceFallBack的mapping配置,代码如下: @Component @RequestMapping("fallback/api") public class ConsumerFeignServiceFallBack implements ConsumerFeignService { @Override public User getUserById(long id) { return new User(); } @Override public String echo(String name) { return "echo error: " + name; } } 二是使用fallbackFactory,代码如下: @Component public class ConsumerFeignServiceFallBack implements FallbackFactory { @Override public ConsumerFeignService create(Throwable cause) { return new ConsumerFeignService() { @Override public User getUserById(long id) { return new User(); } @Override public String echo(String name) { return "echo error: " + name; } }; } } 运行后,关闭服务提供者,发现熔断并没有生效,没有像单独使用@HystrixCommand时进入fallback方法,查了很多方式,发现原来是feign的hystix的配置开关没有打开 解决方法,在application.yml中增加配置如下: feign: hystrix: enabled: true ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/feign/impl/FeignServiceImpl.java ================================================ /* * Winner * 文件名 :FeignServiceImpl.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.consumer.feign.impl; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.xiao.skywalking.consumer.feign.FeignService; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @Component public class FeignServiceImpl implements FeignService { // @Value("${profile}") jdbc获取 // git获取变量 @Value("${springcloud.configure.test}") private String test; @Override public String helloSkywalking(String hello) { return "Service error!" + test; } } ================================================ FILE: SpringCloud-Consumer/src/main/java/com/xiao/skywalking/consumer/ribbon/RibbonService.java ================================================ /* * Winner * 文件名 :RibbonService.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.consumer.ribbon; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @Service public class RibbonService { @Autowired private RestTemplate restTemplate; // @Value("${profile}") jdbc获取 // git获取变量 @Value("${springcloud.configure.test}") private String test; // HystrixCommand出现异常调用fallback方法 @HystrixCommand(fallbackMethod = "fallback") public String helloSkywalking(String hello) { return this.restTemplate.getForObject("http://provider-1112/skywalking?hello=" + hello, String.class) + test; } public String fallback(String hello) { return "Service error!" + hello + ":" + test; } } ================================================ FILE: SpringCloud-Consumer/src/main/resources/application.yml ================================================ server: port: 1113 #spring: #application: #name: service-1113 # 开启熔断功能 feign: hystrix: enabled: true ### spring 配置 spring: ## zipkin 链路跟踪配置 sleuth: enabled: true #采样率,越高会有性能影响 sampler: percentage: 1.0 cloud: ## kafka zk配置 配合zipkin stream: kafka: binder: brokers: 192.168.206.203:9092 zkNodes: 192.168.206.203:2181 ================================================ FILE: SpringCloud-Consumer/src/main/resources/bootstrap.yml ================================================ #使用bootsrap.yml,之所以不在application.yml中配置,spring boot会优先加载bootstrap.yml,不然上下文中无法读取到配置 spring: application: name: consumer-1113 zipkin: base-url: http://localhost:1115 cloud: config: profile: dev label: master # git配置需要 name: springcloud # 配置中心url uri: http://localhost:1110 #无法连接配置中心启动失败 #fail-fast: true #使用服务发现到注册中心找配置中心 #discovery: #使用配置中心的配置 #enabled: true #配置中心服务id #serviceId: config-server #使用Spring Cloud Bus自动刷新 需要 rabbitmq支持 #rabbitmq: #host: localhost #port: 5673 #username: guest #password: guest #注册中心客户端配置 eureka: client: serviceUrl: defaultZone: http://localhost:1111/eureka/ #使用Spring Cloud Bus自动刷新 需要 rabbitmq支持 #endpoints: #bus: #enabled: true #sensitive: false ================================================ FILE: SpringCloud-Custom-ConfigCenter/README.MD ================================================ **配置中心(SpringCloud配置中心扩展)** _1._ **使用自定义读取配置源,基于mysql的配置读取实现**。
_2._ **设计简述**:
1) 基于IP、应用名称、LABEL、Profile获取配置
1.1) IP主要用于区分不同区域获取不同配置信息
1.2) 应用名称+Label+profile遵循原spring-cloud config的设计
2) 表结构设计,参考:configMysql.sql文件
2.1) 维护应用与区域之间的关系,即在获取配置时,依据请求IP查询所属区域对应的配置信息。
2.2) 抽象出具体配置项、配置组、应用配置信息。即配置组可挂多个配置项,应用环境配置挂多个配置组,以达到资源共享。
2.3) 支持单个服务私有配置
2.4) 维护客户端连接信息,提供服务的IP和端口
3) 整体设计
3.1) custom-config-server为服务端,单独部署启动,也可以集成到自己的应用,直接使用``@CustomEnableConfigServer``注解加上spring jdbc的配置即可: ``` spring: application: name: config-server #mysql datasource: url: jdbc:mysql://192.168.206.210:3306/config_center?useSSL=false username: admin password: Admin@123 driver-class-name: com.mysql.jdbc.Driver # 使用druid数据源 type: com.alibaba.druid.pool.DruidDataSource ``` 3.2) custom-config-service为Rest服务端,提供配置、应用、区域等维护的API,可单独部署应用也可以集成到web工程中
3.3) custom-config-web 提供前端的页面管理
3.4) custom-config-client 客户端jar包
3.5) custom-starter-config 客户端引用该工程即可
3.6) 客户端查询配置核心时序图,参考:配置中心查询核心时序图.png
3.7) 客户端刷新实现简介:
A. 改造原springcloud client,在发起restTemplate的时候,将客户端服务的端口上报到配置服务端,并添加refresh刷新的接口,其实际是调用了ContextRefresher.refresh()方法
B. 配置服务端保存应用+环境+应用服务的IP:PORT信息到数据库
C. 配置管理界面修改应用配置,进行发布配置刷新客户端配置
D. 客户端引用starter-config jar包,并在需要刷新的配置类上添加@RefreshScope注解。[custom-config-simple](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Custom-ConfigCenter/custom-config-simple)使用方法
E. 服务端发起刷新配置应用,通过应用+环境查找该应用下提供的所有应用服务,调用refresh刷新客户端的配置,如果调用失败标记该服务下线,下一次不进行刷新操作
F. springcloud的刷新参考资料:
[数据源刷新参考](https://www.jianshu.com/p/5acd4db7cd5e)
[Spring Cloud Config 是如何实现热更新的](http://www.scienjus.com/spring-cloud-refresh/)
[配置刷新基本流程](https://blog.csdn.net/cml_blog/article/details/78411312)
3.8) 引入netty,实现心跳机制监听服务端客户端连接状态。
A. 支持自定义netty端口,服务端和客户端自定义填写,配置参数:netty.server.port,默认使用8999
B. springboot的 server.port 一定要配置到 bootstrap.yml配置文件中,客户端需要使用到该端口号上报到服务端
C. springboot + netty 简单设计实现:
心跳维持在线状态
初始化连接时,模拟一个登陆操作,以此绑定netty连接端口和服务端所提供的端口,用于断开下线使用
客户端失去心跳或异常连接,服务端监听到,标记应用离线
D. 新增配置 ``spring.cloud.config.custom`` 参数来标记使用自定义配置中心,主要用于加载netty,使用心跳机制 E. netty实现配置刷新方案,netty连接后发起登录请求,上报客户端服务的IP和端口,后台数据库绑定netty和提供服务端口。服务端用一个ConcurrentHashMap存储连接,后台管理发起刷新请求通过netty发给客户端,刷新配置
_3._**配置中心高可用方案理论**
既然使用spring-cloud套件,可结合eureka注册中心来实现高可用。其他详细设计可参考携程开源的apollo的高可用设计 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/README.md ================================================ **springcloud config client改造** 1. 在客户端发起rest请求获取配置时,在http header中添加当前提供服务的端口号,参考:
``com.xiao.custom.config.client.configuration.ConfigServicePropertySourceLocator.getSecureRestTemplate`` 2. 新增refresh包,提供refresh API接口,技术关键点:
``com.xiao.custom.config.client.refresh.component.RefreshBeanConfig``类
``@ComponentScan``注解,用于扫描提供的api接口bean对象注入到spring容器中
3. ``@Configuration 和 @EnableConfigurationProperties`` 注解,来确定唯一引用位置 resouces/META-INF/spring.factories文件(SPI机制)
该文件内记录了需要加载的@Configuration注解类:
```$xslt # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xiao.custom.config.client.configuration.ConfigClientAutoConfiguration # Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.xiao.custom.config.client.configuration.ConfigServiceBootstrapConfiguration,\ com.xiao.custom.config.client.configuration.DiscoveryClientConfigServiceBootstrapConfiguration ``` 4. 引入netty,实现心跳机制监听服务端客户端连接状态。
A. 支持自定义netty端口,服务端和客户端自定义填写,配置参数:netty.server.port,默认使用8999
B. springboot的 server.port 一定要配置到 bootstrap.yml配置文件中,客户端需要使用到该端口号上报到服务端
C. springboot + netty 简单设计实现:
心跳维持在线状态
初始化连接时,模拟一个登陆操作,以此绑定netty连接端口和服务端所提供的端口,用于断开下线使用
客户端失去心跳或异常连接,服务端监听到,标记应用离线
D. 新增配置 ``spring.cloud.config.custom`` 参数来标记使用自定义配置中心,主要用于加载netty,使用心跳机制 E. netty实现配置刷新方案,netty连接后发起登录请求,上报客户端服务的IP和端口,后台数据库绑定netty和提供服务端口。服务端用一个ConcurrentHashMap存储连接,后台管理发起刷新请求通过netty发给客户端,刷新配置
**配置中心(SpringCloud配置中心扩展** _1._ **使用自定义读取配置源,基于mysql的配置读取实现,兼容原声的springcloud config模块**。
_2._ **设计简述**:
1) 基于IP、应用名称、LABEL、Profile获取配置
1.1) IP主要用于区分不同区域获取不同配置信息
1.2) 应用名称+Label+profile遵循原spring-cloud config的设计
2) 表结构设计,参考:configMysql.sql文件
2.1) 维护应用与区域之间的关系,即在获取配置时,依据请求IP查询所属区域对应的配置信息。
2.2) 抽象出具体配置项、配置组、应用配置信息。即配置组可挂多个配置项,应用环境配置挂多个配置组,以达到资源共享。
2.3) 支持单个服务私有配置
2.4) 维护客户端连接信息,提供服务的IP和端口
3) 整体设计
3.1) 采用SpringCloud实现,Center-Server为服务端,单独部署启动,也可以集成到自己的应用,直接使用``@CustomEnableConfigServer``注解加上spring jdbc的配置即可: ``` spring: application: name: winner-config-server #mysql datasource: url: jdbc:mysql://192.168.206.210:3306/config_center?useSSL=false username: admin password: Admin@123 driver-class-name: com.mysql.jdbc.Driver # 使用druid数据源 type: com.alibaba.druid.pool.DruidDataSource ``` 3.2) center-service为Rest服务端,提供配置、应用、区域等维护的API,可单独部署应用也可以集成到web工程中
3.3) center-web 提供前端的页面管理,请求地址:http://localhost:9002
3.4) center-client 客户端jar包
3.5) starter-config 客户端引用该工程即可
3.6) 客户端查询配置核心时序图,参考:配置中心查询核心时序图.png
3.7) 客户端刷新实现简介:
A. 改造原springcloud client,在发起restTemplate的时候,将客户端服务的端口上报到配置服务端,并添加refresh刷新的接口,其实际是调用了ContextRefresher.refresh()方法
B. 配置服务端保存应用+环境+应用服务的IP:PORT信息到数据库
C. 配置管理界面修改应用配置,进行发布配置刷新客户端配置
D. 客户端引用starter-config jar包,并在需要刷新的配置类上添加@RefreshScope注解。参考config-center-simple使用方法 `` com.winner.config.center winner-starter-config 1.0-SNAPSHOT ``
E. 服务端发起刷新配置应用,通过应用+环境查找该应用下提供的所有应用服务,调用refresh刷新客户端的配置,如果调用失败标记该服务下线,下一次不进行刷新操作
F. springcloud的刷新参考资料:
[数据源刷新参考](https://www.jianshu.com/p/5acd4db7cd5e)
[Spring Cloud Config 是如何实现热更新的](http://www.scienjus.com/spring-cloud-refresh/)
[配置刷新基本流程](https://blog.csdn.net/cml_blog/article/details/78411312)
3.8) 引入netty,实现心跳机制监听服务端客户端连接状态。
A. 支持自定义netty端口,服务端和客户端自定义填写,配置参数:netty.server.port,默认使用8999
B. springboot的 server.port 一定要配置到 bootstrap.yml配置文件中,客户端需要使用到该端口号上报到服务端
C. springboot + netty 简单设计实现:
心跳维持在线状态
初始化连接时,模拟一个登陆操作,以此绑定netty连接端口和服务端所提供的端口,用于断开下线使用
客户端失去心跳或异常连接,服务端监听到,标记应用离线
D. 新增配置 ``spring.cloud.config.custom`` 参数来标记使用自定义配置中心,主要用于加载netty,使用心跳机制 E. netty实现配置刷新方案,netty连接后发起登录请求,上报客户端服务的IP和端口,后台数据库绑定netty和提供服务端口。服务端用一个ConcurrentHashMap存储连接,后台管理发起刷新请求通过netty发给客户端,刷新配置
_3._**配置中心高可用方案理论**
既然使用spring-cloud套件,可结合eureka注册中心来实现高可用。其他详细设计可参考携程开源的apollo的高可用设计 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-client 4.1.42.Final org.springframework.boot spring-boot-configuration-processor true org.springframework.boot spring-boot-autoconfigure org.springframework.boot spring-boot-starter-logging true org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-context org.springframework spring-web com.fasterxml.jackson.core jackson-annotations org.springframework.retry spring-retry true org.springframework.boot spring-boot-starter-actuator true org.springframework.boot spring-boot-starter-aop true com.fasterxml.jackson.core jackson-databind org.springframework.boot spring-boot-starter-test test com.dyuproject.protostuff protostuff-core 1.1.2 com.dyuproject.protostuff protostuff-runtime 1.1.2 org.objenesis objenesis 2.1 io.netty netty-all ${netty.version} org.apache.commons commons-lang3 3.6 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigClientAutoConfiguration.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import com.xiao.custom.config.client.netty.client.NettyClient; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; /** * Expose a ConfigClientProperties just so that there is a way to inspect the properties * bound to it. It won't be available in time for autowiring into the bootstrap context, * but the values in this properties object will be the same as the ones used to bind to * the config server, if there is one. * * @author Dave Syer * @author Marcos Barbero */ @Configuration public class ConfigClientAutoConfiguration { @Bean public ConfigClientProperties configClientProperties(Environment environment, ApplicationContext context) { if (context.getParent() != null && BeanFactoryUtils .beanNamesForTypeIncludingAncestors(context.getParent(), ConfigClientProperties.class).length > 0) { return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(), ConfigClientProperties.class); } ConfigClientProperties client = new ConfigClientProperties(environment); return client; } @Bean public ConfigClientHealthProperties configClientHealthProperties() { return new ConfigClientHealthProperties(); } @Configuration @ConditionalOnClass(HealthIndicator.class) @ConditionalOnBean(ConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "health.config.enabled", matchIfMissing = true) protected static class ConfigServerHealthIndicatorConfiguration { @Bean public ConfigServerHealthIndicator configServerHealthIndicator(ConfigServicePropertySourceLocator locator, ConfigClientHealthProperties properties, Environment environment) { return new ConfigServerHealthIndicator(locator, environment, properties); } } @Configuration @ConditionalOnClass(ContextRefresher.class) @ConditionalOnBean(ContextRefresher.class) @ConditionalOnProperty(value = "spring.cloud.config.watch.enabled") protected static class ConfigClientWatchConfiguration { @Bean public ConfigClientWatch configClientWatch(ContextRefresher contextRefresher) { return new ConfigClientWatch(contextRefresher); } } @Bean @ConditionalOnProperty(name = "spring.cloud.config.custom", havingValue = "true") public NettyClient nettyClient() { return new NettyClient(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigClientHealthProperties.java ================================================ /* * Copyright 2013-2016 the original author or authors. * * Licensed 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. * */ package com.xiao.custom.config.client.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Spencer Gibb */ @ConfigurationProperties("health.config") public class ConfigClientHealthProperties { /** * Flag to indicate that the config server health indicator should be installed. */ boolean enabled; /** * Time to live for cached result, in milliseconds. Default 300000 (5 min). */ private long timeToLive = 60 * 5 * 1000; public boolean isEnabled() { return this.enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public long getTimeToLive() { return timeToLive; } public void setTimeToLive(long timeToLive) { this.timeToLive = timeToLive; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigClientProperties.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; import java.net.MalformedURLException; import java.net.URL; /** * @author Dave Syer */ @ConfigurationProperties(ConfigClientProperties.PREFIX) public class ConfigClientProperties { public static final String PREFIX = "spring.cloud.config"; public static final String TOKEN_HEADER = "X-Config-Token"; public static final String STATE_HEADER = "X-Config-State"; /** * 自定义配置中心,默认不使用 * true使用,false不适用 */ private boolean custom = false; /** * server port */ @Value("${server.port:}") private int serverPort; /** * 服务端netty端口号 */ @Value("${netty.server.port:8999}") private int nettyPort; /** * Flag to say that remote configuration is enabled. Default true; */ private boolean enabled = true; /** * The default profile to use when fetching remote configuration (comma-separated). * Default is "default". */ private String profile = "default"; /** * Name of application used to fetch remote properties. */ @Value("${spring.application.name:application}") private String name; /** * The label name to use to pull remote configuration properties. The default is set * on the server (generally "master" for a git based server). */ private String label; /** * The username to use (HTTP Basic) when contacting the remote server. */ private String username; /** * The password to use (HTTP Basic) when contacting the remote server. */ private String password; /** * The URI of the remote server (default http://localhost:8888). */ private String uri = "http://localhost:8888"; /** * Discovery properties. */ private Discovery discovery = new Discovery(); /** * Flag to indicate that failure to connect to the server is fatal (default false). */ private boolean failFast = false; /** * Security Token passed thru to underlying environment repository. */ private String token; /** * Authorization token used by the client to connect to the server. */ private String authorization; private ConfigClientProperties() { } public ConfigClientProperties(Environment environment) { String[] profiles = environment.getActiveProfiles(); if (profiles.length == 0) { profiles = environment.getDefaultProfiles(); } this.setProfile(StringUtils.arrayToCommaDelimitedString(profiles)); } public boolean isCustom() { return custom; } public void setCustom(boolean custom) { this.custom = custom; } public int getNettyPort() { return nettyPort; } public void setNettyPort(int nettyPort) { this.nettyPort = nettyPort; } public boolean isEnabled() { return this.enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getRawUri() { return extractCredentials().uri; } public String getUri() { return this.uri; } public void setUri(String url) { this.uri = url; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getProfile() { return this.profile; } public void setProfile(String env) { this.profile = env; } public String getLabel() { return this.label; } public void setLabel(String label) { this.label = label; } public String getUsername() { return extractCredentials().username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return extractCredentials().password; } public void setPassword(String password) { this.password = password; } public Discovery getDiscovery() { return this.discovery; } public void setDiscovery(Discovery discovery) { this.discovery = discovery; } public boolean isFailFast() { return this.failFast; } public void setFailFast(boolean failFast) { this.failFast = failFast; } public String getToken() { return this.token; } public void setToken(String token) { this.token = token; } public String getAuthorization() { return this.authorization; } public void setAuthorization(String authorization) { this.authorization = authorization; } public int getServerPort() { return serverPort; } public void setServerPort(int serverPort) { this.serverPort = serverPort; } private Credentials extractCredentials() { Credentials result = new Credentials(); String uri = this.uri; result.uri = uri; Credentials explicitCredentials = getUsernamePassword(); result.username = explicitCredentials.username; result.password = explicitCredentials.password; try { URL url = new URL(uri); String userInfo = url.getUserInfo(); // no credentials in url, return explicit credentials if (StringUtils.isEmpty(userInfo) || ":".equals(userInfo)) { return result; } String bare = UriComponentsBuilder.fromHttpUrl(uri).userInfo(null).build().toUriString(); result.uri = bare; // handle the password only case if (!userInfo.contains(":")) { userInfo = userInfo + ":"; } String[] split = userInfo.split(":"); // set username and password from uri result.username = split[0]; result.password = split[1]; // override password if explicitly set if (explicitCredentials.password != null) { // Explicit username / password takes precedence result.password = explicitCredentials.password; } // override username if explicitly set if (!"user".equals(explicitCredentials.username)) { // But the username can be overridden result.username = explicitCredentials.username; } return result; } catch (MalformedURLException e) { throw new IllegalStateException("Invalid URL: " + uri); } } private Credentials getUsernamePassword() { Credentials credentials = new Credentials(); if (StringUtils.hasText(this.password)) { credentials.password = this.password.trim(); } if (StringUtils.hasText(this.username)) { credentials.username = this.username.trim(); } else { credentials.username = "user"; } return credentials; } private static class Credentials { private String username; private String password; private String uri; } public static class Discovery { public static final String DEFAULT_CONFIG_SERVER = "configserver"; /** * Flag to indicate that config server discovery is enabled (config server URL will be * looked up via discovery). */ private boolean enabled; /** * Service id to locate config server. */ private String serviceId = DEFAULT_CONFIG_SERVER; public boolean isEnabled() { return this.enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getServiceId() { return this.serviceId; } public void setServiceId(String serviceId) { this.serviceId = serviceId; } } public ConfigClientProperties override(Environment environment) { ConfigClientProperties override = new ConfigClientProperties(); BeanUtils.copyProperties(this, override); override.setName(environment.resolvePlaceholders( "${" + ConfigClientProperties.PREFIX + ".name:${spring.application.name:application}}")); if (environment.containsProperty(ConfigClientProperties.PREFIX + ".profile")) { override.setProfile(environment.getProperty(ConfigClientProperties.PREFIX + ".profile")); } if (environment.containsProperty(ConfigClientProperties.PREFIX + ".label")) { override.setLabel(environment.getProperty(ConfigClientProperties.PREFIX + ".label")); } return override; } @Override public String toString() { return "ConfigClientProperties [enabled=" + this.enabled + ", profile=" + this.profile + ", name=" + this.name + ", label=" + (this.label == null ? "" : this.label) + ", username=" + this.username + ", password=" + this.password + ", uri=" + this.uri + ", authorization=" + this.authorization + ", discovery.enabled=" + this.discovery.enabled + ", failFast=" + this.failFast + ", token=" + this.token + "]"; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigClientStateHolder.java ================================================ package com.xiao.custom.config.client.configuration; /** * @author Spencer Gibb */ public class ConfigClientStateHolder { private static ThreadLocal state = new ThreadLocal<>(); public static void resetState() { state.remove(); } public static void setState(String newState) { if (newState == null) { resetState(); return; } state.set(newState); } public static String getState() { return state.get(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigClientWatch.java ================================================ /* * Copyright 2013-2016 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.PostConstruct; import java.io.Closeable; import java.util.concurrent.atomic.AtomicBoolean; import static org.springframework.util.StringUtils.hasText; /** * @author Spencer Gibb */ public class ConfigClientWatch implements Closeable, EnvironmentAware { private static Log log = LogFactory.getLog(ConfigServicePropertySourceLocator.class); private final AtomicBoolean running = new AtomicBoolean(false); private final ContextRefresher refresher; private Environment environment; public ConfigClientWatch(ContextRefresher refresher) { this.refresher = refresher; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @PostConstruct public void start() { this.running.compareAndSet(false, true); } @Scheduled(initialDelayString = "${spring.cloud.config.watch.initialDelay:180000}", fixedDelayString = "${spring.cloud.config.watch.delay:500}") public void watchConfigServer() { if (this.running.get()) { String newState = this.environment.getProperty("config.client.state"); String oldState = ConfigClientStateHolder.getState(); // only refresh if state has changed if (stateChanged(oldState, newState)) { ConfigClientStateHolder.setState(newState); this.refresher.refresh(); } } } /* for testing */ boolean stateChanged(String oldState, String newState) { return (!hasText(oldState) && hasText(newState)) || (hasText(oldState) && !oldState.equals(newState)); } @Override public void close() { this.running.compareAndSet(true, false); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigServerHealthIndicator.java ================================================ package com.xiao.custom.config.client.configuration; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health.Builder; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import java.util.ArrayList; import java.util.List; /** * @author Spencer Gibb * @author Marcos Barbero */ public class ConfigServerHealthIndicator extends AbstractHealthIndicator { private ConfigServicePropertySourceLocator locator; private ConfigClientHealthProperties properties; private Environment environment; private long lastAccess = 0; private PropertySource cached; public ConfigServerHealthIndicator(ConfigServicePropertySourceLocator locator, Environment environment, ConfigClientHealthProperties properties) { this.environment = environment; this.locator = locator; this.properties = properties; } @Override protected void doHealthCheck(Builder builder) throws Exception { PropertySource propertySource = getPropertySource(); builder.up(); if (propertySource instanceof CompositePropertySource) { List sources = new ArrayList<>(); for (PropertySource ps : ((CompositePropertySource) propertySource).getPropertySources()) { sources.add(ps.getName()); } builder.withDetail("propertySources", sources); } else if (propertySource != null) { builder.withDetail("propertySources", propertySource.toString()); } else { builder.unknown().withDetail("error", "no property sources located"); } } private PropertySource getPropertySource() { long accessTime = System.currentTimeMillis(); if (isCacheStale(accessTime)) { this.lastAccess = accessTime; this.cached = locator.locate(this.environment); } return this.cached; } private boolean isCacheStale(long accessTime) { if (this.cached == null) { return true; } return (accessTime - this.lastAccess) >= this.properties.getTimeToLive(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigServiceBootstrapConfiguration.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.Retryable; import org.springframework.retry.interceptor.RetryInterceptorBuilder; import org.springframework.retry.interceptor.RetryOperationsInterceptor; /** * @author Dave Syer */ @Configuration @EnableConfigurationProperties public class ConfigServiceBootstrapConfiguration { @Autowired private ConfigurableEnvironment environment; @Bean public ConfigClientProperties configClientProperties() { ConfigClientProperties client = new ConfigClientProperties(this.environment); return client; } @Bean @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true) public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) { ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(properties); return locator; } @ConditionalOnProperty(value = "spring.cloud.config.failFast", matchIfMissing = false) @ConditionalOnClass({ Retryable.class, Aspect.class, AopAutoConfiguration.class }) @Configuration @EnableRetry(proxyTargetClass = true) @Import(AopAutoConfiguration.class) @EnableConfigurationProperties(RetryProperties.class) protected static class RetryConfiguration { @Bean @ConditionalOnMissingBean(name = "configServerRetryInterceptor") public RetryOperationsInterceptor configServerRetryInterceptor(RetryProperties properties) { return RetryInterceptorBuilder.stateless() .backOffOptions(properties.getInitialInterval(), properties.getMultiplier(), properties .getMaxInterval()).maxAttempts(properties.getMaxAttempts()).build(); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/ConfigServicePropertySourceLocator.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import com.xiao.custom.config.client.environment.Environment; import com.xiao.custom.config.client.environment.PropertySource; import com.xiao.custom.config.client.netty.util.RemotingUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.bootstrap.config.PropertySourceLocator; import org.springframework.core.annotation.Order; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.MapPropertySource; import org.springframework.http.*; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.retry.annotation.Retryable; import org.springframework.util.Base64Utils; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static com.xiao.custom.config.client.configuration.ConfigClientProperties.STATE_HEADER; import static com.xiao.custom.config.client.configuration.ConfigClientProperties.TOKEN_HEADER; /** * @author Dave Syer */ @Order(0) public class ConfigServicePropertySourceLocator implements PropertySourceLocator { private static final String LOCAL_HOST = "ClientServerHost"; private static final String LOCAL_PORT = "ClientServerPort"; private static Log logger = LogFactory.getLog(ConfigServicePropertySourceLocator.class); private RestTemplate restTemplate; private ConfigClientProperties defaultProperties; public ConfigServicePropertySourceLocator(ConfigClientProperties defaultProperties) { this.defaultProperties = defaultProperties; } @Override @Retryable(interceptor = "configServerRetryInterceptor") public org.springframework.core.env.PropertySource locate(org.springframework.core.env.Environment environment) { ConfigClientProperties properties = this.defaultProperties.override(environment); CompositePropertySource composite = new CompositePropertySource("configService"); RestTemplate restTemplate = this.restTemplate == null ? getSecureRestTemplate(properties) : this.restTemplate; Exception error = null; String errorBody = null; logger.info("Fetching config from server at: " + properties.getRawUri()); try { String[] labels = new String[] { "" }; if (StringUtils.hasText(properties.getLabel())) { labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel()); } String state = ConfigClientStateHolder.getState(); // Try all the labels until one works for (String label : labels) { Environment result = getRemoteEnvironment(restTemplate, properties, label.trim(), state); if (result != null) { logger.info(String .format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", result .getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result .getLabel(), result.getVersion(), result.getState())); if (result.getPropertySources() != null) { // result.getPropertySources() can be null if using xml for (PropertySource source : result.getPropertySources()) { @SuppressWarnings("unchecked") Map map = (Map) source .getSource(); composite.addPropertySource(new MapPropertySource(source.getName(), map)); } } if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) { HashMap map = new HashMap<>(); putValue(map, "config.client.state", result.getState()); putValue(map, "config.client.version", result.getVersion()); composite.addFirstPropertySource(new MapPropertySource("configClient", map)); } return composite; } } } catch (HttpServerErrorException e) { error = e; if (MediaType.APPLICATION_JSON.includes(e.getResponseHeaders().getContentType())) { errorBody = e.getResponseBodyAsString(); } } catch (Exception e) { error = e; } if (properties.isFailFast()) { throw new IllegalStateException("Could not locate PropertySource and the fail fast property is set, failing", error); } logger.warn("Could not locate PropertySource: " + (errorBody == null ? error == null ? "label not found" : error.getMessage() : errorBody)); return null; } private void putValue(HashMap map, String key, String value) { if (StringUtils.hasText(value)) { map.put(key, value); } } private Environment getRemoteEnvironment(RestTemplate restTemplate, ConfigClientProperties properties, String label, String state) { String path = "/{name}/{profile}"; String name = properties.getName(); String profile = properties.getProfile(); String token = properties.getToken(); String uri = properties.getRawUri(); Object[] args = new String[] { name, profile }; if (StringUtils.hasText(label)) { args = new String[] { name, profile, label }; path = path + "/{label}"; } ResponseEntity response = null; try { HttpHeaders headers = new HttpHeaders(); if (StringUtils.hasText(token)) { headers.add(TOKEN_HEADER, token); } if (StringUtils.hasText(state)) { //TODO: opt in to sending state? headers.add(STATE_HEADER, state); } final HttpEntity entity = new HttpEntity<>((Void) null, headers); response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args); } catch (HttpClientErrorException e) { if (e.getStatusCode() != HttpStatus.NOT_FOUND) { throw e; } } if (response == null || response.getStatusCode() != HttpStatus.OK) { return null; } Environment result = response.getBody(); return result; } public void setRestTemplate(RestTemplate restTemplate) { this.restTemplate = restTemplate; } private RestTemplate getSecureRestTemplate(ConfigClientProperties client) { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout((60 * 1000 * 3) + 5000); //TODO 3m5s, make configurable? RestTemplate template = new RestTemplate(requestFactory); String password = client.getPassword(); String authorization = client.getAuthorization(); if (password != null && authorization != null) { throw new IllegalStateException("You must set either 'password' or 'authorization'"); } if (password != null) { template.setInterceptors(Arrays.asList(new BasicAuthorizationInterceptor(client .getUsername(), password))); } else if (authorization != null) { template.setInterceptors(Arrays.asList(new GenericAuthorization(authorization))); } // 自定义 http header template.setInterceptors(Arrays .asList((ClientHttpRequestInterceptor) (httpRequest, bytes, clientHttpRequestExecution) -> { HttpHeaders headers = httpRequest.getHeaders(); // TODO 通过netty交互,上报客户端 服务IP和端口号 headers.add(LOCAL_PORT, client.getServerPort() + ""); headers.add(LOCAL_HOST, RemotingUtil.getLocalHost()); return clientHttpRequestExecution.execute(httpRequest, bytes); })); return template; } private static class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor { private final String username; private final String password; public BasicAuthorizationInterceptor(String username, String password) { this.username = username; this.password = (password == null ? "" : password); } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { byte[] token = Base64Utils.encode((this.username + ":" + this.password).getBytes()); request.getHeaders().add("Authorization", "Basic " + new String(token)); return execution.execute(request, body); } } private static class GenericAuthorization implements ClientHttpRequestInterceptor { private final String authorizationToken; public GenericAuthorization(String authorizationToken) { this.authorizationToken = (authorizationToken == null ? "" : authorizationToken); } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { request.getHeaders().add("Authorization", authorizationToken); return execution.execute(request, body); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/DiscoveryClientConfigServiceBootstrapConfiguration.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.event.HeartbeatEvent; import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; import org.springframework.cloud.commons.util.UtilAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import java.util.List; /** * Bootstrap configuration for a config client that wants to lookup the config server via * discovery. * * @author Dave Syer */ @ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false) @Configuration @Import({ UtilAutoConfiguration.class }) @EnableDiscoveryClient public class DiscoveryClientConfigServiceBootstrapConfiguration { private static Log logger = LogFactory.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class); @Autowired private ConfigClientProperties config; @Autowired private DiscoveryClient client; private HeartbeatMonitor monitor = new HeartbeatMonitor(); @EventListener(ContextRefreshedEvent.class) public void startup(ContextRefreshedEvent event) { refresh(); } @EventListener(HeartbeatEvent.class) public void heartbeat(HeartbeatEvent event) { if (monitor.update(event.getValue())) { refresh(); } } private void refresh() { try { logger.debug("Locating configserver via discovery"); String serviceId = this.config.getDiscovery().getServiceId(); List instances = this.client.getInstances(serviceId); if (instances.isEmpty()) { logger.warn("No instances found of configserver (" + serviceId + ")"); return; } ServiceInstance server = instances.get(0); String url = getHomePage(server); if (server.getMetadata().containsKey("password")) { String user = server.getMetadata().get("user"); user = user == null ? "user" : user; this.config.setUsername(user); String password = server.getMetadata().get("password"); this.config.setPassword(password); } if (server.getMetadata().containsKey("configPath")) { String path = server.getMetadata().get("configPath"); if (url.endsWith("/") && path.startsWith("/")) { url = url.substring(0, url.length() - 1); } url = url + path; } this.config.setUri(url); } catch (Exception ex) { logger.warn("Could not locate configserver via discovery", ex); } } private String getHomePage(ServiceInstance server) { return server.getUri().toString() + "/"; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/configuration/RetryProperties.java ================================================ /* * Copyright 2014-2015 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Dave Syer * */ @ConfigurationProperties("spring.cloud.config.retry") public class RetryProperties { /** * Initial retry interval in milliseconds. */ long initialInterval = 1000; /** * Multiplier for next interval. */ double multiplier = 1.1; /** * Maximum interval for backoff. */ long maxInterval = 2000; /** * Maximum number of attempts. */ int maxAttempts = 6; public long getInitialInterval() { return this.initialInterval; } public void setInitialInterval(long initialInterval) { this.initialInterval = initialInterval; } public double getMultiplier() { return this.multiplier; } public void setMultiplier(double multiplier) { this.multiplier = multiplier; } public long getMaxInterval() { return this.maxInterval; } public void setMaxInterval(long maxInterval) { this.maxInterval = maxInterval; } public int getMaxAttempts() { return this.maxAttempts; } public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/environment/Environment.java ================================================ /* * Copyright 2013-2015 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.environment; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Simple plain text serializable encapsulation of a list of property sources. Basically a * DTO for {@link org.springframework.core.env.Environment}, but also applicable outside * the domain of a Spring application. * * @author Dave Syer * @author Spencer Gibb * */ public class Environment { private String name; private String[] profiles = new String[0]; private String label; private List propertySources = new ArrayList<>(); private String version; private String state; public Environment(String name, String... profiles) { this(name, profiles, "master", null, null); } /** * Copies all fields except propertySources * @param env */ public Environment(Environment env) { this(env.getName(), env.getProfiles(), env.getLabel(), env.getVersion(), env.getState()); } @JsonCreator public Environment(@JsonProperty("name") String name, @JsonProperty("profiles") String[] profiles, @JsonProperty("label") String label, @JsonProperty("version") String version, @JsonProperty("state") String state) { super(); this.name = name; this.profiles = profiles; this.label = label; this.version = version; this.state = state; } public void add(PropertySource propertySource) { this.propertySources.add(propertySource); } public void addAll(List propertySources) { this.propertySources.addAll(propertySources); } public void addFirst(PropertySource propertySource) { this.propertySources.add(0, propertySource); } public List getPropertySources() { return propertySources; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String[] getProfiles() { return profiles; } public void setProfiles(String[] profiles) { this.profiles = profiles; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getState() { return state; } public void setState(String state) { this.state = state; } @Override public String toString() { return "Environment [name=" + name + ", profiles=" + Arrays.asList(profiles) + ", label=" + label + ", propertySources=" + propertySources + ", version=" + version + ", state=" + state + "]"; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/environment/PropertySource.java ================================================ /* * Copyright 2013-2014 the original author or authors. * * Licensed 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. */ package com.xiao.custom.config.client.environment; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Map; /** * Simple plain text serializable encapsulation of a named source of key-value pairs. * Basically a DTO for {@link PropertySource}, but also applicable outside the domain of a * Spring application. * * @author Dave Syer * */ public class PropertySource { private String name; private Map source; @JsonCreator public PropertySource(@JsonProperty("name") String name, @JsonProperty("source") Map source) { this.name = name; this.source = source; } public String getName() { return name; } public Map getSource() { return source; } @Override public String toString() { return "PropertySource [name=" + name + "]"; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/client/NettyClient.java ================================================ package com.xiao.custom.config.client.netty.client; import com.xiao.custom.config.client.configuration.ConfigClientProperties; import com.xiao.custom.config.client.netty.dto.CommandEnum; import com.xiao.custom.config.client.netty.dto.Message; import com.xiao.custom.config.client.netty.factory.CoderFactory; import com.xiao.custom.config.client.netty.factory.NamedThreadFactory; import com.xiao.custom.config.client.netty.handler.ServiceHandler; import com.xiao.custom.config.client.netty.util.RemotingUtil; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.IdleStateHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import javax.annotation.Resource; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * [简要描述]: netty客户端 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 16:42 * @since JDK 1.8 */ @Slf4j public class NettyClient implements ApplicationListener, DisposableBean { private String nettyServerHost = "localhost"; private int nettyServerPort = 8999; private AtomicBoolean started = new AtomicBoolean(false); /** * 重连检测任务 */ private ScheduledExecutorService reConnectExecutor; @Resource private ConfigClientProperties configClientProperties; @Autowired private ContextRefresher refresher; private Bootstrap boot; private Channel channel; public NettyClient() { log.info(">>> 初始化netty 客户端......"); } @Override public void destroy() throws Exception { } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { //是否使用自定义配置中心 if (!started.get() && configClientProperties.isCustom()) { log.info(">>> 配置中心服务地址:{}", configClientProperties.getUri()); nettyServerHost = parseHost(configClientProperties.getUri()); nettyServerPort = configClientProperties.getNettyPort(); init(); connect(); //2分钟检测一次连接情况 reConnectExecutor.scheduleAtFixedRate((Runnable) () -> check(), 1, 2, TimeUnit.MINUTES); } } /** * [简要描述]:发送消息
* [详细描述]:
* * @param message : * llxiao 2019/4/1 - 11:38 **/ public void pushMessage(Message message) { if (null != message && started.get()) { channel.writeAndFlush(message); } } private void login() { Message message = new Message(); message.setCommand(CommandEnum.LOGIN.getStatus()); message.setServerPort(configClientProperties.getServerPort()); message.setHostIp(RemotingUtil.parseLocalIP(channel)); message.setApplicationName(configClientProperties.getName()); pushMessage(message); } /** * 获取服务端IP地址 * * @param uri * @return */ private String parseHost(String uri) { URL url = null; try { url = new URL(uri); } catch (MalformedURLException e) { log.error("解析地址错误!"); } return url.getHost(); } private void init() { reConnectExecutor = new ScheduledThreadPoolExecutor(1, NamedThreadFactory .create("Netty client reconnect-", true)); EventLoopGroup group = new NioEventLoopGroup(); boot = new Bootstrap(); boot.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true); boot.handler(new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); //监听读写动作,10S后无服务器响应信息,4S后无客户端写动作,触发userEventTriggered发起心跳 pipeline.addLast(new IdleStateHandler(10, 4, 0)); //先解码 后编码 pipeline.addLast(CoderFactory.newDecoder()); pipeline.addLast(CoderFactory.newEncoder()); //业务处理 pipeline.addLast(new ServiceHandler(started, refresher)); } }); } private void connect() { log.info(">>> 客户端建立netty 连接,服务端-IP:{},Port:{}....", nettyServerHost, nettyServerPort); try { final ChannelFuture sync = boot.connect(nettyServerHost, nettyServerPort).sync(); channel = sync.channel(); started.set(sync.isSuccess()); login(); } catch (Exception e) { log.error(">>> 连接服务端异常,等待重连....", e); } } private void check() { if (log.isDebugEnabled()) { log.debug(">>> 执行检测任务....."); } if (!started.get()) { log.info(">>> 执行客户端重连操作..."); connect(); if (started.get()) { //重连成功后再次刷新下配置文件 refresher.refresh(); } } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/coder/ProtoDecoder.java ================================================ package com.xiao.custom.config.client.netty.coder; import com.xiao.custom.config.client.netty.util.ProtostuffUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; /** * [简要描述]: 编码 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 13:54 * @since JDK 1.8 */ public class ProtoDecoder extends ByteToMessageDecoder { private static final int PROTO_BUFF_FLAG = 4; private Class genericClass; public ProtoDecoder(Class cls) { this.genericClass = cls; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List list) throws Exception { if (byteBuf.readableBytes() < PROTO_BUFF_FLAG) { return; } byteBuf.markReaderIndex(); int dataLength = byteBuf.readInt(); if (dataLength < 0) { ctx.close(); } if (byteBuf.readableBytes() < dataLength) { byteBuf.resetReaderIndex(); } // 将ByteBuf转换为byte[] byte[] data = new byte[dataLength]; byteBuf.readBytes(data); // 将data转换成object Object obj = ProtostuffUtil.deserialize(data, genericClass); list.add(obj); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/coder/ProtoEncoder.java ================================================ package com.xiao.custom.config.client.netty.coder; import com.xiao.custom.config.client.netty.util.ProtostuffUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * [简要描述]: 解码 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 13:54 * @since JDK 1.8 */ @ChannelHandler.Sharable public class ProtoEncoder extends MessageToByteEncoder { private Class genericClass; public ProtoEncoder(Class cls) { this.genericClass = cls; } @Override protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception { // 序列化 if (genericClass.isInstance(msg)) { byte[] data = ProtostuffUtil.serialize(msg); byteBuf.writeInt(data.length); byteBuf.writeBytes(data); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/dto/CommandEnum.java ================================================ package com.xiao.custom.config.client.netty.dto; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 16:18 * @since JDK 1.8 */ public enum CommandEnum { /** * 心跳 */ IDLE(0), /** * 刷新 */ REFRESH(2), /** * 登录,绑定信息 */ LOGIN(1); int status; CommandEnum(int status) { this.status = status; } public int getStatus() { return status; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/dto/Message.java ================================================ package com.xiao.custom.config.client.netty.dto; import lombok.Data; /** * [简要描述]: Netty交互消息体 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 10:42 * @since JDK 1.8 */ @Data public class Message { /** * 0:心跳,1:登录以及绑定NETTY信息,2:刷新 */ private int command; /** * 消息体 */ private String message; /** * 返回状态 */ private int status; /** * 错误消息 */ private String errorMessage; /** * 客户端服务端口 */ private int serverPort; /** * 客户端服务IP */ private String hostIp; /** * 应用名称 */ private String applicationName; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/factory/CoderFactory.java ================================================ package com.xiao.custom.config.client.netty.factory; import com.xiao.custom.config.client.netty.coder.ProtoDecoder; import com.xiao.custom.config.client.netty.coder.ProtoEncoder; import com.xiao.custom.config.client.netty.dto.Message; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 14:31 * @since JDK 1.8 */ public class CoderFactory { public static ProtoDecoder newDecoder() { return new ProtoDecoder(Message.class); } public static ProtoEncoder newEncoder() { return new ProtoEncoder(Message.class); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/factory/NamedThreadFactory.java ================================================ /* * 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. */ package com.xiao.custom.config.client.netty.factory; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Thread factory to name the thread purposely * * @author jiangping * @version $Id: NamedThreadFactory.java, v 0.1 Sept 5, 2016 10:17:10 PM tao Exp $ */ public class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private final ThreadGroup group; private final String namePrefix; private final boolean isDaemon; public NamedThreadFactory() { this("ThreadPool"); } public NamedThreadFactory(String name) { this(name, false); } public NamedThreadFactory(String preffix, boolean daemon) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = preffix + "-" + poolNumber.getAndIncrement() + "-thread-"; isDaemon = daemon; } /** * Create a thread. * * @see ThreadFactory#newThread(Runnable) */ @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); t.setDaemon(isDaemon); if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } /** * 自定义线程factory * * @param namePrefix * @param daemon * @return */ public static ThreadFactory create(final String namePrefix, final boolean daemon) { return new NamedThreadFactory(namePrefix, daemon); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/handler/ServiceHandler.java ================================================ package com.xiao.custom.config.client.netty.handler; import com.xiao.custom.config.client.netty.dto.CommandEnum; import com.xiao.custom.config.client.netty.dto.Message; import com.xiao.custom.config.client.netty.util.RemotingUtil; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.context.refresh.ContextRefresher; import java.util.concurrent.atomic.AtomicBoolean; /** * [简要描述]: 服务端业务处理handler * [详细描述]: * 业务处理请使用线程池 * * @author llxiao * @version 1.0, 2019/3/30 17:16 * @since JDK 1.8 */ @ChannelHandler.Sharable @Slf4j public class ServiceHandler extends SimpleChannelInboundHandler { private static final Message HEARTBEAT_SEQUENCE = new Message(); private AtomicBoolean stated; private ContextRefresher refresher; public ServiceHandler(AtomicBoolean stated, ContextRefresher refresher) { super(); this.stated = stated; HEARTBEAT_SEQUENCE.setCommand(CommandEnum.IDLE.getStatus()); this.refresher = refresher; } @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Message message) throws Exception { //心跳消息 if (CommandEnum.IDLE.getStatus() == message.getCommand()) { if (log.isDebugEnabled()) { log.debug(">>> 接收到服务端返回心跳消息:{}", message); } } else if (CommandEnum.REFRESH.getStatus() == message.getCommand()) { log.info(">>> 服务端推送了刷新信息", message.getCommand()); refresher.refresh(); } else { log.info(">>> 业务处理"); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { log.warn(">>> 服务端异常,等待定时重连......"); stated.set(false); super.exceptionCaught(ctx, cause); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleState state = ((IdleStateEvent) evt).state(); if (state == IdleState.WRITER_IDLE) { //如果没有触发写事件则向服务器发送一次心跳包 if (log.isDebugEnabled()) { log.debug(">>> 客户端-IP:{},PORT:{}不活跃了,发起心跳请求.....", RemotingUtil .parseLocalIP(ctx.channel()), RemotingUtil.parseLocalPort(ctx.channel())); } // 发起心跳请求 ctx.writeAndFlush(HEARTBEAT_SEQUENCE); } else if (state == IdleState.READER_IDLE) { //如果没有收到服务端的写 则表示服务器超时 判断是否断开连接 log.warn("未收到服务器消息,服务器可能出现异常。需要进行重连操作.."); stated.set(false); if (ctx.channel().isOpen()) { ctx.close(); } } else { super.userEventTriggered(ctx, evt); } } else { super.userEventTriggered(ctx, evt); } } //建立连接时回调 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info(">>> 建立连接成功,服务端IP:{},PORT:{}", RemotingUtil.parseRemoteIP(ctx.channel()), RemotingUtil .parseRemotePort(ctx.channel())); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.warn(">>> 服务端不活跃了。IP:{},PORT:{}", RemotingUtil.parseRemoteIP(ctx.channel()), RemotingUtil .parseRemotePort(ctx.channel())); super.channelInactive(ctx); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/util/ProtostuffUtil.java ================================================ /* * Winner * 文件名 :ProtostuffUtil.java * 创建人 :llxiao * 创建时间:2018年5月2日 */ package com.xiao.custom.config.client.netty.util; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; import org.objenesis.Objenesis; import org.objenesis.ObjenesisStd; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * [简要描述]:序列化工具类(基于 Protostuff 实现)
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年5月2日 * @since JDK 1.8 */ public class ProtostuffUtil { private static Map, Schema> cachedSchema = new ConcurrentHashMap<>(); private static Objenesis objenesis = new ObjenesisStd(true); /** * 获取类的schema * * @param cls * @return */ @SuppressWarnings("unchecked") private static Schema getSchema(Class cls) { Schema schema = (Schema) cachedSchema.get(cls); if (schema == null) { schema = RuntimeSchema.createFrom(cls); if (schema != null) { cachedSchema.put(cls, schema); } } return schema; } /** * [简要描述]:序列化(对象 -> 字节数组)
* [详细描述]:
* * @param obj 序列化对象 * @return 对象字节数组 */ @SuppressWarnings("unchecked") public static byte[] serialize(T obj) { Class cls = (Class) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema schema = getSchema(cls); return ProtostuffIOUtil.toByteArray(obj, schema, buffer); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } /** * [简要描述]:反序列化(字节数组 -> 对象)
* [详细描述]:
* * @param data 数据 * @param cls class对象 * @return class对象 */ public static T deserialize(byte[] data, Class cls) { try { /* * 如果一个类没有参数为空的构造方法时候,那么你直接调用newInstance方法试图得到一个实例对象的时候是会抛出异常的 * 通过ObjenesisStd可以完美的避开这个问题 */ T message = (T) objenesis.newInstance(cls);// 实例化 Schema schema = getSchema(cls);// 获取类的schema ProtostuffIOUtil.mergeFrom(data, message, schema); return message; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/netty/util/RemotingUtil.java ================================================ /* * 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. */ package com.xiao.custom.config.client.netty.util; import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.net.*; import java.util.Enumeration; /** * Some utilities for remoting. * * @author jiangping * @version $Id: RemotingUtil.java, v 0.1 Mar 30, 2016 11:51:02 AM jiangping Exp $ */ @Slf4j public class RemotingUtil { /** * Parse the remote address of the channel. * * @param channel * @return */ public static String parseRemoteAddress(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final SocketAddress remote = channel.remoteAddress(); return doParse(remote != null ? remote.toString().trim() : StringUtils.EMPTY); } /** * Parse the local address of the channel. * * @param channel * @return */ public static String parseLocalAddress(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final SocketAddress local = channel.localAddress(); return doParse(local != null ? local.toString().trim() : StringUtils.EMPTY); } /** * Parse the remote host ip of the channel. * * @param channel * @return */ public static String parseRemoteIP(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getAddress().getHostAddress(); } return StringUtils.EMPTY; } /** * Parse the remote hostname of the channel. *

* Note: take care to use this method, for a reverse name lookup takes uncertain time in {@link InetAddress#getHostName}. * * @param channel * @return */ public static String parseRemoteHostName(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getAddress().getHostName(); } return StringUtils.EMPTY; } /** * Parse the local host ip of the channel. * * @param channel * @return */ public static String parseLocalIP(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress local = (InetSocketAddress) channel.localAddress(); if (local != null) { return local.getAddress().getHostAddress(); } return StringUtils.EMPTY; } /** * Parse the remote host port of the channel. * * @param channel * @return int */ public static int parseRemotePort(final Channel channel) { if (null == channel) { return -1; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getPort(); } return -1; } /** * Parse the local host port of the channel. * * @param channel * @return int */ public static int parseLocalPort(final Channel channel) { if (null == channel) { return -1; } final InetSocketAddress local = (InetSocketAddress) channel.localAddress(); if (local != null) { return local.getPort(); } return -1; } /** * Parse the socket address, omit the leading "/" if present. *

* e.g.1 /127.0.0.1:1234 -> 127.0.0.1:1234 * e.g.2 sofatest-2.stack.alipay.net/10.209.155.54:12200 -> 10.209.155.54:12200 * * @param socketAddress * @return String */ public static String parseSocketAddressToString(SocketAddress socketAddress) { if (socketAddress != null) { return doParse(socketAddress.toString().trim()); } return StringUtils.EMPTY; } /** * Parse the host ip of socket address. *

* e.g. /127.0.0.1:1234 -> 127.0.0.1 * * @param socketAddress * @return String */ public static String parseSocketAddressToHostIp(SocketAddress socketAddress) { final InetSocketAddress addrs = (InetSocketAddress) socketAddress; if (addrs != null) { InetAddress addr = addrs.getAddress(); if (null != addr) { return addr.getHostAddress(); } } return StringUtils.EMPTY; } /** * URL地址获取host信息 * * @param url * @return */ public static String getHost(String url) { String host = ""; if (StringUtils.isNotBlank(url)) { try { URL u = new URL(url); host = u.getHost(); } catch (Exception e) { log.error("Url错误,获取不到主机信息!"); } } return host; } /** * [简要描述]:获取本地IP地址
* [详细描述]:
* * @return java.lang.String * llxiao 2019/4/3 - 16:52 **/ public static String getLocalHost() { InetAddress inetAddress = getLocalHostLANAddress(); if (null != inetAddress) { return inetAddress.getHostName(); } else { return ""; } } /** * [简要描述]:获取本地的IP地址
* [详细描述]:
* * @return java.net.InetAddress * llxiao 2019/4/3 - 16:44 **/ public static InetAddress getLocalHostLANAddress() { InetAddress jdkSuppliedAddress = null; try { InetAddress candidateAddress = null; InetAddress inetAddr; NetworkInterface iface; // 遍历所有的网络接口 for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) { iface = (NetworkInterface) ifaces.nextElement(); // 在所有的接口下再遍历IP for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) { inetAddr = (InetAddress) inetAddrs.nextElement(); if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址 if (inetAddr.isSiteLocalAddress()) { // 如果是site-local地址,就是它了 return inetAddr; } else if (candidateAddress == null) { // site-local类型的地址未被发现,先记录候选地址 candidateAddress = inetAddr; } } } } if (candidateAddress != null) { jdkSuppliedAddress = candidateAddress; } else { // 如果没有发现 non-loopback地址.只能用最次选的方案 jdkSuppliedAddress = InetAddress.getLocalHost(); } } catch (Exception e) { log.error("获取本地IP地址错误,错误信息:", e); } return jdkSuppliedAddress; } /** *

    *
  1. if an address starts with a '/', skip it. *
  2. if an address contains a '/', substring it. *
* * @param addr * @return */ private static String doParse(String addr) { if (StringUtils.isBlank(addr)) { return StringUtils.EMPTY; } if (addr.charAt(0) == '/') { return addr.substring(1); } else { int len = addr.length(); for (int i = 1; i < len; ++i) { if (addr.charAt(i) == '/') { return addr.substring(i + 1); } } return addr; } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/refresh/api/RefreshController.java ================================================ package com.xiao.custom.config.client.refresh.api; import com.xiao.custom.config.client.refresh.service.ConfigRefreshService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: 配置刷新rest服务 * [详细描述]: 待考虑安全问题 * * @author llxiao * @version 1.0, 2019/1/29 14:18 * @since JDK 1.8 */ //@RestController //@RequestMapping("/config") public class RefreshController { /** * 成功标识 */ private static final int SUCCESS = 0; // @Autowired private ConfigRefreshService configRefreshService; /** * [简要描述]:刷新配置
* [详细描述]:仅支持post请求
*

* llxiao 2019/1/29 - 14:23 **/ @RequestMapping(value = "/refresh", method = RequestMethod.POST) public int refresh() { configRefreshService.refresh(); return SUCCESS; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/refresh/component/RefreshBeanConfig.java ================================================ package com.xiao.custom.config.client.refresh.component; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * [简要描述]: 注解扫描refresh包下所有的bean
* [详细描述]: 主要提供刷新配置文件的入口
* ComponentScan默认会扫描该类所在的包下的所有bean文件 * * @author llxiao * @version 1.0, 2019/1/29 14:07 * @since JDK 1.8 */ //@Configuration //@ComponentScan public class RefreshBeanConfig { } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/refresh/service/ConfigRefreshService.java ================================================ package com.xiao.custom.config.client.refresh.service; /** * [简要描述]: 配置刷新服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/29 14:19 * @since JDK 1.8 */ public interface ConfigRefreshService { /** * [简要描述]:刷新spring容器中的变更的配置
* [详细描述]:
*

* llxiao 2019/1/29 - 14:19 **/ void refresh(); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/java/com/xiao/custom/config/client/refresh/service/impl/ConfigRefreshServiceImpl.java ================================================ package com.xiao.custom.config.client.refresh.service.impl; import com.xiao.custom.config.client.refresh.service.ConfigRefreshService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.stereotype.Service; /** * [简要描述]: 配置刷新服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/29 14:20 * @since JDK 1.8 */ //@Service @Slf4j public class ConfigRefreshServiceImpl implements ConfigRefreshService { // @Autowired private ContextRefresher refresher; /** * [简要描述]:刷新spring容器中的变更的配置
* [详细描述]:
*

* llxiao 2019/1/29 - 14:19 **/ @Override public void refresh() { if (log.isDebugEnabled()) { log.debug("开始执行配置刷新动作......................"); } refresher.refresh(); if (log.isDebugEnabled()) { log.debug("配置刷新完成......................"); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-client/src/main/resources/META-INF/spring.factories ================================================ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xiao.custom.config.client.configuration.ConfigClientAutoConfiguration # Bootstrap components org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.xiao.custom.config.client.configuration.ConfigServiceBootstrapConfiguration,\ com.xiao.custom.config.client.configuration.DiscoveryClientConfigServiceBootstrapConfiguration ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-dependencies/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-dependencies org.springframework.cloud spring-cloud-starter-config com.xiao.skywalking.demo custom-config-client ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-pojo org.apache.commons commons-lang3 3.4 commons-collections commons-collections 3.2.2 org.projectlombok lombok true org.mybatis mybatis 3.5.6 compile ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/common/BaseQuery.java ================================================ package com.xiao.custom.config.pojo.common; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:04 * @since JDK 1.8 */ @Data public class BaseQuery { int pageNum = 1; int pageSize = 10; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ApplicationConfigDto.java ================================================ package com.xiao.custom.config.pojo.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2019/01/07 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ApplicationConfigDto { // private Long id; //关联的应用ID private Long applicationId; //配置项KEY private String itemKey; //配置项值 private String itemValue; //配置描述 private String itemDesc; //创建时间 private Date createTime; //更新时间 private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ApplicationDto.java ================================================ package com.xiao.custom.config.pojo.dto; import com.xiao.custom.config.pojo.entity.Application; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ApplicationDto { // private Long id; //应用名称 private String application; //应用描述 private String applicationName; // private String label; //环境 private String profile; // private Date createTime; // private Date updateTime; //所属区域 private Long regionId; //区域名称 private String regionName; private String groupIds; public static Application convertToEntity(ApplicationDto applicationConfigDto) { Application applicationConfig = null; if (applicationConfigDto != null) { applicationConfig = new Application(); applicationConfig.setApplicationName(applicationConfigDto.getApplicationName()); applicationConfig.setCreateTime(applicationConfigDto.getCreateTime()); applicationConfig.setLabel(applicationConfigDto.getLabel()); applicationConfig.setProfile(applicationConfigDto.getProfile()); applicationConfig.setRegionId(applicationConfigDto.getRegionId()); applicationConfig.setId(applicationConfigDto.getId()); applicationConfig.setUpdateTime(applicationConfigDto.getUpdateTime()); applicationConfig.setApplication(applicationConfigDto.getApplication()); } return applicationConfig; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ClientHostInfoDto.java ================================================ package com.xiao.custom.config.pojo.dto; import lombok.Data; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 14:23 * @since JDK 1.8 */ @Data public class ClientHostInfoDto { private Long id; /** * 应用ID */ private Long applicationClientId; /** * 应用 */ private String application; private String nettyIp; private Integer nettyPort; private String hostIp; private Integer hostPort; private Integer status; private Date createTime; private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ConfigItemDto.java ================================================ package com.xiao.custom.config.pojo.dto; import com.xiao.custom.config.pojo.entity.ConfigItem; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ConfigItemDto { // private Long id; //配置项KEY private String itemKey; //配置项值 private String itemValue; //配置项描述 private String itemDesc; // private Date createTime; // private Date updateTime; //0可用,1不可用 private Integer status; //应用类型,0通用,1开发环境,2测试环境,3生产环境,4其他。默认通用类型 private Integer itemType; public static ConfigItem convertToEntity(ConfigItemDto configItemDto) { ConfigItem configItem = null; if (configItemDto != null) { configItem = new ConfigItem(); configItem.setCreateTime(configItemDto.getCreateTime()); configItem.setItemDesc(configItemDto.getItemDesc()); configItem.setItemKey(configItemDto.getItemKey()); configItem.setItemValue(configItemDto.getItemValue()); configItem.setStatus(configItemDto.getStatus()); configItem.setId(configItemDto.getId()); configItem.setItemType(configItemDto.getItemType()); configItem.setUpdateTime(configItemDto.getUpdateTime()); } return configItem; } public static ConfigItemDto convertToDto(ConfigItem configItem) { ConfigItemDto configItemDto = null; if (configItem != null) { configItemDto = new ConfigItemDto(); configItemDto.setCreateTime(configItem.getCreateTime()); configItemDto.setId(configItem.getId()); configItemDto.setItemDesc(configItem.getItemDesc()); configItemDto.setItemKey(configItem.getItemKey()); configItemDto.setItemType(configItem.getItemType()); configItemDto.setItemValue(configItem.getItemValue()); configItemDto.setStatus(configItem.getStatus()); configItemDto.setUpdateTime(configItem.getUpdateTime()); } return configItemDto; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ConfigItemGroupDto.java ================================================ package com.xiao.custom.config.pojo.dto; import com.xiao.custom.config.pojo.entity.ConfigItemGroup; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ConfigItemGroupDto { // private Long id; //组名称 private String groupName; //组描述 private String groupDesc; // private Date createTime; // private Date updateTime; public static ConfigItemGroup convertToEntity(ConfigItemGroupDto configItemGroupDto) { ConfigItemGroup configItemGroup = null; if (configItemGroupDto != null) { configItemGroup = new ConfigItemGroup(); configItemGroup.setCreateTime(configItemGroupDto.getCreateTime()); configItemGroup.setGroupDesc(configItemGroupDto.getGroupDesc()); configItemGroup.setGroupName(configItemGroupDto.getGroupName()); configItemGroup.setId(configItemGroupDto.getId()); configItemGroup.setUpdateTime(configItemGroupDto.getUpdateTime()); } return configItemGroup; } public static ConfigItemGroupDto convertToDto(ConfigItemGroup configItemGroup) { ConfigItemGroupDto dto = null; if (configItemGroup != null) { dto = new ConfigItemGroupDto(); dto.setCreateTime(configItemGroup.getCreateTime()); dto.setGroupDesc(configItemGroup.getGroupDesc()); dto.setGroupName(configItemGroup.getGroupName()); dto.setId(configItemGroup.getId()); dto.setUpdateTime(configItemGroup.getUpdateTime()); } return dto; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/RegionDto.java ================================================ package com.xiao.custom.config.pojo.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class RegionDto { // private Long id; //区域名称 private String regionName; //区域描述 private String regionDesc; //创建时间 private Date createTime; //更新时间 private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/dto/ServerHostConfigDto.java ================================================ package com.xiao.custom.config.pojo.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ServerHostConfigDto { // private Long id; //IP地址 private String serverHost; //服务描述 private String serverDesc; //关联区域 private Long regionId; //区域名称 private String regionName; // private Date createTime; // private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/Application.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Application { // private Long id; //应用 private String application; //应用描述 private String applicationName; // private String label; //环境 private String profile; // private Date createTime; // private Date updateTime; //所属区域 private Long regionId; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ApplicationConfig.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2019/01/07 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ApplicationConfig { // private Long id; //关联的应用ID private Long applicationId; //配置项KEY private String itemKey; //配置项值 private String itemValue; //配置描述 private String itemDesc; //创建时间 private Date createTime; //更新时间 private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ApplicationItemGroupRelation.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ApplicationItemGroupRelation { // private Long id; //应用ID private Long applicationId; //配置组ID private Long itemGroupId; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/AuthUser.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.Data; import java.sql.Timestamp; /** * @author : JoeTao * createAt: 2018/9/14 */ @Data public class AuthUser { private long id; private String username; private String password; private String nickname; /** * 最后一次重置密码时间 */ private Timestamp lastResetTime; private Role role; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ClientApplication.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.*; import java.util.Date; /** * t_client_application * Created by Mybatis Generator on 2019/01/29 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class ClientApplication { private Long id; private String application; private Date createTime; private Date updateTime; private Integer status; private String profile; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ClientHostInfo.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.*; import java.util.Date; /** * t_client_host_info * Created by Mybatis Generator on 2019/01/29 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ToString public class ClientHostInfo { /** * 在线 */ public static final int ONLINE = 0; private Long id; private Long clientApplicationId; private String hostIp; private Integer hostPort; private String nettyIp; private Integer nettyPort; private Integer status; private Date createTime; private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ClientInfo.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.Data; import java.util.List; /** * [简要描述]: 配置中心连接的应用客户端信息 * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/29 14:37 * @since JDK 1.8 */ @Data public class ClientInfo { private String applicationName; private List hostInofs; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ConfigItem.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ConfigItem { // private Long id; //配置项KEY private String itemKey; //配置项值 private String itemValue; //配置项描述 private String itemDesc; // private Date createTime; // private Date updateTime; //0可用,1不可用 private Integer status; //应用类型,0通用,1开发环境,2测试环境,3生产环境,4其他。默认通用类型 private Integer itemType; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ConfigItemGroup.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ConfigItemGroup { // private Long id; //组名称 private String groupName; //组描述 private String groupDesc; // private Date createTime; // private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ConfigItemGroupRelation.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ConfigItemGroupRelation { // private Long id; //配置项ID private Long itemId; //组ID private Long groupId; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/Region.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Region { // private Long id; //区域名称 private String regionName; //区域描述 private String regionDesc; //创建时间 private Date createTime; //更新时间 private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/Role.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author : JoeTao * createAt: 2018/9/17 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Role { private Long id; private String name; private String nameZh; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/entity/ServerHostConfig.java ================================================ package com.xiao.custom.config.pojo.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ServerHostConfig { // private Long id; //IP地址 private String serverHost; //服务描述 private String serverDesc; //关联区域 private Long regionId; // private Date createTime; // private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ApplicationConfigMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.entity.ApplicationConfig; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; import java.util.List; /** * 应用对应的私有配置属性 * Created by Mybatis Generator on 2019/01/07 */ public interface ApplicationConfigMapper { int deleteByPrimaryKey(Long id); int insert(ApplicationConfig record); int insertSelective(ApplicationConfig record); ApplicationConfig selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ApplicationConfig record); int updateByPrimaryKey(ApplicationConfig record); /** * [简要描述]:分页查询应用关联的私有属性
* [详细描述]:
* * @param query : * @return java.util.List * llxiao 2019/1/7 - 15:27 **/ List pageQuery(ApplicationConfigQuery query); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ApplicationItemGroupRelationMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.entity.ApplicationItemGroupRelation; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ApplicationItemGroupRelationMapper { int deleteByPrimaryKey(Long id); int insert(ApplicationItemGroupRelation record); int insertSelective(ApplicationItemGroupRelation record); ApplicationItemGroupRelation selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ApplicationItemGroupRelation record); int updateByPrimaryKey(ApplicationItemGroupRelation record); /** * [简要描述]:绑定应用与多个配置组
* [详细描述]:
* * @param groupIdArr : * @param appId : * @return void * jun.liu 2018/11/28 - 14:10 **/ int batchSave(@Param("groupIdArr") String[] groupIdArr, @Param("appId") Long appId); /** * [简要描述]:删除绑定
* [详细描述]:
* * @param groupIdArr : * @param appId : * @return int * jun.liu 2018/11/28 - 15:41 **/ int batchDelete(@Param("groupIdArr") String[] groupIdArr, @Param("appId") Long appId); /** * [简要描述]:应用ID删除关联配置信息
* [详细描述]:
* * @param appId : 应用ID * @return int * llxiao 2018/12/24 - 10:34 **/ int deleteByAppId(@Param("appId") Long appId); /** * [简要描述]:组ID统计关联的应用数量
* [详细描述]:
* * @param groupId : * @return int * llxiao 2019/1/2 - 17:24 **/ int countByGroupId(String groupId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ApplicationMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.entity.Application; import com.xiao.custom.config.pojo.query.AppQuery; import java.util.List; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ApplicationMapper { int deleteByPrimaryKey(Long id); int insert(Application record); int insertSelective(Application record); Application selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(Application record); int updateByPrimaryKey(Application record); /** * [简要描述]:根据条件查询
* [详细描述]:
* * @param appQuery : * @return java.util.List * jun.liu 2018/11/26 - 17:08 **/ List pageApplicationConfig(AppQuery appQuery); /** * [简要描述]:区域ID统计应用
* [详细描述]:
* * @param regionId : * @return int * llxiao 2019/1/2 - 17:55 **/ Integer countByRegionId(Long regionId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/AuthMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; /** * @author JoeTao * createAt: 2018/9/17 */ @Repository public interface AuthMapper { /** * 根据用户名查找用户 * * @param username * @return */ AuthUser findByUsername(@Param("username") String username); /** * 创建新用户 * * @param userDetail */ void insert(AuthUser userDetail); /** * 创建用户角色 * * @param userId * @param roleId * @return */ int insertRole(@Param("userId") long userId, @Param("roleId") long roleId); /** * 根据角色id查找角色 * * @param roleId * @return */ Role findRoleById(@Param("roleId") long roleId); /** * 根据用户id查找该用户角色 * * @param userId * @return */ Role findRoleByUserId(@Param("userId") long userId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ClientApplicationMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.entity.ClientApplication; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2019/01/29 */ public interface ClientApplicationMapper { int deleteByPrimaryKey(Long id); int insert(ClientApplication record); int insertSelective(ClientApplication record); ClientApplication selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ClientApplication record); int updateByPrimaryKey(ClientApplication record); /** * [简要描述]:修改应用状态
* [详细描述]:
* * @param application : * @param profile : * @param status : * @return void * llxiao 2019/1/30 - 16:11 **/ void updateStatus(@Param("application") String application, @Param("profile") String profile, @Param("status") int status); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ClientHostInfoMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.entity.ClientHostInfo; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; import org.apache.ibatis.annotations.Param; import java.util.List; /** * Created by Mybatis Generator on 2019/01/29 */ public interface ClientHostInfoMapper { int deleteByPrimaryKey(Long id); int insert(ClientHostInfo record); int insertSelective(ClientHostInfo record); ClientHostInfo selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ClientHostInfo record); int updateByPrimaryKey(ClientHostInfo record); /** * [简要描述]:应用名称+环境查询已连接的应用信息
* [详细描述]:
* * @param application : * @param profile : * @return java.util.List * llxiao 2019/1/30 - 11:15 **/ List queryByApplication(@Param("application") String application, @Param("profile") String profile); /** * [简要描述]:修改服务状态
* [详细描述]:0在线,1下线
* * @param id : * @param status: * @return int * llxiao 2019/1/30 - 14:18 **/ int updateStatus(@Param("id") Long id, @Param("status") int status); /** * [简要描述]:分页查询客户端连接信息
* [详细描述]:
* * @param query : 查询条件 * @return java.util.List * llxiao 2019/3/27 - 14:46 **/ List pageQuery(ClientHostInfoQuery query); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ConfigItemGroupMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.entity.ConfigItemGroup; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import org.apache.ibatis.annotations.Param; import java.util.List; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ConfigItemGroupMapper { int deleteByPrimaryKey(Long id); int insert(ConfigItemGroup record); int insertSelective(ConfigItemGroup record); ConfigItemGroup selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ConfigItemGroup record); int updateByPrimaryKey(ConfigItemGroup record); /** * [简要描述]:通过条件查询
* [详细描述]:
* * @param configItemGroupQuery : * @return java.util.List * jun.liu 2018/11/27 - 8:34 **/ List pageConfigItemGroup(ConfigItemGroupQuery configItemGroupQuery); /** * [简要描述]:获取已绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return java.util.List * jun.liu 2018/11/28 - 15:03 **/ List pageRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery); /** * [简要描述]:获取未绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return java.util.List * jun.liu 2018/11/28 - 15:32 **/ List pageNotRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery); /** * [简要描述]:批量删除
* [详细描述]:
* * @param idArr : * @return int * jun.liu 2018/12/21 - 15:19 **/ int batchDelete(@Param("idArr") String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ConfigItemGroupRelationMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.entity.ConfigItemGroupRelation; import org.apache.ibatis.annotations.Param; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ConfigItemGroupRelationMapper { int deleteByPrimaryKey(Long id); int insert(ConfigItemGroupRelation record); int insertSelective(ConfigItemGroupRelation record); ConfigItemGroupRelation selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ConfigItemGroupRelation record); int updateByPrimaryKey(ConfigItemGroupRelation record); /** * [简要描述]:批量绑定
* [详细描述]:
* * @param itemIdArr : * @param groupId : * @return int * jun.liu 2018/11/28 - 9:48 **/ int batchSave(@Param("itemIdArr") String[] itemIdArr, @Param("groupId") Long groupId); /** * [简要描述]:批量删除
* [详细描述]:
* * @param itemIdArr : * @param groupId : * @return int * jun.liu 2018/11/28 - 15:13 **/ int batchDelete(@Param("itemIdArr") String[] itemIdArr, @Param("groupId") Long groupId); /** * [简要描述]:通过itemId批量删除配置项和配置组关联关系
* [详细描述]:
* * @param idArr : * @return int * jun.liu 2018/12/24 - 15:50 **/ int batchDeleteByItemId(@Param("idArr") String[] idArr); /** * [简要描述]:通过groupId批量删除配置项和配置组关联关系
* [详细描述]:
* * @param idArr : * @return void * jun.liu 2018/12/25 - 8:59 **/ int batchDeleteByGroupId(@Param("idArr") String[] idArr); /** * [简要描述]:统计配置项关联的配置组的数据
* [详细描述]:
* * @param itemId : * @return int * llxiao 2019/1/2 - 17:38 **/ int countByItemId(String itemId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ConfigItemMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.entity.ConfigItem; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import org.apache.ibatis.annotations.Param; import java.util.List; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ConfigItemMapper { int deleteByPrimaryKey(Long id); int insert(ConfigItem record); int insertSelective(ConfigItem record); ConfigItem selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ConfigItem record); int updateByPrimaryKey(ConfigItem record); /** * [简要描述]:通过条件查询
* [详细描述]:
* * @param configItemQuery : * @return java.util.List * jun.liu 2018/11/27 - 9:40 **/ List pageConfigItem(ConfigItemQuery configItemQuery); /** * [简要描述]:分页获取已关联group的配置项
* [详细描述]:
* * @param configItemQuery : * @return java.util.List * jun.liu 2018/11/27 - 17:05 **/ List pageRefConfigItemWithGroup(ConfigItemQuery configItemQuery); /** * [简要描述]:分页获取未关联group的配置项
* [详细描述]:
* * @param configItemQuery : * @return java.util.List * jun.liu 2018/11/28 - 10:04 **/ List pageNotRefConfigItemWithGroup(ConfigItemQuery configItemQuery); /** * [简要描述]:批量删除
* [详细描述]:
* * @param idArr : * @return int * jun.liu 2018/12/20 - 19:17 **/ int batchDelete(@Param("idArr") String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/RegionMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.entity.Region; import com.xiao.custom.config.pojo.query.RegionQuery; import org.apache.ibatis.annotations.Param; import java.util.List; /** * Created by Mybatis Generator on 2018/11/23 */ public interface RegionMapper { int deleteByPrimaryKey(Long id); int insert(Region record); int insertSelective(Region record); Region selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(Region record); int updateByPrimaryKey(Region record); /** * [简要描述]:通过条件查询
* [详细描述]:
* * @param regionQuery : * @return java.util.List * jun.liu 2018/11/26 - 17:27 **/ List pageRegion(RegionQuery regionQuery); /** * [简要描述]:查询所有的区域 * [详细描述]:
* * @return java.util.List * mjye 2018/12/21 - 16:51 **/ List selectRegion(); /** * [简要描述]:批量删除 * [详细描述]:
* * @param idArr : * @return int * mjye 2018/12/25 - 10:37 **/ int batchDelete(@Param("idArr") String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/mapper/ServerHostConfigMapper.java ================================================ package com.xiao.custom.config.pojo.mapper; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.entity.ServerHostConfig; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; import java.util.List; /** * Created by Mybatis Generator on 2018/11/23 */ public interface ServerHostConfigMapper { int deleteByPrimaryKey(Long id); int insert(ServerHostConfig record); int insertSelective(ServerHostConfig record); ServerHostConfig selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ServerHostConfig record); int updateByPrimaryKey(ServerHostConfig record); /** * [简要描述]:通过条件查询
* [详细描述]:
* * @param serverHostConfigQuery : * @return java.util.List * jun.liu 2018/11/27 - 9:51 **/ List pageServerHostConfig(ServerHostConfigQuery serverHostConfigQuery); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/AppQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 16:51 * @since JDK 1.8 */ @Data public class AppQuery extends BaseQuery { //应用 private String application; //应用描述 private String applicationName; //环境 private String profile; //创建开始时间 private Data startTime; //创建结束时间 private Data endTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/ApplicationConfigQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/7 15:18 * @since JDK 1.8 */ @Data public class ApplicationConfigQuery extends BaseQuery { private Long applicationId; private String itemKey; private String itemDesc; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/ClientHostInfoQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: 配置中心客户端连接列查询条件 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 14:21 * @since JDK 1.8 */ @Data public class ClientHostInfoQuery extends BaseQuery { /** * IP查询 */ private String hostIp; /** * 状态查询 */ private Integer status; /** * 应用查询 */ private String application; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/ConfigItemGroupQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:53 * @since JDK 1.8 */ @Data public class ConfigItemGroupQuery extends BaseQuery { //组名称 private String groupName; private String groupDesc; private String createTime; private String updateTime; private Long appId; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/ConfigItemQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:36 * @since JDK 1.8 */ @Data public class ConfigItemQuery extends BaseQuery { //配置项KEY private String itemKey; //配置项值 private String itemValue; private String itemDesc; //0可用,1不可用 private Integer status; //应用类型,0通用,1开发环境,2测试环境,3生产环境,4其他。默认通用类型 private Integer itemType; private String createTime; private String updateTime; private Long groupId; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/RegionQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:24 * @since JDK 1.8 */ @Data public class RegionQuery extends BaseQuery { //区域名称 private String regionName; //开始时间 private Date createTime; //结束时间 private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/java/com/xiao/custom/config/pojo/query/ServerHostConfigQuery.java ================================================ package com.xiao.custom.config.pojo.query; import com.xiao.custom.config.pojo.common.BaseQuery; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:47 * @since JDK 1.8 */ @Data public class ServerHostConfigQuery extends BaseQuery { //IP地址 private String serverHost; //服务描述 private String serverDesc; //开始时间 private String createTime; //结束时间 private String updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ApplicationConfigMapper.xml ================================================ id, application_id, item_key, item_value, item_desc, create_time, update_time delete from t_application_config where id = #{id,jdbcType=BIGINT} insert into t_application_config (id, application_id, item_key, item_value, item_desc, create_time, update_time) values (#{id,jdbcType=BIGINT}, #{applicationId,jdbcType=BIGINT}, #{itemKey,jdbcType=VARCHAR}, #{itemValue,jdbcType=VARCHAR}, #{itemDesc,jdbcType=VARCHAR}, now(), #{updateTime,jdbcType=TIMESTAMP}) insert into t_application_config id, application_id, item_key, item_value, item_desc, create_time, update_time, #{id,jdbcType=BIGINT}, #{applicationId,jdbcType=BIGINT}, #{itemKey,jdbcType=VARCHAR}, #{itemValue,jdbcType=VARCHAR}, #{itemDesc,jdbcType=VARCHAR}, now(), update t_application_config application_id = #{applicationId,jdbcType=BIGINT}, item_key = #{itemKey,jdbcType=VARCHAR}, item_value = #{itemValue,jdbcType=VARCHAR}, item_desc = #{itemDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = now(), where id = #{id,jdbcType=BIGINT} update t_application_config set application_id = #{applicationId,jdbcType=BIGINT}, item_key = #{itemKey,jdbcType=VARCHAR}, item_value = #{itemValue,jdbcType=VARCHAR}, item_desc = #{itemDesc,jdbcType=VARCHAR}, update_time = now() where id = #{id,jdbcType=BIGINT} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ApplicationItemGroupRelationMapper.xml ================================================ id, application_id, item_group_id delete from t_application_item_group_relation where id = #{id,jdbcType=BIGINT} insert into t_application_item_group_relation (id, application_id, item_group_id ) values (#{id,jdbcType=BIGINT}, #{applicationId,jdbcType=BIGINT}, #{itemGroupId,jdbcType=BIGINT} ) insert into t_application_item_group_relation id, application_id, item_group_id, #{id,jdbcType=BIGINT}, #{applicationId,jdbcType=BIGINT}, #{itemGroupId,jdbcType=BIGINT}, update t_application_item_group_relation application_id = #{applicationId,jdbcType=BIGINT}, item_group_id = #{itemGroupId,jdbcType=BIGINT}, where id = #{id,jdbcType=BIGINT} update t_application_item_group_relation set application_id = #{applicationId,jdbcType=BIGINT}, item_group_id = #{itemGroupId,jdbcType=BIGINT} where id = #{id,jdbcType=BIGINT} insert into t_application_item_group_relation (application_id, item_group_id) values (#{appId,jdbcType=BIGINT}, #{groupId,jdbcType=BIGINT}) delete from t_application_item_group_relation where application_id = #{appId} and item_group_id in #{groupId} delete from t_application_item_group_relation where application_id = #{appId} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ApplicationMapper.xml ================================================ id, application, application_name, label, profile, create_time, update_time, region_id delete from t_application where id = #{id,jdbcType=BIGINT} insert into t_application (id, application, application_name, label, profile, create_time, update_time, region_id) values (#{id,jdbcType=BIGINT}, #{application,jdbcType=VARCHAR}, #{applicationName,jdbcType=VARCHAR}, #{label,jdbcType=VARCHAR}, #{profile,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{regionId,jdbcType=BIGINT}) insert into t_application id, application, application_name, label, profile, create_time, update_time, region_id, #{id,jdbcType=BIGINT}, #{application,jdbcType=VARCHAR}, #{applicationName,jdbcType=VARCHAR}, #{label,jdbcType=VARCHAR}, #{profile,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{regionId,jdbcType=BIGINT}, update t_application application = #{application,jdbcType=VARCHAR}, application_name = #{applicationName,jdbcType=VARCHAR}, label = #{label,jdbcType=VARCHAR}, profile = #{profile,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, region_id = #{regionId,jdbcType=BIGINT}, where id = #{id,jdbcType=BIGINT} update t_application set application = #{application,jdbcType=VARCHAR}, application_name = #{applicationName,jdbcType=VARCHAR}, label = #{label,jdbcType=VARCHAR}, profile = #{profile,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, region_id = #{regionId,jdbcType=BIGINT} where id = #{id,jdbcType=BIGINT} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/AuthMapper.xml ================================================ insert into t_sys_user (username, password,nickname) VALUES (#{username}, #{password},#{nikename}); insert into t_sys_user_role (user_id, role_id) VALUES (#{userId}, #{roleId}); ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ClientApplicationMapper.xml ================================================ id, application, create_time, update_time, status, profile delete from t_client_application where id = #{id,jdbcType=BIGINT} insert into t_client_application (id, application, create_time, update_time, status, profile ) values (#{id,jdbcType=BIGINT}, #{application,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER}, #{profile,jdbcType=VARCHAR} ) insert into t_client_application id, application, create_time, update_time, status, profile, #{id,jdbcType=BIGINT}, #{application,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER}, #{profile,jdbcType=VARCHAR}, update t_client_application application = #{application,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, status = #{status,jdbcType=INTEGER}, profile = #{profile,jdbcType=VARCHAR}, where id = #{id,jdbcType=BIGINT} update t_client_application set application = #{application,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, status = #{status,jdbcType=INTEGER}, profile = #{profile,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} update t_client_application set status = #{status},update_time = now() where application = #{application} and profile = #{profile} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ClientHostInfoMapper.xml ================================================ id, client_application_id, host_ip, host_port,netty_ip,netty_port, status, create_time, update_time delete from t_client_host_info where id = #{id,jdbcType=BIGINT} insert into t_client_host_info (id, client_application_id, host_ip, host_port, status, create_time, update_time) values (#{id,jdbcType=BIGINT}, #{clientApplicationId,jdbcType=BIGINT}, #{hostIp,jdbcType=VARCHAR}, #{hostPort,jdbcType=INTEGER}, #{status,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) insert into t_client_host_info id, client_application_id, host_ip, host_port, status, create_time, update_time, #{id,jdbcType=BIGINT}, #{clientApplicationId,jdbcType=BIGINT}, #{hostIp,jdbcType=VARCHAR}, #{hostPort,jdbcType=INTEGER}, #{status,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, update t_client_host_info client_application_id = #{clientApplicationId,jdbcType=BIGINT}, host_ip = #{hostIp,jdbcType=VARCHAR}, host_port = #{hostPort,jdbcType=INTEGER}, status = #{status,jdbcType=INTEGER}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_client_host_info set client_application_id = #{clientApplicationId,jdbcType=BIGINT}, host_ip = #{hostIp,jdbcType=VARCHAR}, host_port = #{hostPort,jdbcType=INTEGER}, status = #{status,jdbcType=INTEGER}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = now() where id = #{id,jdbcType=BIGINT} update t_client_host_info set update_time = now(),status = #{status} where id = #{id} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ConfigItemGroupMapper.xml ================================================ id, group_name, group_desc, create_time, update_time delete from t_config_item_group where id = #{id,jdbcType=BIGINT} insert into t_config_item_group (id, group_name, group_desc, create_time, update_time) values (#{id,jdbcType=BIGINT}, #{groupName,jdbcType=VARCHAR}, #{groupDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) insert into t_config_item_group id, group_name, group_desc, create_time, update_time, #{id,jdbcType=BIGINT}, #{groupName,jdbcType=VARCHAR}, #{groupDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, update t_config_item_group group_name = #{groupName,jdbcType=VARCHAR}, group_desc = #{groupDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_config_item_group set group_name = #{groupName,jdbcType=VARCHAR}, group_desc = #{groupDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=BIGINT} delete from t_config_item_group where id in #{id} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ConfigItemGroupRelationMapper.xml ================================================ id, item_id, group_id delete from t_config_item_group_relation where id = #{id,jdbcType=BIGINT} insert into t_config_item_group_relation (id, item_id, group_id ) values (#{id,jdbcType=BIGINT}, #{itemId,jdbcType=BIGINT}, #{groupId,jdbcType=BIGINT} ) insert into t_config_item_group_relation id, item_id, group_id, #{id,jdbcType=BIGINT}, #{itemId,jdbcType=BIGINT}, #{groupId,jdbcType=BIGINT}, update t_config_item_group_relation item_id = #{itemId,jdbcType=BIGINT}, group_id = #{groupId,jdbcType=BIGINT}, where id = #{id,jdbcType=BIGINT} update t_config_item_group_relation set item_id = #{itemId,jdbcType=BIGINT}, group_id = #{groupId,jdbcType=BIGINT} where id = #{id,jdbcType=BIGINT} insert into t_config_item_group_relation(item_id, group_id) values (#{itemId},#{groupId}) delete from t_config_item_group_relation where group_id = #{groupId} and item_id in #{itemId} delete from t_config_item_group_relation where item_id in #{itemId} delete from t_config_item_group_relation where group_id in #{groupId} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ConfigItemMapper.xml ================================================ id, item_key, item_value, item_desc, create_time, update_time, status, item_type delete from t_config_item where id = #{id,jdbcType=BIGINT} insert into t_config_item (id, item_key, item_value, item_desc, create_time, update_time, status,item_type) values (#{id,jdbcType=BIGINT}, #{itemKey,jdbcType=VARCHAR}, #{itemValue,jdbcType=VARCHAR}, #{itemDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER}, #{itemType,jdbcType=INTEGER}) insert into t_config_item id, item_key, item_value, item_desc, create_time, update_time, status, item_type, #{id,jdbcType=BIGINT}, #{itemKey,jdbcType=VARCHAR}, #{itemValue,jdbcType=VARCHAR}, #{itemDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER}, #{itemType,jdbcType=INTEGER}, update t_config_item item_key = #{itemKey,jdbcType=VARCHAR}, item_value = #{itemValue,jdbcType=VARCHAR}, item_desc = #{itemDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, status = #{status,jdbcType=INTEGER}, item_type = #{itemType,jdbcType=INTEGER}, where id = #{id,jdbcType=BIGINT} update t_config_item set item_key = #{itemKey,jdbcType=VARCHAR}, item_value = #{itemValue,jdbcType=VARCHAR}, item_desc = #{itemDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, status = #{status,jdbcType=INTEGER}, item_type = #{itemType,jdbcType=INTEGER} where id = #{id,jdbcType=BIGINT} delete from t_config_item where id in #{id} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/RegionMapper.xml ================================================ id, region_name, region_desc, create_time, update_time delete from t_region where id = #{id,jdbcType=BIGINT} insert into t_region (id, region_name, region_desc, create_time, update_time) values (#{id,jdbcType=BIGINT}, #{regionName,jdbcType=VARCHAR}, #{regionDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}) insert into t_region id, region_name, region_desc, create_time, update_time, #{id,jdbcType=BIGINT}, #{regionName,jdbcType=VARCHAR}, #{regionDesc,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, update t_region region_name = #{regionName,jdbcType=VARCHAR}, region_desc = #{regionDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_region set region_name = #{regionName,jdbcType=VARCHAR}, region_desc = #{regionDesc,jdbcType=VARCHAR}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=BIGINT} delete from t_region where id in #{id} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-pojo/src/main/resources/com/xiao/custom/config/pojo/mapper/ServerHostConfigMapper.xml ================================================ id, server_host, server_desc, region_id, create_time, update_time delete from t_server_host_config where id = #{id,jdbcType=BIGINT} insert into t_server_host_config (id, server_host, server_desc, region_id, create_time, update_time ) values (#{id,jdbcType=BIGINT}, #{serverHost,jdbcType=VARCHAR}, #{serverDesc,jdbcType=VARCHAR}, #{regionId,jdbcType=BIGINT}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP} ) insert into t_server_host_config id, server_host, server_desc, region_id, create_time, update_time, #{id,jdbcType=BIGINT}, #{serverHost,jdbcType=VARCHAR}, #{serverDesc,jdbcType=VARCHAR}, #{regionId,jdbcType=BIGINT}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, update t_server_host_config server_host = #{serverHost,jdbcType=VARCHAR}, server_desc = #{serverDesc,jdbcType=VARCHAR}, region_id = #{regionId,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP}, where id = #{id,jdbcType=BIGINT} update t_server_host_config set server_host = #{serverHost,jdbcType=VARCHAR}, server_desc = #{serverDesc,jdbcType=VARCHAR}, region_id = #{regionId,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=TIMESTAMP}, update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=BIGINT} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/README.md ================================================ 1. 配置中心服务端,集成注册中心、配置中心一体化 2. 使用mysql存储配置 3. 客户端访问时,会记录客户端提供的服务信息保存到数据库,便于管理平台发布更新配置 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-server 4.1.42.Final org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework spring-jdbc org.springframework.cloud spring-cloud-starter-eureka-server org.springframework.cloud spring-cloud-config-server org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true mysql mysql-connector-java com.alibaba druid 1.1.10 org.apache.commons commons-lang3 3.4 commons-collections commons-collections 3.2.2 org.springframework.boot spring-boot-devtools true cn.hutool hutool-core 4.1.12 compile com.dyuproject.protostuff protostuff-core 1.1.2 com.dyuproject.protostuff protostuff-runtime 1.1.2 org.objenesis objenesis 2.1 io.netty netty-all ${netty.version} ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/ConfigerCenterApplication.java ================================================ package com.xiao.custom.config.server; import com.xiao.custom.config.server.annotation.CustomEnableConfigServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * [简要描述]: 自定义配置中心实现,注册中心、配置中心合并一体化 * [详细描述]: spring-cloud config扩展 * * @author llxiao * @version 1.0, 2019/1/31 10:01 * @since JDK 1.8 */ @SpringBootApplication(scanBasePackages = "com.xiao.custom.config") @CustomEnableConfigServer @EnableEurekaServer public class ConfigerCenterApplication { public static void main(String[] args) { SpringApplication.run(ConfigerCenterApplication.class, args); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/annotation/CustomEnableConfigServer.java ================================================ package com.xiao.custom.config.server.annotation; import com.xiao.custom.config.server.config.CustomEnvironmentRepositoryConfiguration; import org.springframework.cloud.config.server.EnableConfigServer; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /** * [简要描述]: 自定义配置中心注解 * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/22 15:50 * @since JDK 1.8 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Import(CustomEnvironmentRepositoryConfiguration.class) @EnableConfigServer public @interface CustomEnableConfigServer { } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/config/CustomEnvironmentRepositoryConfiguration.java ================================================ package com.xiao.custom.config.server.config; import com.xiao.custom.config.server.environment.CustomEnvironmentRepository; import com.xiao.custom.config.server.manager.ClientManagerService; import com.xiao.custom.config.server.service.RepositoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/22 15:49 * @since JDK 1.8 */ @Configuration public class CustomEnvironmentRepositoryConfiguration { @Autowired private RepositoryService repositoryService; @Autowired private ClientManagerService clientManagerService; @Bean public EnvironmentRepository environmentRepository() { return new CustomEnvironmentRepository(repositoryService, clientManagerService); } @Bean public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate template) { return new NamedParameterJdbcTemplate(template); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/controller/RefreshController.java ================================================ package com.xiao.custom.config.server.controller; import com.xiao.custom.config.server.service.RefreshService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 10:29 * @since JDK 1.8 */ @RestController @RequestMapping("/refresh") public class RefreshController { @Autowired private RefreshService refreshService; @RequestMapping("/client") public boolean refresh(@RequestParam("ip") String ip, @RequestParam("port") int port) { return refreshService.refresh(ip, port); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/environment/CustomEnvironmentRepository.java ================================================ package com.xiao.custom.config.server.environment; import com.xiao.custom.config.server.manager.ClientManagerService; import com.xiao.custom.config.server.service.RepositoryService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.MapUtils; import org.springframework.cloud.config.environment.Environment; import org.springframework.cloud.config.environment.PropertySource; import org.springframework.cloud.config.server.environment.EnvironmentRepository; import org.springframework.core.Ordered; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.*; import static org.springframework.util.StringUtils.commaDelimitedListToStringArray; import static org.springframework.util.StringUtils.isEmpty; /** * [简要描述]: 数据库实现 * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/22 15:16 * @since JDK 1.8 */ @Slf4j public class CustomEnvironmentRepository implements EnvironmentRepository, Ordered { /** * config server 一些默认配置 */ private static final String APPLICATION = "application"; private static final String DEFAULT_PROFILE = "default"; private static final String DEFAULT_LABEL = "master"; private static final String CLIENT_HOST = "ClientServerHost"; private static final String CLIENT_PORT = "ClientServerPort"; /** * Squid 服务代理 */ private static final String X_FORWARDED_FOR = "X-Forwarded-For"; /** * apache 服务代理 */ private static final String PROXY_CLIENT_IP = "Proxy-Client-IP"; /** * weblogic 服务代理 */ private static final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP"; /** * http 其他代理 */ private static final String HTTP_CLIENT_IP = "HTTP_CLIENT_IP"; /** * nginx服务代理 */ private static final String X_REA_IP = "X-Real-IP"; private static final String UNKNOWN = "unknown"; private int order = Ordered.LOWEST_PRECEDENCE - 10; private RepositoryService repositoryService; private ClientManagerService clientManagerService; public CustomEnvironmentRepository(RepositoryService repositoryService, ClientManagerService clientManagerService) { this.repositoryService = repositoryService; this.clientManagerService = clientManagerService; } @Override public Environment findOne(String application, String profile, String label) { String ip = saveClientInfo(application, profile); String config = application; if (isEmpty(label)) { label = DEFAULT_LABEL; } if (isEmpty(profile)) { profile = DEFAULT_PROFILE; } if (!profile.startsWith(DEFAULT_PROFILE)) { profile = DEFAULT_PROFILE + ',' + profile; } String[] profiles = commaDelimitedListToStringArray(profile); Environment environment = new Environment(application, profiles, label, null, null); if (!config.startsWith(APPLICATION)) { config = APPLICATION + ',' + config; } List applications = new ArrayList<>(new LinkedHashSet<>(Arrays .asList(commaDelimitedListToStringArray(config)))); List envs = new ArrayList<>(new LinkedHashSet<>(Arrays.asList(profiles))); Collections.reverse(applications); Collections.reverse(envs); Map resource; for (String app : applications) { for (String env : envs) { resource = repositoryService.getPropertySource(ip, app, label, env); if (MapUtils.isNotEmpty(resource)) { environment.add(new PropertySource(app + '-' + env, resource)); } } } return environment; } private String saveClientInfo(String application, String profile) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); String ip = ""; String port = ""; if (null != servletRequestAttributes) { HttpServletRequest request = servletRequestAttributes.getRequest(); //获取IP ip = request.getHeader(CLIENT_HOST); if (org.apache.commons.lang3.StringUtils.isBlank(ip)) { ip = getClientIp(request); } port = request.getHeader(CLIENT_PORT); log.info(">>> 应用:{}开始从配置中心拉取配置", application); log.info(">>> 应用端服务器信息-IP:{},Port:{}", ip, port); if (!isEmpty(ip) && !isEmpty(port)) { clientManagerService.setClientHost(application, profile, ip, Integer.parseInt(port)); } } return ip; } private String getClientIp(HttpServletRequest request) { String ip = null; //X-Forwarded-For:Squid 服务代理 String ipAddresses = request.getHeader(X_FORWARDED_FOR); if (isEmpty(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) { //Proxy-Client-IP:apache 服务代理 ipAddresses = request.getHeader(PROXY_CLIENT_IP); } if (isEmpty(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) { //WL-Proxy-Client-IP:weblogic 服务代理 ipAddresses = request.getHeader(WL_PROXY_CLIENT_IP); } if (isEmpty(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) { //HTTP_CLIENT_IP:有些代理服务器 ipAddresses = request.getHeader(HTTP_CLIENT_IP); } if (isEmpty(ipAddresses) || UNKNOWN.equalsIgnoreCase(ipAddresses)) { //X-Real-IP:nginx服务代理 ipAddresses = request.getHeader(X_REA_IP); } //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP if (ipAddresses != null && ipAddresses.length() != 0) { ip = ipAddresses.split(",")[0]; } //还是不能获取到,最后再通过request.getRemoteAddr();获取 if (isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ipAddresses)) { ip = request.getRemoteAddr(); } return ip; } @Override public int getOrder() { return order; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/manager/ClientManagerService.java ================================================ package com.xiao.custom.config.server.manager; /** * [简要描述]: 服务端链接管理 * [详细描述]: 服务名称+链接的IP地址 * * @author llxiao * @version 1.0, 2019/1/28 11:26 * @since JDK 1.8 */ public interface ClientManagerService { /** * 在线 */ int ON_LINE = 0; /** * 离线 */ int OFF_LINE = 1; /** * [简要描述]:存储服务链接信息,服务IP
* [详细描述]:
* * @param serviceName : 应用名 * @param profile: 应用环境 * @param hostIp : 应用对应服务的IP * @param hostPort : 应用对应服务的端口 * llxiao 2019/1/28 - 11:38 **/ void setClientHost(String serviceName, String profile, String hostIp, int hostPort); /** * [简要描述]:更新状态
* [详细描述]:
* * @param hostIp : 客户端IP * @param nettyPort : 客户端PORT * @param status : 0在线,1离线 * llxiao 2019/4/1 - 10:34 **/ void updateStatus(String hostIp, int nettyPort, int status); /** * [简要描述]:更新应用的服务信息与NETTY连接的IP信息
* [详细描述]:
* * @param hostIp : * @param hostPort : * @param nettyPort : * @param nettyHostIp : * @return void * llxiao 2019/4/1 - 11:49 **/ void updateNettyInfo(String hostIp, int hostPort, int nettyPort, String nettyHostIp); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/manager/SqlConstants.java ================================================ package com.xiao.custom.config.server.manager; /** * [简要描述]: SQL常量 * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/29 16:53 * @since JDK 1.8 */ public interface SqlConstants { /** * 应用名称和环境查找应用ID,返回Null则表示不存在 */ String SELECT_APPLICATION = "select id from t_client_application a where application = :application and profile = :profile"; /** * 插入一条应用环境信息,应用名称+环境 */ String INSERT_APPLICATION = "INSERT INTO `t_client_application` (`application`,`profile`,`create_time`) VALUES (:application,:profile,now())"; /** * 主键ID更新应服务状态 */ String UPDATE_APPLICATION = "update t_client_application set status = 0,update_time = now() where id = :id"; /** * 应用ID+客户端IP+客户端提供服务IP 查询客户端信息主键ID */ String SELECT_APPLICATION_HOST_INFO = "select id from t_client_host_info where client_application_id = :appId and host_ip = :ip and host_port = :port"; /** * 插入一条应用服务端的客户端HOST信息 应用主键ID+客户端IP+客户端提供的服务端口 */ String INSERT_APPLICATION_HOST_INFO = "INSERT INTO `t_client_host_info` (`client_application_id`, `host_ip`, `host_port`,`create_time`) VALUES (:appId, :ip,:port, now())"; /** * 主键ID更新用服务端的客户端HOST信息的状态 */ String UPDATE_APPLICATION_HOST_INFO = "update t_client_host_info set status = 0 ,update_time = now() where id = :id"; /** * IP+PORT更新客户端状态 */ String UPDATE_CLIENT_STATUS = "update t_client_host_info set status = :status,update_time = now() where netty_ip = :nettyIp and netty_port = :nettyPort"; /** * 绑定host和netty端口信息,标记上线 */ String UPDATE_NETTY_INFO = "update t_client_host_info set netty_port = :nettyPort,netty_ip = :nettyIp,status = 0,update_time = now() where host_ip = :ip and host_port = :port"; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/manager/impl/ClientManagerServiceDbImpl.java ================================================ package com.xiao.custom.config.server.manager.impl; import com.xiao.custom.config.server.manager.ClientManagerService; import com.xiao.custom.config.server.manager.SqlConstants; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.Map; /** * [简要描述]: 客户端管理jdbc实现 * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/29 16:28 * @since JDK 1.8 */ @Service @Slf4j public class ClientManagerServiceDbImpl implements ClientManagerService, SqlConstants { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; /** * [简要描述]:存储服务链接信息,服务IP
* [详细描述]:
* * @param serviceName : 应用名 * @param profile : 应用环境 * @param hostIp : 应用对应服务的IP * @param hostPort : 应用对应服务的端口 **/ @Override @Transactional(rollbackFor = Exception.class) public void setClientHost(String serviceName, String profile, String hostIp, int hostPort) { if (log.isDebugEnabled()) { log.debug("=======存储服务链接信息:"); log.debug("应用名称:{}", serviceName); log.debug("应用环境:{}", profile); log.debug("应用IP:{}", hostIp); log.debug("应用端口:{}", hostPort); } // 应用 Long id = processApplication(serviceName, profile); // 应用主机信息 processHost(hostIp, hostPort, id); } /** * [简要描述]:更新状态
* [详细描述]:
* * @param hostIp : 客户端IP * @param hostPort : 客户端PORT * @param status : status **/ @Override @Transactional(rollbackFor = Exception.class) public void updateStatus(String hostIp, int hostPort, int status) { Map params = new HashMap<>(); params.put("status", status); params.put("nettyIp", hostIp); params.put("nettyPort", hostPort); namedParameterJdbcTemplate.update(SqlConstants.UPDATE_CLIENT_STATUS, params); } /** * [简要描述]:
* [详细描述]:
* * @param hostIp : * @param hostPort : * @param nettyPort : * @param nettyHostIp llxiao 2019/4/1 - 11:49 **/ @Override @Transactional(rollbackFor = Exception.class) public void updateNettyInfo(String hostIp, int hostPort, int nettyPort, String nettyHostIp) { Map params = new HashMap<>(); params.put("nettyPort", nettyPort); params.put("ip", hostIp); params.put("port", hostPort); params.put("nettyIp", nettyHostIp); namedParameterJdbcTemplate.update(SqlConstants.UPDATE_NETTY_INFO, params); } /** * 客户端应用维护 * * @param serviceName * @param profile * @return */ private Long processApplication(String serviceName, String profile) { //应用+环境 是否存在 Map params = new HashMap<>(); params.put("application", serviceName); params.put("profile", profile); Long id = getPriKey(SELECT_APPLICATION, params); if (null == id) { //插入 id = insertApplication(serviceName, profile); } else { updateApplicationStatus(id); } return id; } private Long getPriKey(String sql, Map params) { Long id = null; try { id = namedParameterJdbcTemplate.queryForObject(sql, params, Long.class); } catch (EmptyResultDataAccessException e) { log.warn("获取不到数据"); } return id; } /** * 客户主机信息维护 * * @param hostIp * @param hostPort * @param id */ private void processHost(String hostIp, int hostPort, Long id) { // 添加客户端IP和端口信息 Map params = new HashMap<>(3); params.put("appId", id); params.put("ip", hostIp); params.put("port", hostPort); Long hostId = getPriKey(SELECT_APPLICATION_HOST_INFO, params); if (null == hostId) { //添加 namedParameterJdbcTemplate.update(INSERT_APPLICATION_HOST_INFO, params); } else { params = new HashMap<>(1); params.put("id", id); //更新状态 namedParameterJdbcTemplate.update(UPDATE_APPLICATION_HOST_INFO, params); } } private void updateApplicationStatus(Long id) { Map param = new HashMap<>(1); param.put("id", id); namedParameterJdbcTemplate.update(UPDATE_APPLICATION, param); } private Long insertApplication(String serviceName, String profile) { MapSqlParameterSource parameters = new MapSqlParameterSource(); parameters.addValue("application", serviceName); parameters.addValue("profile", profile); return insertReKey(INSERT_APPLICATION, parameters); // return insertReId(connection -> // { // PreparedStatement pstmt = connection.prepareStatement(INSERT_APPLICATION, Statement.RETURN_GENERATED_KEYS); // pstmt.setString(1, serviceName); // pstmt.setString(2, profile); // return pstmt; // }); } /** * [简要描述]:jdbcTemplate方式插入一条数据,返回主键
* [详细描述]:
* * @param preparedStatementCreator : * @return java.lang.Long * llxiao 2019/1/29 - 17:11 **/ private Long insertReId(PreparedStatementCreator preparedStatementCreator) { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(preparedStatementCreator, keyHolder); return keyHolder.getKey().longValue(); } /** * [简要描述]:NamedParameterJdbcTemplate方式插入一条数据,返回主键
* [详细描述]:
* * @param sql : 执行的SQL * @param params : 参数 * @return java.lang.Long * llxiao 2019/1/29 - 17:44 **/ private Long insertReKey(String sql, SqlParameterSource params) { KeyHolder keyHolder = new GeneratedKeyHolder(); namedParameterJdbcTemplate.update(sql, params, keyHolder, new String[] { "id" }); return keyHolder.getKey().longValue(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/coder/ProtoDecoder.java ================================================ package com.xiao.custom.config.server.netty.coder; import com.xiao.custom.config.server.netty.util.ProtostuffUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; /** * [简要描述]: 编码 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 13:54 * @since JDK 1.8 */ public class ProtoDecoder extends ByteToMessageDecoder { private static final int PROTO_BUFF_FLAG = 4; private Class genericClass; public ProtoDecoder(Class cls) { this.genericClass = cls; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List list) throws Exception { if (byteBuf.readableBytes() < PROTO_BUFF_FLAG) { return; } byteBuf.markReaderIndex(); int dataLength = byteBuf.readInt(); if (dataLength < 0) { ctx.close(); } if (byteBuf.readableBytes() < dataLength) { byteBuf.resetReaderIndex(); } // 将ByteBuf转换为byte[] byte[] data = new byte[dataLength]; byteBuf.readBytes(data); // 将data转换成object Object obj = ProtostuffUtil.deserialize(data, genericClass); list.add(obj); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/coder/ProtoEncoder.java ================================================ package com.xiao.custom.config.server.netty.coder; import com.xiao.custom.config.server.netty.util.ProtostuffUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; /** * [简要描述]: 解码 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 13:54 * @since JDK 1.8 */ @ChannelHandler.Sharable public class ProtoEncoder extends MessageToByteEncoder { private Class genericClass; public ProtoEncoder(Class cls) { this.genericClass = cls; } @Override protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception { // 序列化 if (genericClass.isInstance(msg)) { byte[] data = ProtostuffUtil.serialize(msg); byteBuf.writeInt(data.length); byteBuf.writeBytes(data); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/dto/CommandEnum.java ================================================ package com.xiao.custom.config.server.netty.dto; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 16:18 * @since JDK 1.8 */ public enum CommandEnum { /** * 心跳 */ IDLE(0), /** * 登录,绑定信息 */ LOGIN(1), /** * 刷新动作 */ REFRESH(2); int status; CommandEnum(int status) { this.status = status; } public int getStatus() { return status; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/dto/Message.java ================================================ package com.xiao.custom.config.server.netty.dto; import lombok.Data; /** * [简要描述]: Netty交互消息体 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 10:42 * @since JDK 1.8 */ @Data public class Message { /** * 0:心跳,1:登录,2:刷新 */ private int command; /** * 消息体 */ private String message; /** * 返回状态 */ private int status; /** * 错误消息 */ private String errorMessage; /** * 客户端服务端口 */ private int serverPort; /** * 客户端服务IP */ private String hostIp; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/factory/CoderFactory.java ================================================ package com.xiao.custom.config.server.netty.factory; import com.xiao.custom.config.server.netty.coder.ProtoDecoder; import com.xiao.custom.config.server.netty.coder.ProtoEncoder; import com.xiao.custom.config.server.netty.dto.Message; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 14:31 * @since JDK 1.8 */ public class CoderFactory { public static ProtoDecoder newDecoder() { return new ProtoDecoder(Message.class); } public static ProtoEncoder newEncoder() { return new ProtoEncoder(Message.class); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/factory/NamedThreadFactory.java ================================================ /* * 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. */ package com.xiao.custom.config.server.netty.factory; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Thread factory to name the thread purposely * * @author jiangping * @version $Id: NamedThreadFactory.java, v 0.1 Sept 5, 2016 10:17:10 PM tao Exp $ */ public class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private final ThreadGroup group; private final String namePrefix; private final boolean isDaemon; public NamedThreadFactory() { this("ThreadPool"); } public NamedThreadFactory(String name) { this(name, false); } public NamedThreadFactory(String preffix, boolean daemon) { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = preffix + "-" + poolNumber.getAndIncrement() + "-thread-"; isDaemon = daemon; } /** * Create a thread. * * @see ThreadFactory#newThread(Runnable) */ @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); t.setDaemon(isDaemon); if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } /** * 自定义线程factory * * @param namePrefix * @param daemon * @return */ public static ThreadFactory create(final String namePrefix, final boolean daemon) { return new NamedThreadFactory(namePrefix, daemon); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/handler/ServiceHandler.java ================================================ package com.xiao.custom.config.server.netty.handler; import com.xiao.custom.config.server.manager.ClientManagerService; import com.xiao.custom.config.server.netty.dto.CommandEnum; import com.xiao.custom.config.server.netty.dto.Message; import com.xiao.custom.config.server.netty.manager.Connection; import com.xiao.custom.config.server.netty.manager.ConnectionManager; import com.xiao.custom.config.server.netty.util.RemotingUtil; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.timeout.IdleStateEvent; import lombok.extern.slf4j.Slf4j; /** * [简要描述]: 业务handler * [详细描述]: * ##@Sharable 注解用来说明ChannelHandler是否可以在多个channel直接共享使用,单例的ChannelHandler
* 业务处理,请使用线程池来处理
* * @author llxiao * @version 1.0, 2019/3/30 16:00 * @since JDK 1.8 */ @Slf4j @ChannelHandler.Sharable public class ServiceHandler extends SimpleChannelInboundHandler { private ClientManagerService clientManagerService; private ConnectionManager connectionManager; public ServiceHandler(ClientManagerService clientManagerService, ConnectionManager connectionManager) { super(); this.clientManagerService = clientManagerService; this.connectionManager = connectionManager; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info(">>> 客户端-IP:{},PORT:{},连接到服务器...", RemotingUtil.parseRemoteIP(ctx.channel()), RemotingUtil .parseRemotePort(ctx.channel())); super.channelActive(ctx); } @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Message message) throws Exception { String nettyIp = RemotingUtil.parseRemoteIP(channelHandlerContext.channel()); int nettyPort = RemotingUtil.parseRemotePort(channelHandlerContext.channel()); if (CommandEnum.IDLE.getStatus() == message.getCommand()) { if (log.isDebugEnabled()) { log.debug(">>> 客户端-IP:{},PORT:{}发起了心跳请求.", nettyIp, nettyPort); } //收到心跳维护客户端http服务和netty服务关系 channelHandlerContext.channel().writeAndFlush(message); } else if (CommandEnum.LOGIN.getStatus() == message.getCommand()) { log.info(">>> 客户端-IP:{},PORT:{}发起了登录请求", message.getHostIp(), message.getServerPort()); log.info(">>> 客户端-Netty IP:{},Netty PORT:{}发起了登录请求", nettyIp, nettyPort); //绑定应用信息和netty端口信息 clientManagerService.updateNettyInfo(message.getHostIp(), message.getServerPort(), nettyPort, nettyIp); connectionManager.addConnection(new Connection(channelHandlerContext.channel(), message.getHostIp(), message .getServerPort())); } else { log.info(">>> 业务处理......"); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { String ip = RemotingUtil.parseRemoteIP(ctx.channel()); int port = RemotingUtil.parseRemotePort(ctx.channel()); log.error(">>> 客户端-IP:{},PORT:{}关闭了连接,并进行下线操作!", ip, port); // 下线处理 if (ctx.channel().isOpen()) { ctx.close(); } clientManagerService.updateStatus(ip, port, ClientManagerService.OFF_LINE); connectionManager.remove(ip, port); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (log.isDebugEnabled()) { log.debug(">>> 已超过5S,未收到客户端发送到的消息....."); } if (evt instanceof IdleStateEvent) { //读取心跳超时后,会将此channel连接断开 try { String ip = RemotingUtil.parseRemoteIP(ctx.channel()); int port = RemotingUtil.parseRemotePort(ctx.channel()); log.warn(">>> 关闭这个不活跃的连接-IP:{},PORT:{}并进行离线操作", ip, port); // 心跳,关闭连接 if (ctx.channel().isOpen()) { ctx.close(); clientManagerService.updateStatus(ip, port, ClientManagerService.OFF_LINE); connectionManager.remove(ip, port); } } catch (Exception e) { log.warn(">>> 关闭链路异常....", e); } } else { super.userEventTriggered(ctx, evt); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String ip = RemotingUtil.parseRemoteIP(ctx.channel()); int port = RemotingUtil.parseRemotePort(ctx.channel()); // log.warn(">>> 客户端已断开连接......:{}", ctx); log.warn(">>> 关闭这个不活跃的连接-IP:{},PORT:{}并进行离线操作", ip, port); // 下线处理 if (ctx.channel().isOpen()) { ctx.close(); } clientManagerService.updateStatus(ip, port, ClientManagerService.OFF_LINE); connectionManager.remove(ip, port); super.channelInactive(ctx); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/manager/Connection.java ================================================ package com.xiao.custom.config.server.netty.manager; import com.xiao.custom.config.server.netty.util.RemotingUtil; import io.netty.channel.Channel; import lombok.Data; /** * [简要描述]: channel连接 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 08:44 * @since JDK 1.8 */ @Data public class Connection { public static final String SPLIT = ":"; private String uniqueKey; private String nettyIp; private int nettyPort; private String serverIp; private int serverPort; private Channel channel; public Connection() { } public Connection(Channel channel) { this.nettyIp = RemotingUtil.parseRemoteIP(channel); this.nettyPort = RemotingUtil.parseRemotePort(channel); this.uniqueKey = nettyIp + SPLIT + nettyPort; this.channel = channel; } public Connection(Channel channel, String serverIp, int serverPort) { this.nettyIp = RemotingUtil.parseRemoteIP(channel); this.nettyPort = RemotingUtil.parseRemotePort(channel); this.uniqueKey = nettyIp + SPLIT + nettyPort; this.channel = channel; this.serverIp = serverIp; this.serverPort = serverPort; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/manager/ConnectionManager.java ================================================ package com.xiao.custom.config.server.netty.manager; import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** * [简要描述]: 连接数管理 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 08:40 * @since JDK 1.8 */ @Component("connectionManager") @Slf4j public class ConnectionManager { /** * 总连接数 */ private ConcurrentHashMap conns = new ConcurrentHashMap(); /** * 活跃连接数 */ private AtomicLong activeConnect = new AtomicLong(0); /** * [简要描述]:添加一个Channel连接
* [详细描述]:
* * @param connection : * llxiao 2019/4/4 - 8:54 **/ public void addConnection(Connection connection) { if (null != connection) { log.info(">>> 新增了一个连接:{}", connection); if (log.isDebugEnabled()) { log.debug(">>> 新增了一个连接:{}", connection); } conns.put(connection.getUniqueKey(), connection); activeConnect.incrementAndGet(); } } /** * [简要描述]:Key 获取一个连接
* [详细描述]:找不到返回一个null
* * @param uniqueKey : * @return com.winner.config.center.server.netty.manager.Connection * llxiao 2019/4/4 - 8:57 **/ public Connection getConnection(String uniqueKey) { Connection connection = null; if (StringUtils.isNotBlank(uniqueKey)) { connection = conns.get(uniqueKey); } return connection; } /** * [简要描述]:依据IP、PORT查找连接信息
* [详细描述]:找不到返回一个null
* * @param nettyIp : * @param nettyPot : * @return com.winner.config.center.server.netty.manager.Connection * llxiao 2019/4/4 - 9:02 **/ public Connection getConnection(String nettyIp, int nettyPot) { Connection connection = null; if (StringUtils.isNotBlank(nettyIp)) { connection = this.getConnection(nettyIp + Connection.SPLIT + nettyPot); } return connection; } /** * [简要描述]:依据IP、PORT查找连接信息
* [详细描述]:找不到返回一个null
* * @param nettyIp : * @param nettyPot : * @return java.nio.channels.Channel * llxiao 2019/4/4 - 9:04 **/ public Channel getChannel(String nettyIp, int nettyPot) { Channel channel = null; if (StringUtils.isNotBlank(nettyIp)) { Connection connection = this.getConnection(nettyIp, nettyPot); if (null != connection) { channel = connection.getChannel(); } } return channel; } /** * [简要描述]:Key 获取一个连接
* [详细描述]:找不到返回一个null
* * @param uniqueKey : * @return io.netty.channel.Channel * llxiao 2019/4/4 - 9:08 **/ public Channel getChannel(String uniqueKey) { Channel channel = null; if (StringUtils.isNotBlank(uniqueKey)) { Connection connection = this.getConnection(uniqueKey); if (null != connection) { channel = connection.getChannel(); } } return channel; } /** * [简要描述]:删除一个连接
* [详细描述]:
* * @param uniqueKey : * @return void * llxiao 2019/4/4 - 9:09 **/ public void remove(String uniqueKey) { if (StringUtils.isNotBlank(uniqueKey)) { Connection connection = conns.remove(uniqueKey); if (null != connection) { log.info(">>> 删除了一个连接:{}", connection); if (log.isDebugEnabled()) { log.debug(">>> 删除了一个连接:{}", connection); } activeConnect.decrementAndGet(); } } } /** * [简要描述]:删除一个连接
* [详细描述]:
* * @param nettyIp : * @param nettyPort : * @return void * llxiao 2019/4/4 - 9:09 **/ public void remove(String nettyIp, int nettyPort) { if (StringUtils.isNotBlank(nettyIp)) { String uniqueKey = nettyIp + Connection.SPLIT + nettyPort; this.remove(uniqueKey); } } /** * [简要描述]:获取当前总连接数
* [详细描述]:
* * @return long * llxiao 2019/4/4 - 9:14 **/ public long getCurrentConnectNum() { return activeConnect.get(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/server/NettyServer.java ================================================ package com.xiao.custom.config.server.netty.server; import com.xiao.custom.config.server.manager.ClientManagerService; import com.xiao.custom.config.server.netty.factory.CoderFactory; import com.xiao.custom.config.server.netty.factory.NamedThreadFactory; import com.xiao.custom.config.server.netty.handler.ServiceHandler; import com.xiao.custom.config.server.netty.manager.ConnectionManager; import com.xiao.custom.config.server.netty.util.NettyConfig; import com.xiao.custom.config.server.netty.util.NettyEventLoopUtil; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.timeout.IdleStateHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import java.net.InetSocketAddress; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; /** * [简要描述]: Netty服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 14:23 * @since JDK 1.8 */ @Component @Slf4j public class NettyServer implements ApplicationListener, DisposableBean { /** * 启动状态 */ private AtomicBoolean started = new AtomicBoolean(false); @Value("${netty.server.port}") private int port; @Autowired private ClientManagerService clientManagerService; @Autowired private ConnectionManager connectionManager; private ScheduledExecutorService scheduledExecutorService; private ExecutorService simpleExecutor; /** * server bootstrap */ private ServerBootstrap serverBootstrap; /** * channelFuture */ private ChannelFuture channelFuture; /** * boss event loop group, boss group should not be daemon, need shutdown manually */ private final EventLoopGroup bossGroup = NettyEventLoopUtil .newEventLoopGroup(1, new NamedThreadFactory("Winner-netty-server-boss", false)); /** * worker event loop group. Reuse I/O worker threads between rpc servers. */ private static final EventLoopGroup workerGroup = NettyEventLoopUtil.newEventLoopGroup( Runtime.getRuntime().availableProcessors() * 2, new NamedThreadFactory("Winner-netty-server-worker", true)); static { if (workerGroup instanceof NioEventLoopGroup) { ((NioEventLoopGroup) workerGroup).setIoRatio(NettyConfig.NETTY_IO_RATIO_DEFAULT); } else if (workerGroup instanceof EpollEventLoopGroup) { ((EpollEventLoopGroup) workerGroup).setIoRatio(NettyConfig.NETTY_IO_RATIO_DEFAULT); } } @Override public void destroy() throws Exception { stop(); } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { initNetty(); setHandler(); start(); // 启动后5S开始检查Netty server的状态,每个2两分钟执行一次 scheduledExecutorService.scheduleAtFixedRate(() -> check(), 1, 2, TimeUnit.MINUTES); } private void check() { if (!started.get()) { log.info(">>> Re start netty server!"); start(); } } private void setHandler() { serverBootstrap.childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); // 设置心跳,readerIdleTime:读空闲5S发起心跳处理,writerIdleTime:写空闲时间,allIdleTime:所有空闲时间,5S内没有 read动作,触发userEventTriggered动作 pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS)); //编解码 pipeline.addLast(CoderFactory.newDecoder()); pipeline.addLast(CoderFactory.newEncoder()); // 业务handler pipeline.addLast(new ServiceHandler(clientManagerService, connectionManager)); } }); } private void start() { log.info(">>>> Netty Server start......"); try { this.channelFuture = this.serverBootstrap.bind(new InetSocketAddress(port)).sync(); started.set(this.channelFuture.isSuccess()); log.info(">>>> Netty Server start successfully,Open port: {}", port); } catch (InterruptedException e) { log.error(">>>> Netty Server start failed!"); log.error("Error message:", e); } } private void initNetty() { scheduledExecutorService = new ScheduledThreadPoolExecutor(1, NamedThreadFactory .create("Scheduled check netty server-", true)); simpleExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("Netty server-", true)); log.info(">>>> Netty Server init......"); this.serverBootstrap = new ServerBootstrap(); this.serverBootstrap.group(bossGroup, workerGroup).channel(NettyEventLoopUtil.getServerSocketChannelClass()) .option(ChannelOption.SO_BACKLOG, NettyConfig.TCP_SO_BACKLOG_DEFAULT) .option(ChannelOption.SO_REUSEADDR, NettyConfig.TCP_SO_REUSEADDR_DEFAULT) .childOption(ChannelOption.TCP_NODELAY, NettyConfig.TCP_NODELAY_DEFAULT) .childOption(ChannelOption.SO_KEEPALIVE, NettyConfig.TCP_SO_KEEPALIVE_DEFAULT); this.serverBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); this.serverBootstrap .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(NettyConfig.NETTY_BUFFER_LOW_WATERMARK_DEFAULT, NettyConfig.NETTY_BUFFER_HIGH_WATERMARK_DEFAULT)); } private void stop() { log.warn(">>> Stop Netty server.............."); if (started.get()) { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/util/NettyConfig.java ================================================ package com.xiao.custom.config.server.netty.util; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/30 14:57 * @since JDK 1.8 */ public class NettyConfig { public static final int NETTY_IO_RATIO_DEFAULT = 70; public static final int TCP_SO_BACKLOG_DEFAULT = 128; public static final boolean TCP_SO_REUSEADDR_DEFAULT = true; public static final boolean TCP_NODELAY_DEFAULT = true; public static final boolean TCP_SO_KEEPALIVE_DEFAULT = true; /** * low.watermark默认大小为8388608,即8M; * high.watermark默认大小为16777216,即16M * 两个参数控制的是Channel.isWritable()方法 * https://www.jianshu.com/p/a1166c34ae46 */ public static final Integer NETTY_BUFFER_HIGH_WATERMARK_DEFAULT = 64 * 1024; public static final Integer NETTY_BUFFER_LOW_WATERMARK_DEFAULT = 32 * 1024; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/util/NettyEventLoopUtil.java ================================================ /* * 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. */ package com.xiao.custom.config.server.netty.util; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.util.concurrent.ThreadFactory; /** * Utils for netty EventLoop * * @author YANGLiiN * @version $Id: NettyEventLoopUtil.java, v 1.5 2018-05-28 14:07 YANGLiiN $ */ public class NettyEventLoopUtil { /** * check whether epoll enabled, and it would not be changed during runtime. */ private static boolean epollEnabled = Epoll.isAvailable(); /** * Create the right event loop according to current platform and system property, fallback to NIO when epoll not enabled. * * @param nThreads * @param threadFactory * @return an EventLoopGroup suitable for the current platform */ public static EventLoopGroup newEventLoopGroup(int nThreads, ThreadFactory threadFactory) { return epollEnabled ? new EpollEventLoopGroup(nThreads, threadFactory) : new NioEventLoopGroup(nThreads, threadFactory); } /** * @return a SocketChannel class suitable for the given EventLoopGroup implementation */ public static Class getClientSocketChannelClass() { return epollEnabled ? EpollSocketChannel.class : NioSocketChannel.class; } /** * @return a ServerSocketChannel class suitable for the given EventLoopGroup implementation */ public static Class getServerSocketChannelClass() { return epollEnabled ? EpollServerSocketChannel.class : NioServerSocketChannel.class; } /** * Use {@link EpollMode#LEVEL_TRIGGERED} for server bootstrap if level trigger enabled by system properties, * otherwise use {@link EpollMode#EDGE_TRIGGERED}. * * @param serverBootstrap server bootstrap */ public static void enableTriggeredMode(ServerBootstrap serverBootstrap) { if (epollEnabled) { serverBootstrap.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/util/ProtostuffUtil.java ================================================ /* * Winner * 文件名 :ProtostuffUtil.java * 创建人 :llxiao * 创建时间:2018年5月2日 */ package com.xiao.custom.config.server.netty.util; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; import org.objenesis.Objenesis; import org.objenesis.ObjenesisStd; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * [简要描述]:序列化工具类(基于 Protostuff 实现)
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年5月2日 * @since JDK 1.8 */ public class ProtostuffUtil { private static Map, Schema> cachedSchema = new ConcurrentHashMap, Schema>(); private static Objenesis objenesis = new ObjenesisStd(true); /** * 获取类的schema * * @param cls * @return */ @SuppressWarnings("unchecked") private static Schema getSchema(Class cls) { Schema schema = (Schema) cachedSchema.get(cls); if (schema == null) { schema = RuntimeSchema.createFrom(cls); if (schema != null) { cachedSchema.put(cls, schema); } } return schema; } /** * [简要描述]:序列化(对象 -> 字节数组)
* [详细描述]:
* * @author llxiao * @param obj * 序列化对象 * @return 对象字节数组 */ @SuppressWarnings("unchecked") public static byte[] serialize(T obj) { Class cls = (Class) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema schema = getSchema(cls); return ProtostuffIOUtil.toByteArray(obj, schema, buffer); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } /** * [简要描述]:反序列化(字节数组 -> 对象)
* [详细描述]:
* * @author llxiao * @param data * 数据 * @param cls * class对象 * @return class对象 */ public static T deserialize(byte[] data, Class cls) { try { /* * 如果一个类没有参数为空的构造方法时候,那么你直接调用newInstance方法试图得到一个实例对象的时候是会抛出异常的 * 通过ObjenesisStd可以完美的避开这个问题 */ T message = (T) objenesis.newInstance(cls);// 实例化 Schema schema = getSchema(cls);// 获取类的schema ProtostuffIOUtil.mergeFrom(data, message, schema); return message; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/netty/util/RemotingUtil.java ================================================ /* * 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. */ package com.xiao.custom.config.server.netty.util; import io.netty.channel.Channel; import org.apache.commons.lang3.StringUtils; import java.net.*; /** * Some utilities for remoting. * * @author jiangping * @version $Id: RemotingUtil.java, v 0.1 Mar 30, 2016 11:51:02 AM jiangping Exp $ */ public class RemotingUtil { /** * Parse the remote address of the channel. * * @param channel * @return */ public static String parseRemoteAddress(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final SocketAddress remote = channel.remoteAddress(); return doParse(remote != null ? remote.toString().trim() : StringUtils.EMPTY); } /** * Parse the local address of the channel. * * @param channel * @return */ public static String parseLocalAddress(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final SocketAddress local = channel.localAddress(); return doParse(local != null ? local.toString().trim() : StringUtils.EMPTY); } /** * Parse the remote host ip of the channel. * * @param channel * @return */ public static String parseRemoteIP(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getAddress().getHostAddress(); } return StringUtils.EMPTY; } /** * Parse the remote hostname of the channel. *

* Note: take care to use this method, for a reverse name lookup takes uncertain time in {@link InetAddress#getHostName}. * * @param channel * @return */ public static String parseRemoteHostName(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getAddress().getHostName(); } return StringUtils.EMPTY; } /** * Parse the local host ip of the channel. * * @param channel * @return */ public static String parseLocalIP(final Channel channel) { if (null == channel) { return StringUtils.EMPTY; } final InetSocketAddress local = (InetSocketAddress) channel.localAddress(); if (local != null) { return local.getAddress().getHostAddress(); } return StringUtils.EMPTY; } /** * Parse the remote host port of the channel. * * @param channel * @return int */ public static int parseRemotePort(final Channel channel) { if (null == channel) { return -1; } final InetSocketAddress remote = (InetSocketAddress) channel.remoteAddress(); if (remote != null) { return remote.getPort(); } return -1; } /** * Parse the local host port of the channel. * * @param channel * @return int */ public static int parseLocalPort(final Channel channel) { if (null == channel) { return -1; } final InetSocketAddress local = (InetSocketAddress) channel.localAddress(); if (local != null) { return local.getPort(); } return -1; } /** * Parse the socket address, omit the leading "/" if present. *

* e.g.1 /127.0.0.1:1234 -> 127.0.0.1:1234 * e.g.2 sofatest-2.stack.alipay.net/10.209.155.54:12200 -> 10.209.155.54:12200 * * @param socketAddress * @return String */ public static String parseSocketAddressToString(SocketAddress socketAddress) { if (socketAddress != null) { return doParse(socketAddress.toString().trim()); } return StringUtils.EMPTY; } /** * Parse the host ip of socket address. *

* e.g. /127.0.0.1:1234 -> 127.0.0.1 * * @param socketAddress * @return String */ public static String parseSocketAddressToHostIp(SocketAddress socketAddress) { final InetSocketAddress addrs = (InetSocketAddress) socketAddress; if (addrs != null) { InetAddress addr = addrs.getAddress(); if (null != addr) { return addr.getHostAddress(); } } return StringUtils.EMPTY; } /** * URL地址获取host信息 * * @param url * @return */ public static String getHost(String url) { String host = ""; try { URL u = new URL(url); host = u.getHost(); } catch (MalformedURLException e) { } return host; } /** *

    *
  1. if an address starts with a '/', skip it. *
  2. if an address contains a '/', substring it. *
* * @param addr * @return */ private static String doParse(String addr) { if (StringUtils.isBlank(addr)) { return StringUtils.EMPTY; } if (addr.charAt(0) == '/') { return addr.substring(1); } else { int len = addr.length(); for (int i = 1; i < len; ++i) { if (addr.charAt(i) == '/') { return addr.substring(i + 1); } } return addr; } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/service/RefreshService.java ================================================ package com.xiao.custom.config.server.service; /** * [简要描述]: 刷新操作 * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 09:36 * @since JDK 1.8 */ public interface RefreshService { /** * [简要描述]:刷新服务
* [详细描述]:
* * @param ip : * @param port : * @return void * llxiao 2019/4/4 - 9:44 **/ boolean refresh(String ip, int port); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/service/RepositoryService.java ================================================ package com.xiao.custom.config.server.service; import java.util.Map; /** * [简要描述]: 资源服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/23 08:36 * @since JDK 1.8 */ public interface RepositoryService { /** * [简要描述]:
* [详细描述]:
* * @param ip : 服务IP * @param application : 应用名称 * @param label : 标签默认master * @param profile : 环境 * @return java.util.Map * llxiao 2018/11/23 - 8:38 **/ Map getPropertySource(String ip, String application, String label, String profile); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/service/impl/JdbcRepositoryServiceImpl.java ================================================ package com.xiao.custom.config.server.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.xiao.custom.config.server.service.RepositoryService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.*; /** * [简要描述]: 资源获取,jdbc实现 * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/23 08:39 * @since JDK 1.8 */ @Slf4j @Service public class JdbcRepositoryServiceImpl implements RepositoryService { //区域查询 private static final String REGION_SQL = "select region_id from t_server_host_config h where h.server_host = ?"; //主配置查询 private static final String ITEM_GROUP_SQL = "select air.item_group_id from t_application ac STRAIGHT_JOIN t_application_item_group_relation air on ac.id = air.application_id where ac.region_id = (" + REGION_SQL + ") and ac.application = ? and ac.label = ? and ac.profile = ?"; // 配置组查询配置项 private static final String ITEM_ID_SQL = "select cigr.item_id from t_config_item_group cig left join t_config_item_group_relation cigr on cig.id = cigr.group_id where cig.id in (:ids)"; // 具体配置项内容查询 private static final String ITEM_SQL = "select ci.item_key,ci.item_value from t_config_item ci where ci.status = 0 and ci.id in (:ids)"; //IP、应用、环境查询主应用ID private static final String QUERY_APPLICATION_ID = "select app.id from t_application app where app.region_id = (select region_id from t_server_host_config h where h.server_host = ?) and app.application = ? and app.label = ? and app.profile = ?"; //查询应用私有配置 private static final String PRIVATE_CONFIG_ITEM_SQL = "select ac.item_key,ac.item_value from t_application_config ac where ac.application_id = :appId"; @Autowired private JdbcTemplate jdbcTemplate; //返回结果获取long id结合 private ResultSetExtractor> tResultSetExtractor = rs -> { List itemGroups = new ArrayList<>(); while (rs.next()) { itemGroups.add(rs.getLong(1)); } return itemGroups; }; //获取具体的资源集合 private ResultSetExtractor> sourceExtractor = rs -> { Map map = new LinkedHashMap<>(); while (rs.next()) { map.put(rs.getString(1), rs.getObject(2)); } return map; }; /** * [简要描述]:
* [详细描述]:
* * @param ip : 服务IP * @param application : 应用名称 * @param label : 标签默认master * @param profile : 环境 * @return java.util.Map * llxiao 2018/11/23 - 8:38 **/ @Override public Map getPropertySource(String ip, String application, String label, String profile) { if (log.isDebugEnabled()) { log.debug("Query config properties for Jdbc!"); log.debug("[ip:{},application:{},label:{},profile:{}]", ip, application, label, profile); } Map map = new HashMap<>(); // 公共配置项 List itemGroupIds = queryIds(ITEM_GROUP_SQL, ip, application, label, profile); if (!CollectionUtils.isEmpty(itemGroupIds)) { List itemIds = queryItemsForGroups(itemGroupIds); if (!CollectionUtils.isEmpty(itemIds)) { map = queryItems(itemIds); if (log.isDebugEnabled()) { log.debug("Found public properties:{}", map); } } } //私有配置项 Map privateItems = queryPrivateItems(ip, application, label, profile); if (CollectionUtil.isNotEmpty(privateItems)) { map.putAll(privateItems); if (log.isDebugEnabled()) { log.debug("Found private properties:{}", privateItems); } } return map; } //获取资源集合 private Map queryItems(List itemIds) { NamedParameterJdbcTemplate itemsTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); Map> stringMap = new HashMap<>(); stringMap.put("ids", itemIds); return itemsTemplate.query(ITEM_SQL, stringMap, sourceExtractor); } //查询私有属性 private Map queryPrivateItems(String ip, String application, String label, String profile) { Map privateItems = new HashMap<>(); List appIds = queryIds(QUERY_APPLICATION_ID, ip, application, label, profile); if (CollectionUtil.isNotEmpty(appIds) && appIds.size() == 1) { NamedParameterJdbcTemplate itemsTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); Map params = new HashMap<>(); params.put("appId", appIds.get(0)); privateItems = itemsTemplate.query(PRIVATE_CONFIG_ITEM_SQL, params, sourceExtractor); } return privateItems; } // 获取所有的Item 的ID集合 private List queryItemsForGroups(List itemGroupIds) { NamedParameterJdbcTemplate itemIdsTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); Map> stringMap = new HashMap<>(); stringMap.put("ids", itemGroupIds); return itemIdsTemplate.queryForList(ITEM_ID_SQL, stringMap, Long.class); } //获取对应的配置项组列表 private List queryIds(String sql, String ip, String application, String label, String profile) { return jdbcTemplate.query(sql, new Object[] { ip, application, label, profile }, tResultSetExtractor); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/java/com/xiao/custom/config/server/service/impl/RefreshServiceImpl.java ================================================ package com.xiao.custom.config.server.service.impl; import com.xiao.custom.config.server.netty.dto.CommandEnum; import com.xiao.custom.config.server.netty.dto.Message; import com.xiao.custom.config.server.netty.manager.Connection; import com.xiao.custom.config.server.netty.manager.ConnectionManager; import com.xiao.custom.config.server.service.RefreshService; import io.netty.channel.Channel; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 09:45 * @since JDK 1.8 */ @Service @Slf4j public class RefreshServiceImpl implements RefreshService { @Resource private ConnectionManager connectionManager; /** * [简要描述]:刷新服务
* [详细描述]:
* * @param ip : * @param port : * @return void * llxiao 2019/4/4 - 9:44 **/ @Override public boolean refresh(String ip, int port) { boolean flag = false; if (StringUtils.isNotBlank(ip)) { Connection connection = connectionManager.getConnection(ip, port); if (null != connection) { log.info(">>> 服务端通知IP:{},PORT:{}的客户端刷新配置....", connection.getServerIp(), connection.getServerPort()); Channel channel = connection.getChannel(); Message message = new Message(); message.setCommand(CommandEnum.REFRESH.getStatus()); message.setMessage("服务通知客户端发起刷新配置"); channel.writeAndFlush(message); flag = true; } else { log.error(">>> 无法发起刷新配置请求,当前IP:{}和PORT:{}找不到对应的客户端信息...", ip, port); } } return flag; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-server/src/main/resources/application.yml ================================================ server: port: 9000 #context-path: /config-server spring: application: name: config-server #mysql datasource: url: jdbc:mysql://192.168.206.210:3306/config_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: admin password: Admin@123 driver-class-name: com.mysql.jdbc.Driver # 使用druid数据源 type: com.alibaba.druid.pool.DruidDataSource ## spring-cloud config请求路径拦截配置,注册中心配置中心合并样式问题处理 cloud: config: server: prefix: /config #netty服务端口,默认8999 netty: server: port: 8999 ##配置中心 eureka: instance: hostname: localhost prefer-ip-address: true instance-id: ${spring.cloud.client.ipAddress}:${server.port} client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:9000/eureka/ server: ##以下配置,生产环境不建议使用 ###自我保护机制关闭 enable-self-preservation: false ## 清理间隔(单位毫秒,默认是60*1000) eviction-interval-timer-in-ms: 20000 ##注册中心的日志error级别 #logging.com.netflix.eureka.registry: ERROR logging.level.com.netflix.eureka.registry: ERROR ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/README.md ================================================ 1.配置管理提供个后台接口服务 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-service org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka-server org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true mysql mysql-connector-java com.alibaba druid 1.1.10 org.mybatis mybatis-spring 1.3.0 org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 com.github.pagehelper pagehelper-spring-boot-starter 1.2.5 com.github.pagehelper pagehelper 5.1.4 com.xiao.skywalking.demo custom-config-pojo ${project.version} cn.hutool hutool-core 4.1.12 compile custom-config-service ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/ConfigServiceApplication.java ================================================ package com.xiao.custom.config.service; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * [简要描述]: 注册中心、配置中心一体化 * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/21 11:37 * @since JDK 1.8 */ @SpringBootApplication(scanBasePackages = "com.xiao.custom.config") @MapperScan("com.xiao.custom.config.pojo.mapper") @EnableEurekaServer public class ConfigServiceApplication { public static void main(String[] args) { SpringApplication.run(ConfigServiceApplication.class, args); } // 启动的时候要注意,由于我们在controller中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例 @Autowired private RestTemplateBuilder builder; // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例 @Bean public RestTemplate restTemplate() { return builder.build(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/ApplicationApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.entity.Application; import com.xiao.custom.config.pojo.entity.ApplicationConfig; import com.xiao.custom.config.pojo.query.AppQuery; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.service.service.ApplicationItemGroupRelationService; import com.xiao.custom.config.service.service.ApplicationService; import com.xiao.custom.config.service.service.ConfigItemGroupService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/28 10:18 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/application") @Slf4j public class ApplicationApi { @Autowired private ApplicationService applicationConfigService; @Autowired private ConfigItemGroupService configItemGroupService; @Autowired private ApplicationItemGroupRelationService applicationItemGroupRelationService; /** * [简要描述]:分页获取
* [详细描述]:
* * @param appQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 10:20 **/ @RequestMapping(value = "/page") public PageInfo pageApplicationConfig(@RequestBody AppQuery appQuery) { return applicationConfigService.pageApplicationConfig(appQuery, appQuery.getPageNum(), appQuery.getPageSize()); } /** * [简要描述]:新增应用
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * jun.liu 2018/11/28 - 10:29 **/ @RequestMapping(value = "/save") public Boolean save(@RequestBody ApplicationDto applicationConfigDto) { if (StringUtils.isBlank(applicationConfigDto.getApplication()) || StringUtils .isBlank(applicationConfigDto.getLabel()) || StringUtils.isBlank(applicationConfigDto.getProfile())) { log.info("新增应用失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } applicationConfigDto.setCreateTime(new Date()); String[] groupIdArr = null; if (StringUtils.isNotBlank(applicationConfigDto.getGroupIds())) { groupIdArr = applicationConfigDto.getGroupIds().split(","); } int a = applicationConfigService.save(ApplicationDto.convertToEntity(applicationConfigDto), groupIdArr); if (a > 0) { return true; } return false; } /** * [简要描述]:更新
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * jun.liu 2018/11/28 - 14:54 **/ @RequestMapping(value = "/update") public Boolean update(@RequestBody ApplicationDto applicationConfigDto) { if (null == applicationConfigDto.getId() || StringUtils.isBlank(applicationConfigDto.getApplication()) || StringUtils.isBlank(applicationConfigDto.getLabel()) || StringUtils .isBlank(applicationConfigDto.getProfile())) { log.info("更新应用失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } Application applicationConfig = applicationConfigService .selectApplicationConfigById(applicationConfigDto.getId()); if (applicationConfig == null) { log.info("通过id:" + applicationConfigDto.getId() + "获取应用失败"); throw new RuntimeException("获取应用失败"); } applicationConfig.setUpdateTime(new Date()); applicationConfig.setRegionId(applicationConfigDto.getRegionId()); applicationConfig.setProfile(applicationConfigDto.getProfile()); applicationConfig.setLabel(applicationConfigDto.getLabel()); applicationConfig.setApplicationName(applicationConfigDto.getApplicationName()); applicationConfig.setApplication(applicationConfigDto.getApplication()); int a = applicationConfigService.update(applicationConfig); if (a > 0) { return true; } return false; } /** * [简要描述]:删除
* [详细描述]:
* * @param id : * @return java.lang.Boolean * jun.liu 2018/11/28 - 14:54 **/ @RequestMapping(value = "/delete/{id}") public Boolean delete(@PathVariable("id") Long id) { if (null == id) { log.info("删除应用失败,id不能为空"); throw new RuntimeException("参数不能为空"); } int a = applicationConfigService.delete(id); if (a > 0) { return true; } return false; } /** * [简要描述]:应用更新配置
* [详细描述]:
* * @param id : 应用主键ID * @return boolean * llxiao 2019/1/30 - 10:52 **/ @RequestMapping("/refresh") public boolean refresh(@RequestParam("id") Long id) { return applicationConfigService.refresh(id); } /** * [简要描述]:批量发布
* [详细描述]:
* * @param id : 客户端ID集合 * @return boolean * llxiao 2019/3/27 - 20:05 **/ @RequestMapping("/batchRefresh") public boolean batchRefresh(@RequestBody Long[] id) { return this.applicationConfigService.batchRefresh(id); } /** * [简要描述]:获取已绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 14:59 **/ @RequestMapping(value = "/isRefApp") public PageInfo pageRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { if (null == configItemGroupQuery.getAppId()) { log.info("获取已绑定该应用的配置组失败,appId不能为空"); throw new RuntimeException("参数不能为空"); } return configItemGroupService .pageRefGroupWithApp(configItemGroupQuery, configItemGroupQuery.getPageNum(), configItemGroupQuery .getPageSize()); } /** * [简要描述]:获取未绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 15:31 **/ @RequestMapping(value = "/notRefApp") public PageInfo pageNotRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { if (null == configItemGroupQuery.getAppId()) { log.info("获取未绑定该应用的配置组失败,appId不能为空"); throw new RuntimeException("参数不能为空"); } return configItemGroupService .pageNotRefGroupWithApp(configItemGroupQuery, configItemGroupQuery.getPageNum(), configItemGroupQuery .getPageSize()); } /** * [简要描述]:批量绑定
* [详细描述]:
* * @param groupIds : * @param appId : * @return java.lang.Boolean * jun.liu 2018/11/28 - 15:34 **/ @RequestMapping(value = "/batchSaveRef") public Boolean batchSave(String groupIds, Long appId) { if (StringUtils.isBlank(groupIds) || null == appId) { log.info("绑定应用与配置组失败,{groupIds}、{appId}不能为空", groupIds, appId); throw new RuntimeException("参数不能为空"); } Application applicationConfig = applicationConfigService.selectApplicationConfigById(appId); if (null == applicationConfig) { log.info("绑定应用与配置组失败,{appId}找不到对应的应用信息", appId); return false; } String[] groupIdArr = groupIds.split(","); int a = applicationItemGroupRelationService.batchSave(groupIdArr, appId); if (a > 0) { return true; } return false; } /** * [简要描述]:删除绑定
* [详细描述]:
* * @param groupIds : * @param appId : * @return java.lang.Boolean * jun.liu 2018/11/28 - 15:39 **/ @RequestMapping(value = "/batchDeleteRef") public Boolean batchDelete(String groupIds, Long appId) { if (StringUtils.isBlank(groupIds) || null == appId) { log.info("删除应用与配置组失败,{groupIds}、{appId}不能为空", groupIds, appId); throw new RuntimeException("参数不能为空"); } String[] groupIdArr = groupIds.split(","); int a = applicationItemGroupRelationService.batchDelete(groupIdArr, appId); if (a > 0) { return true; } return false; } /** * [简要描述]:查收私有属性
* [详细描述]:
* * @param applicationConfigQuery * @return java.lang.Boolean * llxiao 2019/1/7 - 17:01 **/ @RequestMapping("/queryPrivateConfig") public PageInfo queryPrivateConfig(@RequestBody ApplicationConfigQuery applicationConfigQuery) { return this.applicationConfigService.pageQuery(applicationConfigQuery); } /** * [简要描述]:新增一条私有配置
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * llxiao 2019/1/7 - 17:06 **/ @RequestMapping("/savePrivateConfig") public Boolean savePrivateConfig(@RequestBody ApplicationConfigDto applicationConfigDto) { ApplicationConfig config = convertPrivateConf(applicationConfigDto); return this.applicationConfigService.saveApplicationConfig(config); } /** * [简要描述]:更新一条私有配置信息
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * llxiao 2019/1/7 - 17:06 **/ @RequestMapping("/updatePrivateConfig") public Boolean updatePrivateConfig(@RequestBody ApplicationConfigDto applicationConfigDto) { ApplicationConfig config = convertPrivateConf(applicationConfigDto); return this.applicationConfigService.updateApplicationConfig(config); } /** * [简要描述]:删除某项私有属性
* [详细描述]:
* * @param id : 私有属性主键ID * @return java.lang.Boolean * llxiao 2019/1/8 - 9:20 **/ @RequestMapping("/delPrivateConfig") public Boolean delPrivateConfig(@RequestParam("id") Long id) { boolean flag = false; if (null != id) { flag = this.applicationConfigService.delPrivateConfig(id); } return flag; } private ApplicationConfig convertPrivateConf(ApplicationConfigDto applicationConfigDto) { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setId(applicationConfigDto.getId()); applicationConfig.setApplicationId(applicationConfigDto.getApplicationId()); applicationConfig.setItemKey(applicationConfigDto.getItemKey()); applicationConfig.setItemValue(applicationConfigDto.getItemValue()); applicationConfig.setItemDesc(applicationConfigDto.getItemDesc()); applicationConfig.setCreateTime(applicationConfigDto.getCreateTime()); applicationConfig.setUpdateTime(applicationConfigDto.getUpdateTime()); return applicationConfig; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/AuthApi.java ================================================ package com.xiao.custom.config.service.api; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.service.service.AuthService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: 鉴权服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 17:56 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/auth") @Slf4j public class AuthApi { @Autowired private AuthService authService; /** * 根据用户名查找用户 * * @param username * @return */ @RequestMapping("/findByUsername") public AuthUser findByUsername(@RequestParam("username") String username) { return authService.findByUsername(username); } /** * 创建新用户 * * @param userDetail */ @RequestMapping("/insert") public void insert(@RequestBody AuthUser userDetail) { authService.insert(userDetail); } /** * 创建用户角色 * * @param userId * @param roleId * @return */ @RequestMapping("/insertRole") public int insertRole(@RequestParam("userId") long userId, @RequestParam("roleId") long roleId) { return authService.insertRole(userId, roleId); } /** * 根据角色id查找角色 * * @param roleId * @return */ @RequestMapping("/findRoleById") public Role findRoleById(long roleId) { return authService.findRoleById(roleId); } /** * 根据用户id查找该用户角色 * * @param userId * @return */ @RequestMapping("/findRoleByUserId") public Role findRoleByUserId(long userId) { return authService.findRoleByUserId(userId); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/ClientHostApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; import com.xiao.custom.config.service.service.ClientHostService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 15:36 * @since JDK 1.8 */ @RestController @RequestMapping("/clientInfo") @Slf4j public class ClientHostApi { @Autowired private ClientHostService clientHostService; /** * 分页查询客户端信息 * * @param query * @return */ @RequestMapping("/page") public PageInfo pageQuery(@RequestBody ClientHostInfoQuery query) { return clientHostService.pageQuery(query); } /** * [简要描述]:删除数据
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/3/27 - 15:46 **/ @RequestMapping("/del") public boolean deleteById(@RequestParam("id") Long id) { if (null != id) { clientHostService.delete(id); } else { return false; } return true; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/ConfigItemApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.entity.ConfigItem; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.service.service.ConfigItemService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 10:44 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/configItem") @Slf4j public class ConfigItemApi { @Autowired private ConfigItemService configItemService; /** * [简要描述]:分页获取
* [详细描述]:
* * @param configItemQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/27 - 10:54 **/ @RequestMapping(value = "/page") public PageInfo pageConfigItem(@RequestBody ConfigItemQuery configItemQuery) { return configItemService .pageConfigItem(configItemQuery, configItemQuery.getPageNum(), configItemQuery.getPageSize()); } /** * [简要描述]:新增
* [详细描述]:
* * @param configItemDto : * @return java.lang.Boolean * jun.liu 2018/11/27 - 13:53 **/ @RequestMapping(value = "/save") public Boolean save(@RequestBody ConfigItemDto configItemDto) { if (StringUtils.isBlank(configItemDto.getItemKey()) || StringUtils.isBlank(configItemDto.getItemValue())) { log.info("新增配置项失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } configItemDto.setCreateTime(new Date()); int a = configItemService.save(ConfigItemDto.convertToEntity(configItemDto)); if (a > 0) { return true; } return false; } /** * [简要描述]:更新
* [详细描述]:
* * @param configItemDto : * @return java.lang.Boolean * jun.liu 2018/11/27 - 14:06 **/ @RequestMapping(value = "/update") public Boolean update(@RequestBody ConfigItemDto configItemDto) { if (null == configItemDto.getId() || StringUtils.isBlank(configItemDto.getItemKey()) || StringUtils .isBlank(configItemDto.getItemValue())) { log.info("修改配置项失败,id:{}、key:{}、value:{}不能为空", configItemDto.getId(), configItemDto .getItemKey(), configItemDto.getItemValue()); throw new RuntimeException("修改配置项失败,参数不能为空"); } ConfigItem configItem = configItemService.getConfigItemById(configItemDto.getId()); if (configItem == null) { log.info("通过id:" + configItemDto.getId() + ",获取configItem失败"); throw new RuntimeException("获取对象失败"); } configItem.setItemValue(configItemDto.getItemValue()); configItem.setItemKey(configItemDto.getItemKey()); configItem.setItemDesc(configItemDto.getItemDesc()); configItem.setItemType(configItemDto.getItemType()); configItem.setUpdateTime(new Date()); int a = configItemService.update(configItem); if (a > 0) { return true; } return false; } /** * [简要描述]:删除配置项
* [详细描述]:
* * @param ids : * @return java.lang.Boolean * jun.liu 2018/11/27 - 14:39 **/ @RequestMapping(value = "/batchDelete/{ids}") public Integer enableOrDisable(@PathVariable("ids") String ids) { if (StringUtils.isBlank(ids)) { log.info("启用/禁用配置项失败,数据为空"); throw new RuntimeException("数据不能为空"); } String[] idArr = ids.split(","); int a = configItemService.batchDelete(idArr); return idArr.length - a; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/ConfigItemGroupApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.entity.ConfigItemGroup; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.service.service.ConfigItemGroupRelationService; import com.xiao.custom.config.service.service.ConfigItemGroupService; import com.xiao.custom.config.service.service.ConfigItemService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 15:31 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/configItemGroup") @Slf4j public class ConfigItemGroupApi { @Autowired private ConfigItemGroupService configItemGroupService; @Autowired private ConfigItemService configItemService; @Autowired private ConfigItemGroupRelationService configItemGroupRelationService; /** * [简要描述]:分页获取
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/27 - 15:35 **/ @RequestMapping(value = "/page") public PageInfo pageConfigItemGroup(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { return configItemGroupService .pageConfigItemGroup(configItemGroupQuery, configItemGroupQuery.getPageNum(), configItemGroupQuery .getPageSize()); } /** * [简要描述]:保存
* [详细描述]:
* * @param configItemGroupDto : * @return java.lang.Boolean * jun.liu 2018/11/27 - 15:44 **/ @RequestMapping(value = "/save") public Boolean save(@RequestBody ConfigItemGroupDto configItemGroupDto) { if (StringUtils.isBlank(configItemGroupDto.getGroupName())) { log.info("新增配置组失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } configItemGroupDto.setCreateTime(new Date()); int a = configItemGroupService.save(ConfigItemGroupDto.convertToEntity(configItemGroupDto)); if (a > 0) { return true; } return false; } /** * [简要描述]:获取组信息
* [详细描述]:
* * @param id : * @return com.winner.config.center.pojo.db.dto.ConfigItemGroupDto * jun.liu 2018/12/13 - 9:48 **/ @RequestMapping(value = "/select/{id}") public ConfigItemGroupDto getConfigItemGroupById(@PathVariable("id") Long id) { if (null == id) { log.info("获取组失败,id不能为空"); throw new RuntimeException("参数不能为空"); } return ConfigItemGroupDto.convertToDto(configItemGroupService.getConfigItemGroupById(id)); } /** * [简要描述]:更新
* [详细描述]:
* * @param configItemGroupDto : * @return java.lang.Boolean * jun.liu 2018/11/27 - 16:08 **/ @RequestMapping(value = "/update") public Boolean update(@RequestBody ConfigItemGroupDto configItemGroupDto) { if (StringUtils.isBlank(configItemGroupDto.getGroupName()) || StringUtils .isBlank(configItemGroupDto.getGroupDesc())) { log.info("修改配置组失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } ConfigItemGroup configItemGroup = configItemGroupService.getConfigItemGroupById(configItemGroupDto.getId()); if (configItemGroup == null) { log.info("通过id:" + configItemGroupDto.getId() + ",获取对象失败"); throw new RuntimeException("获取对象失败"); } configItemGroup.setGroupName(configItemGroupDto.getGroupName()); configItemGroup.setGroupDesc(configItemGroupDto.getGroupDesc()); configItemGroup.setUpdateTime(new Date()); int a = configItemGroupService.update(configItemGroup); if (a > 0) { return true; } return false; } /** * [简要描述]:删除
* [详细描述]:
* * @param ids : * @return java.lang.Boolean * jun.liu 2018/11/27 - 16:19 **/ @RequestMapping(value = "/delete/{ids}") public Integer delete(@PathVariable("ids") String ids) { if (StringUtils.isBlank(ids)) { log.info("删除失败,id不能为空"); throw new RuntimeException("参数不能为空"); } String[] idArr = ids.split(","); int a = configItemGroupService.batchDelete(idArr); a = idArr.length - a; //返回0表示全部删除成功,否则返回对应失败的数量。 return a; } /** * [简要描述]:获取已关联当前group的配置项
* [详细描述]:
* * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/27 - 16:38 **/ @RequestMapping(value = "/isRefGroup") public PageInfo pageRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery) { if (null == configItemQuery.getGroupId()) { log.info("获取已关联当前group的配置项失败,groupId不能为空"); throw new RuntimeException("参数不能为空"); } return configItemService .pageRefConfigItemWithGroup(configItemQuery, configItemQuery.getPageNum(), configItemQuery .getPageSize()); } /** * [简要描述]:获取未关联当前group的配置项
* [详细描述]:
* * @param configItemQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 10:01 **/ @RequestMapping(value = "/notRefGroup") public PageInfo pageNotRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery) { if (null == configItemQuery.getGroupId()) { log.info("获取未关联当前group的配置项失败,groupId不能为空"); throw new RuntimeException("参数不能为空"); } return configItemService .pageNotRefConfigItemWithGroup(configItemQuery, configItemQuery.getPageNum(), configItemQuery .getPageSize()); } /** * [简要描述]:行政配置项和组的绑定关系
* [详细描述]:
* * @return java.lang.Boolean * jun.liu 2018/11/28 - 9:26 **/ @RequestMapping(value = "/batchSave/{groupId}/{itemIds}") public Boolean batchSave(@PathVariable("itemIds") String itemIds, @PathVariable("groupId") Long groupId) { if (StringUtils.isBlank(itemIds) || groupId == null) { log.info("新增配置项和组绑定关系失败,itemIds和groupId不能为空"); throw new RuntimeException("参数不能为空"); } String[] itemIdArr = itemIds.split(","); int a = configItemGroupRelationService.batchSave(itemIdArr, groupId); if (a > 0) { return true; } return false; } /** * [简要描述]:批量删除
* [详细描述]:
* * @param itemIds : * @param groupId : * @return java.lang.Boolean * jun.liu 2018/11/28 - 15:12 **/ @RequestMapping(value = "/batchDelete/{groupId}/{itemIds}") public Boolean batchDelete(@PathVariable("itemIds") String itemIds, @PathVariable("groupId") Long groupId) { if (StringUtils.isBlank(itemIds) || groupId == null) { log.info("删除配置项和组绑定关系失败,itemIds和groupId不能为空"); throw new RuntimeException("参数不能为空"); } String[] itemIdArr = itemIds.split(","); int a = configItemGroupRelationService.batchDelete(itemIdArr, groupId); if (a > 0) { return true; } return false; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/RegionApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.entity.Region; import com.xiao.custom.config.pojo.query.RegionQuery; import com.xiao.custom.config.service.service.RegionService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * [简要描述]:区域管理 * [详细描述]:区域管理 * * @author jyqiao * @version 1.0, 2018年11月27日 上午11:29:12 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/region") @Slf4j public class RegionApi { @Autowired private RegionService regionService; //分页查询区域 @RequestMapping(value = "/queryRegion") public PageInfo queryRegion(@RequestBody RegionQuery regionQuery) { return regionService.pageRegion(regionQuery, regionQuery.getPageNum(), regionQuery.getPageSize()); } /** * [简要描述]:查询所有的region * [详细描述]:
* * @return java.util.List * mjye 2018/12/21 - 17:00 **/ @RequestMapping(value = "/selectRgion") public List selectRgion() { return regionService.selectRegion(); } //删除区域 @PostMapping(value = "/delectRegion/{ids}") public Boolean delectRegion(@PathVariable("ids") String ids) { if (null == ids) { log.info("删除区域信息失败,id不能为空"); throw new RuntimeException("参数不能为空"); } String[] idArr = ids.split(","); int a = regionService.batchDelete(idArr); if (a > 0) { return true; } return false; } //更新区域信息 @PostMapping(value = "/updateRegion") public Boolean updateRegion(@RequestBody RegionDto regionDto) { if (null == regionDto.getId() || StringUtils.isBlank(regionDto.getRegionDesc()) || StringUtils .isBlank(regionDto.getRegionName()) || null == regionDto.getCreateTime()) { log.info("更改区域信息失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } int b = regionService.update(regionDto); if (b > 0) { return true; } return false; } //新增区域信息 @PostMapping(value = "/addRegion") public Boolean addRegion(@RequestBody RegionDto regionDto) { if (StringUtils.isBlank(regionDto.getRegionDesc()) || StringUtils.isBlank(regionDto.getRegionName())) { log.info("新增区域信息失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } int b = regionService.save(regionDto); if (b > 0) { return true; } return false; } //根据id查询区域信息 @RequestMapping(value = "selectRegionById/{id}") public RegionDto selectRegionById(@PathVariable Long id) { if (null == id) { log.info("获取区域信息失败,id不能为空"); throw new RuntimeException("参数不能为空"); } return RegionToDto(regionService.selectRegionById(id)); } //po转dto public RegionDto RegionToDto(Region region) { RegionDto regionDto = new RegionDto(); regionDto.setId(region.getId()); regionDto.setCreateTime(region.getCreateTime()); regionDto.setRegionDesc(region.getRegionDesc()); regionDto.setRegionName(region.getRegionName()); regionDto.setUpdateTime(region.getUpdateTime()); return regionDto; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/api/ServerHostConfigApi.java ================================================ package com.xiao.custom.config.service.api; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.entity.ServerHostConfig; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; import com.xiao.custom.config.service.service.ServerHostConfigService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * [简要描述]:服务管理 * [详细描述]:服务管理 * * @author jyqiao * @version 1.0, 2018年11月27日 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/serverHostConfig") @Slf4j public class ServerHostConfigApi { @Autowired private ServerHostConfigService serverHostConfigService; //分页查询 @RequestMapping(value = "/queryServerHost") public PageInfo queryServerHost(@RequestBody ServerHostConfigQuery serverHostConfigQuery) { return serverHostConfigService .pageServerHostConfig(serverHostConfigQuery, serverHostConfigQuery.getPageNum(), serverHostConfigQuery .getPageSize()); } //删除服务管理 @RequestMapping(value = "/delectServerHostConfig/{id}") public int delectServerHostConfig(@PathVariable("id") Long id) { if (null == id) { log.info("删除服务管理失败,id不能为空"); throw new RuntimeException("参数不能为空"); } return serverHostConfigService.delete(id); } //更改服务管理 @PostMapping(value = "/updateServerHostConfig") public Boolean updateServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto) { if (null == serverHostConfigDto.getId() || StringUtils.isBlank(serverHostConfigDto.getServerDesc()) || StringUtils.isBlank(serverHostConfigDto.getServerHost()) || null == serverHostConfigDto .getRegionId()) { log.info("更改服务管理失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } int b = serverHostConfigService.update(serverHostConfigDto); if (b > 0) { return true; } return false; } //添加服务管理 @PostMapping(value = "/addServerHostConfig") public Boolean addServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto) { if (StringUtils.isBlank(serverHostConfigDto.getServerDesc()) || StringUtils .isBlank(serverHostConfigDto.getServerHost()) || null == serverHostConfigDto.getRegionId()) { log.info("新增服务管理失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } int b = serverHostConfigService.save(serverHostConfigDto); if (b > 0) { return true; } return false; } @RequestMapping(value = "/selectServerHostConfigById/{id}") public ServerHostConfigDto selectServerHostConfigById(@PathVariable("id") Long id) { if (null == id) { log.info("获取服务管理失败,id不能为空"); throw new RuntimeException("参数不能为空"); } return serverHostConfigToDto(serverHostConfigService.selectServerHostConfigById(id)); } //po转dto public ServerHostConfigDto serverHostConfigToDto(ServerHostConfig serverHostConfig) { ServerHostConfigDto serverHostConfigDto = new ServerHostConfigDto(); serverHostConfigDto.setId(serverHostConfig.getId()); serverHostConfigDto.setRegionId(serverHostConfig.getRegionId()); serverHostConfigDto.setCreateTime(serverHostConfig.getCreateTime()); serverHostConfigDto.setServerDesc(serverHostConfig.getServerDesc()); serverHostConfigDto.setServerHost(serverHostConfig.getServerHost()); serverHostConfigDto.setUpdateTime(serverHostConfig.getUpdateTime()); return serverHostConfigDto; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/feign/RefreshFeign.java ================================================ package com.xiao.custom.config.service.feign; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * [简要描述]: 刷新客户端,调用配置中心的API * [详细描述]: * * @author llxiao * @version 1.0, 2019/4/4 11:10 * @since JDK 1.8 */ @FeignClient(value = "winner-config-server") @RequestMapping("/refresh") public interface RefreshFeign { @RequestMapping("/client") boolean refresh(@RequestParam("ip") String ip, @RequestParam("port") int port); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ApplicationItemGroupRelationService.java ================================================ package com.xiao.custom.config.service.service; import com.xiao.custom.config.pojo.entity.ApplicationItemGroupRelation; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 08:58 * @since JDK 1.8 */ public interface ApplicationItemGroupRelationService { Integer save(ApplicationItemGroupRelation applicationItemGroupRelation); Integer update(ApplicationItemGroupRelation applicationItemGroupRelation); void delete(Long id); /** * [简要描述]:批量绑定
* [详细描述]:
* * @param groupIdArr : * @param appId : * @return int * jun.liu 2018/11/28 - 15:38 **/ int batchSave(String[] groupIdArr, Long appId); /** * [简要描述]:删除绑定
* [详细描述]:
* * @param groupIdArr : * @param appId : * @return int * jun.liu 2018/11/28 - 15:41 **/ int batchDelete(String[] groupIdArr, Long appId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ApplicationService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.entity.Application; import com.xiao.custom.config.pojo.entity.ApplicationConfig; import com.xiao.custom.config.pojo.query.AppQuery; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 16:47 * @since JDK 1.8 */ public interface ApplicationService { /** * [简要描述]:保存
* [详细描述]:
* * @param applicationConfig : * @return java.lang.Integer * jun.liu 2018/11/26 - 16:52 **/ Integer save(Application applicationConfig, String[] groupIdArr); Integer update(Application applicationConfig); Integer delete(Long id); PageInfo pageApplicationConfig(AppQuery appQuery, Integer pageNum, Integer pageSize); /** * [简要描述]:通过id获取
* [详细描述]:
* * @param id : * @return com.winner.config.center.pojo.db.entity.ApplicationConfig * jun.liu 2018/11/28 - 14:50 **/ Application selectApplicationConfigById(Long id); /** * [简要描述]:分页查询应用关联的私有属性
* [详细描述]:
* * @param query : * @return java.util.List * llxiao 2019/1/7 - 15:27 **/ PageInfo pageQuery(ApplicationConfigQuery query); /** * [简要描述]:保存应用的私有配置信息
* [详细描述]:
* * @param applicationConfig : * @return boolean * llxiao 2019/1/7 - 16:56 **/ boolean saveApplicationConfig(ApplicationConfig applicationConfig); /** * [简要描述]:主键更新
* [详细描述]:
* * @param applicationConfig : * @return boolean * llxiao 2019/1/7 - 16:58 **/ boolean updateApplicationConfig(ApplicationConfig applicationConfig); /** * [简要描述]:删除私有属性
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/1/8 - 9:21 **/ boolean delPrivateConfig(Long id); /** * [简要描述]:全部发布配置
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/1/30 - 10:53 **/ boolean refresh(Long id); /** * [简要描述]:指定客户端按户型配置
* [详细描述]:
* * @param hostInfoIds : 客户端信息 * @return boolean * llxiao 2019/3/27 - 14:06 **/ boolean batchRefresh(Long... hostInfoIds); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/AuthService.java ================================================ package com.xiao.custom.config.service.service; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; /** * [简要描述]: 鉴权服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 17:53 * @since JDK 1.8 */ public interface AuthService { /** * 根据用户名查找用户 * * @param username * @return */ AuthUser findByUsername(String username); /** * 创建新用户 * * @param userDetail */ void insert(AuthUser userDetail); /** * 创建用户角色 * * @param userId * @param roleId * @return */ int insertRole(long userId, long roleId); /** * 根据角色id查找角色 * * @param roleId * @return */ Role findRoleById(long roleId); /** * 根据用户id查找该用户角色 * * @param userId * @return */ Role findRoleByUserId(long userId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ClientHostService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; /** * [简要描述]: 客户端连接服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 14:13 * @since JDK 1.8 */ public interface ClientHostService { /** * 分页查询客户端信息 * * @param query * @return */ PageInfo pageQuery(ClientHostInfoQuery query); /** * 删除客户端连接信息 * * @param id */ void delete(long id); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ConfigItemGroupRelationService.java ================================================ package com.xiao.custom.config.service.service; import com.xiao.custom.config.pojo.entity.ConfigItemGroupRelation; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:41 * @since JDK 1.8 */ public interface ConfigItemGroupRelationService { Integer save(ConfigItemGroupRelation configItemGroupRelation); Integer update(ConfigItemGroupRelation configItemGroupRelation); void delete(Long id); /** * [简要描述]:批量绑定
* [详细描述]:
* * @param itemIdArr : * @param groupId : * @return int * jun.liu 2018/11/28 - 9:31 **/ int batchSave(String[] itemIdArr, Long groupId); /** * [简要描述]:批量删除
* [详细描述]:
* * @param itemIdArr : * @param groupId : * @return int * jun.liu 2018/11/28 - 15:13 **/ int batchDelete(String[] itemIdArr, Long groupId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ConfigItemGroupService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.entity.ConfigItemGroup; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:51 * @since JDK 1.8 */ public interface ConfigItemGroupService { Integer save(ConfigItemGroup configItemGroup); Integer update(ConfigItemGroup configItemGroup); Integer delete(Long id); PageInfo pageConfigItemGroup(ConfigItemGroupQuery configItemGroupQuery, Integer pageNum, Integer pageSize); /** * [简要描述]:通过id获取
* [详细描述]:
* * @param id : * @return com.winner.config.center.pojo.db.entity.ConfigItemGroup * jun.liu 2018/11/27 - 16:14 **/ ConfigItemGroup getConfigItemGroupById(Long id); /** * [简要描述]:获取已绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @param pageNum : * @param pageSize : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 15:02 **/ PageInfo pageRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery, int pageNum, int pageSize); /** * [简要描述]:获取未绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @param pageNum : * @param pageSize : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 15:02 **/ PageInfo pageNotRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery, int pageNum, int pageSize); /** * [简要描述]:批量删除
* [详细描述]:
* * @param idArr : * @return int * jun.liu 2018/12/21 - 15:18 **/ int batchDelete(String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ConfigItemService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.entity.ConfigItem; import com.xiao.custom.config.pojo.query.ConfigItemQuery; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:35 * @since JDK 1.8 */ public interface ConfigItemService { Integer save(ConfigItem configItem); Integer update(ConfigItem configItem); void delete(Long id); PageInfo pageConfigItem(ConfigItemQuery configItemQuery, Integer pageNum, Integer pageSize); /** * [简要描述]:通过id获取
* [详细描述]:
* * @param id : * @return com.winner.config.center.pojo.db.entity.ConfigItem * jun.liu 2018/11/27 - 14:25 **/ ConfigItem getConfigItemById(Long id); /** * [简要描述]:分页获取已关联group的配置项
* [详细描述]:
* * @param configItemQuery : * @param pageNum : * @param pageSize : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/27 - 16:44 **/ PageInfo pageRefConfigItemWithGroup(ConfigItemQuery configItemQuery, int pageNum, int pageSize); /** * [简要描述]:分页获取未关联group的配置项
* [详细描述]:
* * @param configItemQuery : * @param pageNum : * @param pageSize : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 10:03 **/ PageInfo pageNotRefConfigItemWithGroup(ConfigItemQuery configItemQuery, int pageNum, int pageSize); /** * [简要描述]:批量删除
* [详细描述]:
* * @param idArr : * @return int * jun.liu 2018/12/20 - 19:16 **/ int batchDelete(String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/RegionService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.entity.Region; import com.xiao.custom.config.pojo.query.RegionQuery; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:22 * @since JDK 1.8 */ public interface RegionService { int save(RegionDto regionDto); int update(RegionDto regionDto); int delete(Long id); Region selectRegionById(Long id); PageInfo pageRegion(RegionQuery regionQuery, Integer pageNum, Integer pageSize); List selectRegion(); int batchDelete(String[] idArr); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/ServerHostConfigService.java ================================================ package com.xiao.custom.config.service.service; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.entity.ServerHostConfig; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:45 * @since JDK 1.8 */ public interface ServerHostConfigService { int save(ServerHostConfigDto serverHostConfigDto); int update(ServerHostConfigDto serverHostConfigDto); /** * [简要描述]:通过id获取
* [详细描述]:
* * @param id : * @return * jyqiao **/ ServerHostConfig selectServerHostConfigById(Long id); int delete(Long id); PageInfo pageServerHostConfig(ServerHostConfigQuery serverHostConfigQuery, Integer pageNum, Integer pageSize); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ApplicationItemGroupRelationServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.xiao.custom.config.pojo.entity.ApplicationItemGroupRelation; import com.xiao.custom.config.pojo.mapper.ApplicationItemGroupRelationMapper; import com.xiao.custom.config.service.service.ApplicationItemGroupRelationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:03 * @since JDK 1.8 */ @Service public class ApplicationItemGroupRelationServiceImpl implements ApplicationItemGroupRelationService { @Autowired private ApplicationItemGroupRelationMapper applicationItemGroupRelationMapper; @Override public Integer save(ApplicationItemGroupRelation applicationItemGroupRelation) { return applicationItemGroupRelationMapper.insert(applicationItemGroupRelation); } @Override public Integer update(ApplicationItemGroupRelation applicationItemGroupRelation) { return applicationItemGroupRelationMapper.updateByPrimaryKey(applicationItemGroupRelation); } @Override public void delete(Long id) { applicationItemGroupRelationMapper.deleteByPrimaryKey(id); } @Override public int batchSave(String[] groupIdArr, Long appId) { return applicationItemGroupRelationMapper.batchSave(groupIdArr, appId); } @Override public int batchDelete(String[] groupIdArr, Long appId) { return applicationItemGroupRelationMapper.batchDelete(groupIdArr, appId); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ApplicationServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import cn.hutool.core.collection.CollectionUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.entity.Application; import com.xiao.custom.config.pojo.entity.ApplicationConfig; import com.xiao.custom.config.pojo.entity.ClientHostInfo; import com.xiao.custom.config.pojo.mapper.*; import com.xiao.custom.config.pojo.query.AppQuery; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; import com.xiao.custom.config.service.feign.RefreshFeign; import com.xiao.custom.config.service.service.ApplicationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.List; /** * [简要描述]: 应用服务 * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 16:47 * @since JDK 1.8 */ @Service @Slf4j public class ApplicationServiceImpl implements ApplicationService { /** * 刷新配置接口常量 */ private static final String REFRESH_URL = "/config/refresh"; private static final int SUCCESS = 0; private static final int HTTP_SUCCESS_CODE = 200; private static final int SERVICE_DOWN = 1; @Autowired private ApplicationMapper applicationMapper; @Autowired private ApplicationItemGroupRelationMapper applicationItemGroupRelationMapper; @Autowired private ApplicationConfigMapper applicationConfigMapper; @Autowired private ClientHostInfoMapper clientHostInfoMapper; @Autowired private ClientApplicationMapper clientApplicationMapper; @Autowired private RestTemplate restTemplate; @Autowired private RefreshFeign refreshFeign; @Override @Transactional(rollbackFor = Exception.class) public Integer save(Application applicationConfig, String[] groupIdArr) { int a = applicationMapper.insert(applicationConfig); if (groupIdArr != null && groupIdArr.length > 0 && a > 0) { Long appId = applicationConfig.getId(); return applicationItemGroupRelationMapper.batchSave(groupIdArr, appId); } return a; } @Override @Transactional(rollbackFor = Exception.class) public Integer update(Application applicationConfig) { return applicationMapper.updateByPrimaryKey(applicationConfig); } @Override @Transactional(rollbackFor = Exception.class) public Integer delete(Long id) { int delFlag = applicationMapper.deleteByPrimaryKey(id); if (delFlag > 0) { //删除关联应用的配置组信息 applicationItemGroupRelationMapper.deleteByAppId(id); } return delFlag; } @Override public PageInfo pageApplicationConfig(AppQuery appQuery, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); return new PageInfo<>(applicationMapper.pageApplicationConfig(appQuery)); } @Override public Application selectApplicationConfigById(Long id) { return applicationMapper.selectByPrimaryKey(id); } /** * [简要描述]:分页查询应用关联的私有属性
* [详细描述]:
* * @param query : * @return java.util.List * llxiao 2019/1/7 - 15:27 **/ @Override public PageInfo pageQuery(ApplicationConfigQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List applicationConfigDtos = new ArrayList<>(); if (null != query && null != query.getApplicationId()) { applicationConfigDtos = applicationConfigMapper.pageQuery(query); } return new PageInfo<>(applicationConfigDtos); } /** * [简要描述]:保存应用的私有配置信息
* [详细描述]:
* * @param applicationConfig : * @return boolean * llxiao 2019/1/7 - 16:56 **/ @Override @Transactional(rollbackFor = Exception.class) public boolean saveApplicationConfig(ApplicationConfig applicationConfig) { int flag = 0; if (null != applicationConfig) { flag = this.applicationConfigMapper.insert(applicationConfig); } return flag > 0; } /** * [简要描述]:主键更新
* [详细描述]:
* * @param applicationConfig : * @return boolean * llxiao 2019/1/7 - 16:58 **/ @Override @Transactional(rollbackFor = Exception.class) public boolean updateApplicationConfig(ApplicationConfig applicationConfig) { int flag = 0; if (null != applicationConfig && null != applicationConfig.getId()) { flag = this.applicationConfigMapper.updateByPrimaryKeySelective(applicationConfig); } return flag > 0; } /** * [简要描述]:删除私有属性
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/1/8 - 9:21 **/ @Override public boolean delPrivateConfig(Long id) { if (null != id) { return this.applicationConfigMapper.deleteByPrimaryKey(id) > 0; } return false; } /** * [简要描述]:发布配置
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/1/30 - 10:53 **/ @Override public boolean refresh(Long id) { Application application = this.applicationMapper.selectByPrimaryKey(id); if (null != application) { //应用+环境查找 已连接上的应用 List hostInfos = clientHostInfoMapper .queryByApplication(application.getApplication(), application.getProfile()); int size = 0; if (CollectionUtil.isNotEmpty(hostInfos)) { for (ClientHostInfo hostInfo : hostInfos) { if (refreshFeign.refresh(hostInfo.getNettyIp(), hostInfo.getNettyPort())) { size++; } // if (restRefresh(hostInfo)) // { // size++; // } } //所有更新服务失败,需要标记应用下线 if (size == 0) { clientApplicationMapper .updateStatus(application.getApplication(), application.getProfile(), SERVICE_DOWN); } return size > 0; } } return false; } /** * [简要描述]:指定客户端按户型配置
* [详细描述]:
* * @param hostInfoIds : 客户端信息 * @return boolean * llxiao 2019/3/27 - 14:06 **/ @Override public boolean batchRefresh(Long... hostInfoIds) { int size = 0; if (null != hostInfoIds) { ClientHostInfo hostInfo; for (Long hostInfoId : hostInfoIds) { hostInfo = this.clientHostInfoMapper.selectByPrimaryKey(hostInfoId); //在线状态 if (null != hostInfo && hostInfo.getStatus() == ClientHostInfo.ONLINE) { if (refreshFeign.refresh(hostInfo.getNettyIp(), hostInfo.getNettyPort())) { size++; } // if (restRefresh(hostInfo)) // { // size++; // } } } } return size > 0; } // /** // * [简要描述]:远程发起刷新请求
// * [详细描述]:
// * // * @param hostInfo : // * llxiao 2019/1/30 - 11:30 // **/ // private boolean restRefresh(ClientHostInfo hostInfo) // { // String url = "http://" + hostInfo.getHostIp() + ':' + hostInfo.getHostPort() + REFRESH_URL; // // 封装参数,千万不要替换为Map与HashMap,否则参数无法传递 // MultiValueMap paramMap = new LinkedMultiValueMap<>(); // // 消息头 // HttpHeaders headers = new HttpHeaders(); // HttpEntity> httpEntity = new HttpEntity<>(paramMap, headers); // boolean flag = true; // int status = 0; // try // { // ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST, httpEntity, Integer.class); // status = result.getStatusCodeValue(); // if (HTTP_SUCCESS_CODE == status && SUCCESS == result.getBody()) // { // log.info("应用更新成功:应用服务IP:{},应用服务PORT:{}", hostInfo.getHostIp(), hostInfo.getHostPort()); // } // else // { // log.error("应用更新失败,服务端返回状态:{},返回更新结果:{}", status, result.getBody()); // flag = false; // } // } // catch (Exception e) // { // log.error("服务错误!", e.getMessage()); // } // if (flag) // { // log.error("应用发布失败:应用服务IP:{},应用服务PORT:{}", hostInfo.getHostIp(), hostInfo.getHostPort()); // log.error("服务返回HTTP状态:{}", status); // //标记服务已经下线 // clientHostInfoMapper.updateStatus(hostInfo.getId(), SERVICE_DOWN); // } // return flag; // } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/AuthServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.pojo.mapper.AuthMapper; import com.xiao.custom.config.service.service.AuthService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * [简要描述]: 鉴权服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 17:54 * @since JDK 1.8 */ @Service public class AuthServiceImpl implements AuthService { @Autowired private AuthMapper authMapper; /** * 根据用户名查找用户 * * @param username * @return */ @Override public AuthUser findByUsername(String username) { AuthUser authUser = null; if (StringUtils.isNotBlank(username)) { authUser = authMapper.findByUsername(username); Role role = authMapper.findRoleByUserId(authUser.getId()); authUser.setRole(role); } return authUser; } /** * 创建新用户 * * @param userDetail */ @Override @Transactional(rollbackFor = Exception.class) public void insert(AuthUser userDetail) { if (null != userDetail) { authMapper.insert(userDetail); } } /** * 创建用户角色 * * @param userId * @param roleId * @return */ @Override @Transactional(rollbackFor = Exception.class) public int insertRole(long userId, long roleId) { return authMapper.insertRole(userId, roleId); } /** * 根据角色id查找角色 * * @param roleId * @return */ @Override public Role findRoleById(long roleId) { return authMapper.findRoleById(roleId); } /** * 根据用户id查找该用户角色 * * @param userId * @return */ @Override public Role findRoleByUserId(long userId) { return authMapper.findRoleByUserId(userId); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ClientHostServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.mapper.ClientHostInfoMapper; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; import com.xiao.custom.config.service.service.ClientHostService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 15:24 * @since JDK 1.8 */ @Service public class ClientHostServiceImpl implements ClientHostService { @Autowired private ClientHostInfoMapper clientHostInfoMapper; /** * 分页查询客户端信息 * * @param query * @return */ @Override public PageInfo pageQuery(ClientHostInfoQuery query) { List clientHostInfoDtos = new ArrayList<>(); if (null != query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); clientHostInfoDtos = this.clientHostInfoMapper.pageQuery(query); } return new PageInfo<>(clientHostInfoDtos); } /** * 删除客户端连接信息 * * @param id */ @Override public void delete(long id) { this.clientHostInfoMapper.deleteByPrimaryKey(id); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ConfigItemGroupRelationServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.xiao.custom.config.pojo.entity.ConfigItemGroupRelation; import com.xiao.custom.config.pojo.mapper.ConfigItemGroupRelationMapper; import com.xiao.custom.config.service.service.ConfigItemGroupRelationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:43 * @since JDK 1.8 */ @Service public class ConfigItemGroupRelationServiceImpl implements ConfigItemGroupRelationService { @Autowired private ConfigItemGroupRelationMapper configItemGroupRelationMapper; @Override public Integer save(ConfigItemGroupRelation configItemGroupRelation) { return configItemGroupRelationMapper.insert(configItemGroupRelation); } @Override public Integer update(ConfigItemGroupRelation configItemGroupRelation) { return configItemGroupRelationMapper.updateByPrimaryKey(configItemGroupRelation); } @Override public void delete(Long id) { configItemGroupRelationMapper.deleteByPrimaryKey(id); } @Override public int batchSave(String[] itemIdArr, Long groupId) { return configItemGroupRelationMapper.batchSave(itemIdArr, groupId); } @Override public int batchDelete(String[] itemIdArr, Long groupId) { return configItemGroupRelationMapper.batchDelete(itemIdArr, groupId); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ConfigItemGroupServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import cn.hutool.core.util.ArrayUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.entity.ConfigItemGroup; import com.xiao.custom.config.pojo.mapper.ApplicationItemGroupRelationMapper; import com.xiao.custom.config.pojo.mapper.ConfigItemGroupMapper; import com.xiao.custom.config.pojo.mapper.ConfigItemGroupRelationMapper; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.service.service.ConfigItemGroupService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:54 * @since JDK 1.8 */ @Service public class ConfigItemGroupServiceImpl implements ConfigItemGroupService { @Autowired private ConfigItemGroupMapper configItemGroupMapper; @Autowired private ConfigItemGroupRelationMapper configItemGroupRelationMapper; @Autowired private ApplicationItemGroupRelationMapper applicationItemGroupRelationMapper; @Override public Integer save(ConfigItemGroup configItemGroup) { return configItemGroupMapper.insert(configItemGroup); } @Override public Integer update(ConfigItemGroup configItemGroup) { return configItemGroupMapper.updateByPrimaryKey(configItemGroup); } @Override public Integer delete(Long id) { return configItemGroupMapper.deleteByPrimaryKey(id); } @Override public PageInfo pageConfigItemGroup(ConfigItemGroupQuery configItemGroupQuery, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemGroupMapper.pageConfigItemGroup(configItemGroupQuery); return new PageInfo<>(list); } @Override public ConfigItemGroup getConfigItemGroupById(Long id) { return configItemGroupMapper.selectByPrimaryKey(id); } @Override public PageInfo pageRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemGroupMapper.pageRefGroupWithApp(configItemGroupQuery); return new PageInfo<>(list); } @Override public PageInfo pageNotRefGroupWithApp(ConfigItemGroupQuery configItemGroupQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemGroupMapper.pageNotRefGroupWithApp(configItemGroupQuery); return new PageInfo<>(list); } @Override public int batchDelete(String[] idArr) { List delGroupIds = new ArrayList<>(); //删除组之前,已经关联应用的不能进行删除,必须先从应用中解除 for (String groupId : idArr) { //删除组之前,已经关联应用的不能进行删除,必须先从应用中解除 if (applicationItemGroupRelationMapper.countByGroupId(groupId) > 0) { continue; } else { delGroupIds.add(groupId); } } String[] del = ArrayUtil.toArray(delGroupIds, String.class); // 删除组关联的配置项 configItemGroupRelationMapper.batchDeleteByGroupId(del); return configItemGroupMapper.batchDelete(del); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ConfigItemServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.entity.ConfigItem; import com.xiao.custom.config.pojo.mapper.ConfigItemGroupRelationMapper; import com.xiao.custom.config.pojo.mapper.ConfigItemMapper; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.service.service.ConfigItemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:38 * @since JDK 1.8 */ @Service public class ConfigItemServiceImpl implements ConfigItemService { @Autowired private ConfigItemMapper configItemMapper; @Autowired private ConfigItemGroupRelationMapper configItemGroupRelationMapper; @Override public Integer save(ConfigItem configItem) { return configItemMapper.insert(configItem); } @Override public Integer update(ConfigItem configItem) { return configItemMapper.updateByPrimaryKey(configItem); } @Override public void delete(Long id) { configItemMapper.deleteByPrimaryKey(id); } @Override public PageInfo pageConfigItem(ConfigItemQuery configItemQuery, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemMapper.pageConfigItem(configItemQuery); return new PageInfo<>(list); } @Override public ConfigItem getConfigItemById(Long id) { return configItemMapper.selectByPrimaryKey(id); } @Override public PageInfo pageRefConfigItemWithGroup(ConfigItemQuery configItemQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemMapper.pageRefConfigItemWithGroup(configItemQuery); return new PageInfo<>(list); } @Override public PageInfo pageNotRefConfigItemWithGroup(ConfigItemQuery configItemQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List list = configItemMapper.pageNotRefConfigItemWithGroup(configItemQuery); return new PageInfo<>(list); } @Override public int batchDelete(String[] idArr) { List dels = new ArrayList<>(); //已经关联组的配置项,不能进行删除,必须先解除组与配置项的关系才能进行删除 for (String itemId : idArr) { if (configItemGroupRelationMapper.countByItemId(itemId) > 0) { continue; } else { dels.add(itemId); } } // configItemGroupRelationMapper.batchDeleteByItemId(idArr); if (CollectionUtil.isNotEmpty(dels)) { return configItemMapper.batchDelete(ArrayUtil.toArray(dels, String.class)); } else { return 0; } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/RegionServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.entity.Region; import com.xiao.custom.config.pojo.mapper.RegionMapper; import com.xiao.custom.config.pojo.query.RegionQuery; import com.xiao.custom.config.service.service.RegionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/26 17:22 * @since JDK 1.8 */ @Service public class RegionServiceImpl implements RegionService { @Autowired private RegionMapper regionMapper; /** * [简要描述]:添加区域信息
* [详细描述]:
* * @return Integer **/ @Override @Transactional public int save(RegionDto regionDto) { regionDto.setCreateTime(new Date()); Region region = regionDtoconvertRegion(regionDto); return regionMapper.insert(region); } /** * [简要描述]:更新区域信息
* [详细描述]:
* * @return Integer **/ @Override @Transactional public int update(RegionDto regionDto) { Region region = regionDtoconvertRegion(regionDto); return regionMapper.updateByPrimaryKey(region); } /** * [简要描述]:根据id删除
* [详细描述]:
* * @param id: * @return int **/ @Override @Transactional public int delete(Long id) { return regionMapper.deleteByPrimaryKey(id); } /** * [简要描述]:分页查询
* [详细描述]:
* * @return RegionDto **/ @Override public PageInfo pageRegion(RegionQuery regionQuery, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List list = regionMapper.pageRegion(regionQuery); return new PageInfo<>(list); } /** * [简要描述]:查询所有的region * [详细描述]:
* * @return java.util.List * mjye 2018/12/21 - 16:58 **/ @Override public List selectRegion() { return regionMapper.selectRegion(); } /** * [简要描述]:批量删除 * [详细描述]:
* * @param idArr : * @return int * mjye 2018/12/25 - 11:09 **/ @Override public int batchDelete(String[] idArr) { return regionMapper.batchDelete(idArr); } /** * [简要描述]:RegionDto转Region
* [详细描述]:RegionDto转Region
* * @return Region **/ public Region regionDtoconvertRegion(RegionDto regionDto) { Region region = new Region(); region.setId(regionDto.getId()); region.setCreateTime(regionDto.getCreateTime()); region.setRegionDesc(regionDto.getRegionDesc()); region.setRegionName(regionDto.getRegionName()); region.setUpdateTime(new Date()); return region; } /** * [简要描述]:根据id查询区域 * [详细描述]:
* * @param id : * @return com.winner.config.center.pojo.db.entity.Region * mjye 2018/12/21 - 16:57 **/ @Override public Region selectRegionById(Long id) { return regionMapper.selectByPrimaryKey(id); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/java/com/xiao/custom/config/service/service/impl/ServerHostConfigServiceImpl.java ================================================ package com.xiao.custom.config.service.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.entity.ServerHostConfig; import com.xiao.custom.config.pojo.mapper.ApplicationMapper; import com.xiao.custom.config.pojo.mapper.ServerHostConfigMapper; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; import com.xiao.custom.config.service.service.ServerHostConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Date; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/11/27 09:48 * @since JDK 1.8 */ @Service public class ServerHostConfigServiceImpl implements ServerHostConfigService { @Resource private ServerHostConfigMapper serverHostConfigMapper; @Autowired private ApplicationMapper applicationConfigMapper; @Override @Transactional public int save(ServerHostConfigDto serverHostConfigDto) { serverHostConfigDto.setCreateTime(new Date()); ServerHostConfig serverHostConfig = serverHostConfigDtoconvertserverHostConfig(serverHostConfigDto); return serverHostConfigMapper.insert(serverHostConfig); } @Override @Transactional public int update(ServerHostConfigDto serverHostConfigDto) { ServerHostConfig serverHostConfig = serverHostConfigDtoconvertserverHostConfig(serverHostConfigDto); return serverHostConfigMapper.updateByPrimaryKey(serverHostConfig); } /** * [简要描述]: -1 标识删除失败,已关联应用不能删除该区域,必须先删除应用
* [详细描述]:
* * @param id : * @return int * llxiao 2019/1/2 - 17:54 **/ @Override @Transactional public int delete(Long id) { // if (applicationConfigMapper.countByRegionId(id) > 0) // { // return -1; // } return serverHostConfigMapper.deleteByPrimaryKey(id); } @Override public PageInfo pageServerHostConfig(ServerHostConfigQuery serverHostConfigQuery, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); List list = serverHostConfigMapper.pageServerHostConfig(serverHostConfigQuery); return new PageInfo<>(list); } /** * [简要描述]:serverHostConfigDto转ServerHostConfig
* [详细描述]:serverHostConfigDto转ServerHostConfig
* * @return ServerHostConfig **/ public ServerHostConfig serverHostConfigDtoconvertserverHostConfig(ServerHostConfigDto serverHostConfigDto) { ServerHostConfig serverHostConfig = new ServerHostConfig(); serverHostConfig.setId(serverHostConfigDto.getId()); serverHostConfig.setRegionId(serverHostConfigDto.getRegionId()); serverHostConfig.setServerDesc(serverHostConfigDto.getServerDesc()); serverHostConfig.setServerHost(serverHostConfigDto.getServerHost()); serverHostConfig.setCreateTime(serverHostConfigDto.getCreateTime()); serverHostConfig.setUpdateTime(new Date()); return serverHostConfig; } @Override public ServerHostConfig selectServerHostConfigById(Long id) { // TODO Auto-generated method stub return serverHostConfigMapper.selectByPrimaryKey(id); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/resources/application.yml ================================================ spring: datasource: url: jdbc:mysql://192.168.206.210:3306/config_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: admin password: Admin@123 driver-class-name: com.mysql.jdbc.Driver # 使用druid数据源 type: com.alibaba.druid.pool.DruidDataSource cloud: #开启重试机制,它默认是关闭的 loadbalancer: retry: enable: true eureka: ## 客户端实例信息配置 instance: hostname: localhost instance-id: ${spring.cloud.client.ipAddress}:${server.port} prefer-ip-address: true ##服务续约重要属性 ##设置心跳的周期间隔(默认90s)[如果10s没响应默认服务宕机] lease-expiration-duration-in-seconds: 90 #设置心跳时间间隔(默认30s)[心跳时间2s] lease-renewal-interval-in-seconds: 30 ##端点配置。若配置了context-path:${server.context-path}/info,actuator的监控端点会增加前缀,此时eureka也需要相应增加 status-page-url-path: /info health-check-url-path: /health # 客户端配置 client: ## 启用客户端 ,默认true enabled: true # 是否在本地缓存注册表信息,默认为true fetch-registry: true # 缓存清单更新时间,默认30秒 registry-fetch-interval-seconds: 20 # 发布到注册中心,默认true register-with-eureka: true serviceUrl: defaultZone: @register-center-url@ #开启健康检查(需要spring-boot-starter-actuator依赖) healthcheck: enable: true logging.level.com.netflix.eureka.registry: ERROR ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-service/src/main/resources/bootstrap.yml ================================================ server: port: 9001 spring: cloud: config: uri: http://localhost:9000/config/ profile: @env@ label: master name: config-center-service application: name: config-center-service #logging.level.root: debug ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/README.md ================================================ 1. 客户端使用demo:
```$xslt com.xiao.skywalking.demo custom-starter-config ${project.version} ``` ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-simple org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-devtools true com.xiao.skywalking.demo custom-starter-config ${project.version} com.alibaba druid RELEASE compile dev true dev test test prod prod ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/src/main/java/com/xiao/custom/config/simple/ConfigClientApplication.java ================================================ package com.xiao.custom.config.simple; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/21 17:20 * @since JDK 1.8 */ @SpringBootApplication() public class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/src/main/java/com/xiao/custom/config/simple/datasource/DataSourceConfigure.java ================================================ package com.xiao.custom.config.simple.datasource; import lombok.extern.slf4j.Slf4j; /** * [简要描述]: 初始化数据源 * [详细描述]: RefreshScope标识需要刷新的数据 * * @author llxiao * @version 1.0, 2019/1/30 09:09 * @since JDK 1.8 */ //@RefreshScope //@Configuration @Slf4j public class DataSourceConfigure { // @Bean // @RefreshScope// 刷新配置文件 // @Primary //Primary可以理解为默认优先选择,同时不可以同时设置多个 // @ConfigurationProperties(prefix = "spring.datasource") // 数据源的自动配置的前缀 // public DataSource dataSource() // { // return DataSourceBuilder.create().build(); // } // @Value("${spring.datasource.url}") // private String dbUrl; // @Value("${spring.datasource.username}") // private String username; // @Value("${spring.datasource.password}") // private String password; // @Value("${spring.datasource.driverClassName}") // private String driverClassName; // @Value("${spring.datasource.initialSize}") // private int initialSize; // @Value("${spring.datasource.minIdle}") // private int minIdle; // @Value("${spring.datasource.maxActive}") // private int maxActive; // @Value("${spring.datasource.maxWait}") // private int maxWait; // @Value("${spring.datasource.timeBetweenEvictionRunsMillis}") // private int timeBetweenEvictionRunsMillis; // @Value("${spring.datasource.minEvictableIdleTimeMillis}") // private int minEvictableIdleTimeMillis; // @Value("${spring.datasource.validationQuery}") // private String validationQuery; // @Value("${spring.datasource.testWhileIdle}") // private boolean testWhileIdle; // @Value("${spring.datasource.testOnBorrow}") // private boolean testOnBorrow; // @Value("${spring.datasource.testOnReturn}") // private boolean testOnReturn; // @Value("${spring.datasource.poolPreparedStatements}") // private boolean poolPreparedStatements; // @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}") // private int maxPoolPreparedStatementPerConnectionSize; // @Value("${spring.datasource.filters}") // private String filters; // @Value("${spring.datasource.connectionProperties}") // private String connectionProperties; // @Value("${spring.datasource.useGlobalDataSourceStat}") // private boolean useGlobalDataSourceStat; // // @Bean //声明其为Bean实例 // @Primary //在同样的DataSource中,首先使用被标注的DataSource // @RefreshScope // public DataSource dataSource() // { // DruidDataSource datasource = new DruidDataSource(); // datasource.setUrl(this.dbUrl); // datasource.setUsername(username); // datasource.setPassword(password); // datasource.setDriverClassName(driverClassName); // // //configuration // datasource.setInitialSize(initialSize); // datasource.setMinIdle(minIdle); // datasource.setMaxActive(maxActive); // datasource.setMaxWait(maxWait); // datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); // datasource.setValidationQuery(validationQuery); // datasource.setTestWhileIdle(testWhileIdle); // datasource.setTestOnBorrow(testOnBorrow); // datasource.setTestOnReturn(testOnReturn); // datasource.setPoolPreparedStatements(poolPreparedStatements); // datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); // datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat); // try // { // datasource.setFilters(filters); // } // catch (SQLException e) // { // log.error("druid configuration initialization filter: " + e); // } // datasource.setConnectionProperties(connectionProperties); // return datasource; // } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/src/main/java/com/xiao/custom/config/simple/demo/ControllerDemo.java ================================================ package com.xiao.custom.config.simple.demo; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/21 17:23 * @since JDK 1.8 */ @RestController @RequestMapping("/demo") @RefreshScope public class ControllerDemo { @Value("${dymaic:test}") private String dbUrl; @RequestMapping("/getDbUrl") public String getDbUrl() { return dbUrl; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/src/main/resources/bootstrap.yml ================================================ server: port: 9003 spring: cloud: config: uri: http://172.16.80.194:9000/config profile: dev label: master name: config-center-simple application: name: config-center-simple management: security: enabled: false #自定义的netty服务端口,默认8999 netty: server: port: 8999 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-simple/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} utf8 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/README.md ================================================ 1. 配置管理平台
访问地址:http://localhost:9002 2. 新增spring session+ spring security + jwt简单鉴权

实现方案简要说明:

>> 1). 后端实现主要参考``com.xiao.custom.config.web.auth.config.WebSecurityConfig``,浏览器交互需要用上session

>> 2). 前端实现思路,如果是浏览器正常session交互,如果是ajax请求,需要在请求前加上jwt的token,代码片段:

```$xslt beforeSend: function (xhr) { if ( this.loadArea ){ this.loadArea.$set(this.loadArea,'loading',true) }else{ this.showLoad && App.showLoadding(null, null, xhr); } this.beforeCallback && this.beforeCallback.call(this,xhr); // 设置token 本地获取token 关键部分 var token = window.localStorage.getItem('M-Auth-Token'); xhr.setRequestHeader('M-Auth-Token', token); }, ``` >> 3). 使用到的相关的pom配置

```$xslt org.springframework.boot spring-boot-starter-security 1.5.9.RELEASE org.springframework.security spring-security-jwt 1.0.9.RELEASE io.jsonwebtoken jjwt 0.9.0 org.springframework.session spring-session ``` ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/pom.xml ================================================ SpringCloud-Custom-ConfigCenter com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 custom-config-web org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-ribbon org.springframework.cloud spring-cloud-starter-hystrix org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator com.github.pagehelper pagehelper 5.1.4 com.xiao.skywalking.demo custom-config-pojo ${project.version} com.xiao.skywalking.demo SpringCloud-Common ${project.version} org.springframework.boot spring-boot-starter-security 1.5.9.RELEASE org.springframework.security spring-security-jwt 1.0.9.RELEASE io.jsonwebtoken jjwt 0.9.0 org.springframework.session spring-session org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import custom-config-web ${project.build.directory}/classes src/main/resources true static/** **/*.xml **/*.yml **/*.properties **/banner.txt META-INF/** **/*.woff **/*.woff2 **/*.ttf **/*.ico src/main/resources false **/*.woff **/*.woff2 **/*.ttf **/*.ico org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/ConfigCenterWebApplication.java ================================================ package com.xiao.custom.config.web; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/27 09:00 * @since JDK 1.8 */ @SpringBootApplication(scanBasePackages = "com.xiao.custom.config") @EnableEurekaClient //开启fegin @EnableFeignClients // 开启熔断功能 //@EnableCircuitBreaker public class ConfigCenterWebApplication { public static void main(String[] args) { SpringApplication.run(ConfigCenterWebApplication.class, args); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/AuthContants.java ================================================ package com.xiao.custom.config.web.auth; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/9 14:55 * @since JDK 1.8 */ public interface AuthContants { /** * 自定义token头 */ String TOKEN_HEADER = "M-Auth-Token"; /** * token 开头 */ String TOKEN_BEARER_START = "Bearer "; /** * reqeust中存储username */ String REQUEST_USER_NAME = "username"; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/config/HttpSessionConfig.java ================================================ package com.xiao.custom.config.web.auth.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.session.web.http.CookieHttpSessionStrategy; import org.springframework.session.web.http.DefaultCookieSerializer; import org.springframework.session.web.http.HttpSessionStrategy; /** * 分布式缓存机制 * * @author zhdong */ @Configuration //@EnableRedissonHttpSession(maxInactiveIntervalInSeconds = 18000) public class HttpSessionConfig { /** * 基于session缓存的策略 * 1.如果配置了header * 那么就使用header的策略,一般情况针对app和移动端适用 * 2.cookie策略则适应于pc端浏览器 * * @return */ @Bean public HttpSessionStrategy httpSessionStrategy() { CookieHttpSessionStrategy sessionStrategy = new CookieHttpSessionStrategy(); DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookiePath("/"); sessionStrategy.setCookieSerializer(serializer); return sessionStrategy; } @Bean public RequestCache requestCache() { return new HttpSessionRequestCache(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/config/JwtAuthenticationEntryPoint.java ================================================ package com.xiao.custom.config.web.auth.config; import com.alibaba.fastjson.JSONObject; import com.xiao.springcloud.demo.common.exception.CommonExceptionEnum; import com.xiao.springcloud.demo.common.gloab.response.ErrorResponseData; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Serializable; /** * 登录失败处理 */ @Component @Slf4j public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Autowired private RequestCache requestCache; @Value("${config.center.loginUrl}") private String loginUrl; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { //验证为未登陆状态会进入此方法,认证错误 log.error("认证失败:" + authException.getMessage()); log.error("请求url: " + request.getRequestURI()); cookiesStrategy(request, response, authException); // headerStategy(request,response,authException); } private void cookiesStrategy(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { // cookie方式认证直接跳转登录页面 String xhr = request.getHeader("X-Requested-With"); // 非ajax请求 if (StringUtils.isEmpty(xhr)) { requestCache.saveRequest(request, response); SavedRequest saveRequest = requestCache.getRequest(request, response); RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); // 获取 跳转url String targetUrl = saveRequest.getRedirectUrl(); log.info("引发跳转的请求是:" + targetUrl); try { redirectStrategy.sendRedirect(request, response, loginUrl + "?refUrl=" + targetUrl); } catch (IOException e1) { log.error("JwtAuthenticationTokenFilter,重定向错误", e1); } return; } else { try { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().print(JSONObject.toJSON(ErrorResponseData .error(CommonExceptionEnum.NO_LOGIN.getCode(), CommonExceptionEnum.NO_LOGIN.getMessage()))); } catch (IOException e1) { log.error("EntryPointUnauthorizedHandler返回错误", e); } } } private void headerStategy(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { response.setHeader("Access-Control-Allow-Origin", "*"); try { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().print(JSONObject.toJSON(ErrorResponseData .error(CommonExceptionEnum.NO_LOGIN.getCode(), CommonExceptionEnum.NO_LOGIN.getMessage()))); } catch (IOException e1) { log.error("EntryPointUnauthorizedHandler返回错误", e1); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/config/JwtAuthenticationTokenFilter.java ================================================ package com.xiao.custom.config.web.auth.config; import com.xiao.custom.config.web.auth.AuthContants; import com.xiao.custom.config.web.auth.entity.UserDetail; import com.xiao.custom.config.web.auth.util.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.annotation.Resource; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * token校验 */ @Component @Slf4j public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Resource private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authToken = request.getHeader(AuthContants.TOKEN_HEADER); // 以 Bearer 开头的token if (StringUtils.isNotEmpty(authToken) && authToken.startsWith(AuthContants.TOKEN_BEARER_START)) { authToken = authToken.substring(AuthContants.TOKEN_BEARER_START.length()); accessToken(request, authToken); } chain.doFilter(request, response); } private void accessToken(HttpServletRequest request, String authToken) { String username = jwtUtils.getUsernameFromToken(authToken); if (jwtUtils.containToken(username, authToken) && username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 可以考虑做分布式session UserDetail userDetail = jwtUtils.getUserFromToken(authToken); if (jwtUtils.validateToken(authToken, userDetail)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail .getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); log.info("Authenticated userDetail {}, setting security context", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } //存储用户登录名,下一步流程处理 request.setAttribute(AuthContants.REQUEST_USER_NAME, username); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/config/RestAccessDeniedHandler.java ================================================ package com.xiao.custom.config.web.auth.config; import com.xiao.custom.config.web.auth.util.ResultCode; import com.xiao.custom.config.web.auth.util.ResultJson; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 权限不足处理 * createAt: 2018/9/21 */ @Component("restAuthenticationAccessDeniedHandler") @Slf4j public class RestAccessDeniedHandler implements AccessDeniedHandler { /** * 登陆状态下,权限不足执行该方法 * * @param httpServletRequest * @param response * @param e * @exception IOException * @exception ServletException */ @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException { log.error("权限不足:" + e.getMessage()); //浏览器方式 webBrowser(response); // API接口方式 // api(response, e); } private void api(HttpServletResponse response, AccessDeniedException e) throws IOException { // 接口返回以json格式 response.setStatus(200); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); PrintWriter printWriter = response.getWriter(); String body = ResultJson.failure(ResultCode.FORBIDDEN, e.getMessage()).toString(); printWriter.write(body); printWriter.flush(); } private void webBrowser(HttpServletResponse response) throws IOException { response.setCharacterEncoding("UTF-8"); response.setHeader("Access-Control-Allow-Origin", "*"); response.sendError(HttpStatus.FORBIDDEN.value(), "没有访问权限"); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/config/WebSecurityConfig.java ================================================ package com.xiao.custom.config.web.auth.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.annotation.Resource; /** * springoot + Security + jwt登录认证 * Author: JoeTao * createAt: 2018/9/14 */ @Configuration @EnableWebSecurity //@EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Resource(name = "restAuthenticationAccessDeniedHandler") private AccessDeniedHandler accessDeniedHandler; @Resource(name = "configUserDetailsService") private UserDetailsService configUserDetailsService; @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; @Value("${config.center.anonymous.urls}") private String anonymousUrls; @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder // 设置UserDetailsService .userDetailsService(configUserDetailsService) // 使用BCrypt进行密码的hash .passwordEncoder(passwordEncoder()); } /** * 装载BCrypt密码编码器 * * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 基于token 接口间鉴权 // tokenConfigure(httpSecurity); // 基于session 适应于浏览器 sessionConfig(httpSecurity); httpSecurity.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll() //排除某些指定页面 .antMatchers(anonymousUrls.split(",")).permitAll().anyRequest().authenticated(); // 禁用缓存 httpSecurity.headers().cacheControl(); // 添加JWT filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 设置 鉴权失败和无权的处理 httpSecurity.exceptionHandling().authenticationEntryPoint(unauthorizedHandler) .accessDeniedHandler(accessDeniedHandler); } private void sessionConfig(HttpSecurity httpSecurity) throws Exception { //默认采用httpsession的方式 SessionCreationPolicy sessionPolicy = SessionCreationPolicy.IF_REQUIRED; //屏蔽csrf,否则post无法访问 httpSecurity.cors().and().csrf().disable() //session管理策略 .sessionManagement().sessionCreationPolicy(sessionPolicy); } private void tokenConfigure(HttpSecurity httpSecurity) throws Exception { httpSecurity // 由于使用的是JWT,我们这里不需要csrf .csrf().disable() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Override public void configure(WebSecurity web) { //忽略鉴权的请求 web.ignoring().antMatchers("/html/assets/**").antMatchers("/favicon.ico"); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/controller/AuthController.java ================================================ package com.xiao.custom.config.web.auth.controller; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.web.auth.AuthContants; import com.xiao.custom.config.web.auth.entity.ResponseUserToken; import com.xiao.custom.config.web.auth.entity.User; import com.xiao.custom.config.web.auth.entity.UserDetail; import com.xiao.custom.config.web.auth.service.AuthUserService; import com.xiao.custom.config.web.auth.util.ResultCode; import com.xiao.custom.config.web.auth.util.ResultJson; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author JoeTao * createAt: 2018/9/17 */ @RestController //@Api(description = "登陆注册及刷新token") @RequestMapping("/user/auth") public class AuthController { private static final long ADMIN_TYPE = 1; @Autowired private AuthUserService authService; @PostMapping(value = "/login") //@ApiOperation(value = "登陆", notes = "登陆成功返回token,测试管理员账号:admin,123456;用户账号:les123,admin") public ResultJson login(String loginName, String password, HttpServletResponse response) { final ResponseUserToken result = authService.login(loginName, password); response.setHeader(AuthContants.TOKEN_HEADER, AuthContants.TOKEN_BEARER_START + result.getToken()); return ResultJson.ok(result); } @GetMapping(value = "/logout") //@ApiOperation(value = "登出", notes = "退出登陆") // @ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")}) public ResultJson logout(HttpServletRequest request, HttpServletResponse response) { String token = request.getHeader(AuthContants.TOKEN_HEADER); if (token == null) { return ResultJson.failure(ResultCode.UNAUTHORIZED); } authService.logout(token); //清除session数据 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } request.getSession().invalidate(); return ResultJson.ok(); } @GetMapping(value = "/getInfo") //@ApiOperation(value = "根据token获取用户信息", notes = "根据token获取用户信息") // @ApiImplicitParams({@ApiImplicitParam(name = "Authorization", value = "Authorization token", required = true, dataType = "string", paramType = "header")}) public ResultJson getUser(HttpServletRequest request) { String username = (String) request.getAttribute(AuthContants.REQUEST_USER_NAME); UserDetail userDetail = authService.getByUsername(username); return ResultJson.ok(userDetail); } @PostMapping(value = "/sign") //@ApiOperation(value = "用户注册") public ResultJson sign(@RequestBody User user) { if (StringUtils.isAnyBlank(user.getName(), user.getPassword())) { return ResultJson.failure(ResultCode.BAD_REQUEST); } AuthUser authUser = new AuthUser(); authUser.setUsername(user.getName()); authUser.setPassword(user.getPassword()); Role role = new Role(); role.setId(ADMIN_TYPE); authUser.setRole(role); return ResultJson.ok(authService.register(authUser)); } @GetMapping(value = "/refresh") // @ApiOperation(value = "刷新token") public ResultJson refreshAndGetAuthenticationToken(HttpServletRequest request) { String token = request.getHeader(AuthContants.TOKEN_HEADER); ResponseUserToken response = authService.refresh(token); if (response == null) { return ResultJson.failure(ResultCode.BAD_REQUEST, "token无效"); } else { return ResultJson.ok(response); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/entity/ResponseUserToken.java ================================================ package com.xiao.custom.config.web.auth.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author JoeTao * createAt: 2018/9/17 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class ResponseUserToken { private String token; private UserDetail userDetail; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/entity/User.java ================================================ package com.xiao.custom.config.web.auth.entity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author : JoeTao * createAt: 2018/9/17 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class User { // @ApiModelProperty(value = "用户名", required = true) private String name; // @ApiModelProperty(value = "密码", required = true) private String password; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/entity/UserDetail.java ================================================ package com.xiao.custom.config.web.auth.entity; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; /** * @author : JoeTao * createAt: 2018/9/14 */ public class UserDetail implements UserDetails { private long id; private String username; private String password; private String nickname; private Role role; private Date lastPasswordResetDate; public UserDetail(AuthUser user) { this.id = user.getId(); this.role = user.getRole(); this.username = user.getUsername(); this.password = user.getPassword(); this.nickname = user.getNickname(); } public UserDetail(AuthUser user, Role role) { this.id = user.getId(); this.role = role; this.username = user.getUsername(); this.password = user.getPassword(); } public UserDetail(long id, String username, Role role, // Date lastPasswordResetDate, String password) { this.id = id; this.username = username; this.password = password; this.role = role; // this.lastPasswordResetDate = lastPasswordResetDate; } public UserDetail(String username, String password, Role role) { this.username = username; this.password = password; this.role = role; } public UserDetail(long id, String username, String password) { this.id = id; this.username = username; this.password = password; } //返回分配给用户的角色列表 @Override public Collection getAuthorities() { List authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(role.getName())); return authorities; } public long getId() { return id; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } /** * 账户是否未过期 */ @Override public boolean isAccountNonExpired() { return true; } /** * 账户是否未锁定 */ @Override public boolean isAccountNonLocked() { return true; } /** * 密码是否未过期 */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 账户是否激活 */ @Override public boolean isEnabled() { return true; } public Date getLastPasswordResetDate() { if (null != lastPasswordResetDate) { return new Date(lastPasswordResetDate.getTime()); } return null; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public void setId(long id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setLastPasswordResetDate(Date lastPasswordResetDate) { this.lastPasswordResetDate = new Date(lastPasswordResetDate.getTime()); } public String getNickname() { return nickname; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/exception/CustomException.java ================================================ package com.xiao.custom.config.web.auth.exception; import com.xiao.custom.config.web.auth.util.ResultJson; import lombok.Getter; /** * @author Joetao * Created at 2018/8/24. */ @Getter public class CustomException extends RuntimeException { private ResultJson resultJson; public CustomException(ResultJson resultJson) { this.resultJson = resultJson; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/exception/DefaultExceptionHandler.java ================================================ package com.xiao.custom.config.web.auth.exception; import com.xiao.custom.config.web.auth.util.ResultCode; import com.xiao.custom.config.web.auth.util.ResultJson; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @author Joetao * 异常处理类 * controller层异常无法捕获处理,需要自己处理 * Created at 2018/8/27. */ @RestControllerAdvice @Slf4j public class DefaultExceptionHandler { /** * 处理所有自定义异常 * * @param e * @return */ @ExceptionHandler(CustomException.class) public ResultJson handleCustomException(CustomException e) { log.error(e.getResultJson().getMsg().toString()); return e.getResultJson(); } /** * 处理参数校验异常 * * @param e * @return */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResultJson handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error(e.getBindingResult().getFieldError().getField() + e.getBindingResult().getFieldError() .getDefaultMessage()); return ResultJson.failure(ResultCode.BAD_REQUEST, e.getBindingResult().getFieldError().getDefaultMessage()); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/service/AuthUserService.java ================================================ package com.xiao.custom.config.web.auth.service; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.web.auth.entity.ResponseUserToken; import com.xiao.custom.config.web.auth.entity.UserDetail; /** * [简要描述]: 登陆验证 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 10:22 * @since JDK 1.8 */ public interface AuthUserService { /** * 注册用户 * * @param userDetail * @return */ UserDetail register(AuthUser userDetail); /** * 登陆 * * @param username * @param password * @return */ ResponseUserToken login(String username, String password); /** * 登出 * * @param token */ void logout(String token); /** * 刷新Token * * @param oldToken * @return */ ResponseUserToken refresh(String oldToken); /** * 根据Token获取用户信息 * * @param token * @return */ UserDetail getUserByToken(String token); /** * [简要描述]:
* [详细描述]:
* * @param token : * @return com.winner.config.center.web.auth.entity.UserDetail * llxiao 2019/5/9 - 17:24 **/ UserDetail getByToken(String token); /** * [简要描述]:
* [详细描述]:
* * @param username : * @return com.winner.config.center.web.auth.entity.UserDetail * llxiao 2019/5/9 - 17:24 **/ UserDetail getByUsername(String username); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/service/impl/AuthUserServiceImpl.java ================================================ package com.xiao.custom.config.web.auth.service.impl; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.web.auth.AuthContants; import com.xiao.custom.config.web.auth.entity.ResponseUserToken; import com.xiao.custom.config.web.auth.entity.UserDetail; import com.xiao.custom.config.web.auth.exception.CustomException; import com.xiao.custom.config.web.auth.service.AuthUserService; import com.xiao.custom.config.web.auth.util.JwtUtils; import com.xiao.custom.config.web.auth.util.ResultCode; import com.xiao.custom.config.web.auth.util.ResultJson; import com.xiao.custom.config.web.feign.auth.AuthFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.sql.Timestamp; /** * [简要描述]: 用户权限实现 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 10:27 * @since JDK 1.8 */ @Service public class AuthUserServiceImpl implements AuthUserService { @Autowired private AuthenticationManager authenticationManager; @Autowired @Qualifier("configUserDetailsService") private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtTokenUtil; @Autowired private AuthFeign authApi; @Override public UserDetail register(AuthUser authUser) { final String username = authUser.getUsername(); if (authApi.findByUsername(username) != null) { throw new CustomException(ResultJson.failure(ResultCode.BAD_REQUEST, "用户已存在")); } BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); final String rawPassword = authUser.getPassword(); authUser.setPassword(encoder.encode(rawPassword)); authUser.setLastResetTime(new Timestamp(System.currentTimeMillis())); authApi.insert(authUser); long roleId = authUser.getRole().getId(); Role role = authApi.findRoleById(roleId); authUser.setRole(role); authApi.insertRole(authUser.getId(), roleId); return new UserDetail(authUser); } @Override public ResponseUserToken login(String username, String password) { //用户验证 final Authentication authentication = authenticate(username, password); //存储认证信息 SecurityContextHolder.getContext().setAuthentication(authentication); //生成token final UserDetail userDetail = (UserDetail) authentication.getPrincipal(); final String token = jwtTokenUtil.generateAccessToken(userDetail); //存储token jwtTokenUtil.putToken(username, token); return new ResponseUserToken(token, userDetail); } @Override public void logout(String token) { token = token.substring(AuthContants.TOKEN_BEARER_START.length()); String userName = jwtTokenUtil.getUsernameFromToken(token); jwtTokenUtil.deleteToken(userName); } @Override public ResponseUserToken refresh(String oldToken) { String token = oldToken.substring(AuthContants.TOKEN_BEARER_START.length()); String username = jwtTokenUtil.getUsernameFromToken(token); UserDetail userDetail = (UserDetail) userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.canTokenBeRefreshed(token, userDetail.getLastPasswordResetDate())) { token = jwtTokenUtil.refreshToken(token); return new ResponseUserToken(token, userDetail); } return null; } @Override public UserDetail getUserByToken(String token) { token = token.substring(AuthContants.TOKEN_BEARER_START.length()); return jwtTokenUtil.getUserFromToken(token); } /** * [简要描述]:
* [详细描述]:
* * @param token : * @return com.winner.config.center.web.auth.entity.UserDetail * llxiao 2019/5/9 - 17:24 **/ @Override public UserDetail getByToken(String token) { token = token.substring(AuthContants.TOKEN_BEARER_START.length()); String username = jwtTokenUtil.getUsernameFromToken(token); return new UserDetail(authApi.findByUsername(username)); } /** * [简要描述]:
* [详细描述]:
* * @param username : * @return com.winner.config.center.web.auth.entity.UserDetail * llxiao 2019/5/9 - 17:24 **/ @Override public UserDetail getByUsername(String username) { return new UserDetail(authApi.findByUsername(username)); } private Authentication authenticate(String username, String password) { try { //该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码,如果正确,则存储该用户名密码到“security 的 context中” return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException | BadCredentialsException e) { throw new CustomException(ResultJson.failure(ResultCode.LOGIN_ERROR, e.getMessage())); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/service/impl/ConfigUserDetailsServiceImpl.java ================================================ package com.xiao.custom.config.web.auth.service.impl; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.web.auth.entity.UserDetail; import com.xiao.custom.config.web.feign.auth.AuthFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** * [简要描述]: 登陆身份认证 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 10:51 * @since JDK 1.8 */ @Service("configUserDetailsService") public class ConfigUserDetailsServiceImpl implements UserDetailsService { @Autowired private AuthFeign authApi; /** * Locates the user based on the username. In the actual implementation, the search * may possibly be case sensitive, or case insensitive depending on how the * implementation instance is configured. In this case, the UserDetails * object that comes back may have a username that is of a different case than what * was actually requested.. * * @param username the username identifying the user whose data is required. * @return a fully populated user record (never null) * @exception UsernameNotFoundException if the user could not be found or the user has no * GrantedAuthority */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AuthUser authUser = authApi.findByUsername(username); if (authUser == null) { throw new UsernameNotFoundException(String.format("No userDetail found with username '%s'.", username)); } return new UserDetail(authUser); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/util/JwtUtils.java ================================================ package com.xiao.custom.config.web.auth.util; import com.alibaba.fastjson.JSONObject; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.web.auth.entity.UserDetail; import io.jsonwebtoken.Claims; import io.jsonwebtoken.CompressionCodecs; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author: JoeTao * createAt: 2018/9/14 */ @Component @Slf4j public class JwtUtils { public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN"; private static final String CLAIM_KEY_USER_ID = "user_id"; private static final String CLAIM_KEY_AUTHORITIES = "scope"; private Map tokenMap = new ConcurrentHashMap<>(32); @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long accessTokenExpiration; @Value("${jwt.expiration}") private Long refreshTokenExpiration; private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; public UserDetail getUserFromToken(String token) { UserDetail userDetail; try { final Claims claims = getClaimsFromToken(token); long userId = getUserIdFromToken(token); String username = claims.getSubject(); String roleName = claims.get(CLAIM_KEY_AUTHORITIES).toString(); Role role = Role.builder().name(roleName).build(); userDetail = new UserDetail(userId, username, role, ""); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); userDetail = null; } return userDetail; } public long getUserIdFromToken(String token) { long userId; try { final Claims claims = getClaimsFromToken(token); userId = Long.parseLong(String.valueOf(claims.get(CLAIM_KEY_USER_ID))); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); userId = 0; } return userId; } public String getUsernameFromToken(String token) { String username; try { final Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); username = null; } return username; } public Date getCreatedDateFromToken(String token) { Date created; try { final Claims claims = getClaimsFromToken(token); created = claims.getIssuedAt(); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); created = null; } return created; } public String generateAccessToken(UserDetail userDetail) { Map claims = generateClaims(userDetail); claims.put(CLAIM_KEY_AUTHORITIES, authoritiesToArray(userDetail.getAuthorities()).get(0)); return generateAccessToken(userDetail.getUsername(), claims); } public Date getExpirationDateFromToken(String token) { Date expiration; try { final Claims claims = getClaimsFromToken(token); expiration = claims.getExpiration(); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); expiration = null; } return expiration; } public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) { final Date created = getCreatedDateFromToken(token); return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && (!isTokenExpired(token)); } public String refreshToken(String token) { String refreshedToken; try { final Claims claims = getClaimsFromToken(token); refreshedToken = generateAccessToken(claims.getSubject(), claims); } catch (Exception e) { log.error("Token 获取用户信息错误!", e); refreshedToken = null; } return refreshedToken; } public Boolean validateToken(String token, UserDetails userDetails) { AuthUser userDetail = (AuthUser) userDetails; final long userId = getUserIdFromToken(token); final String username = getUsernameFromToken(token); // final Date created = getCreatedDateFromToken(token); return (userId == userDetail.getId() && username.equals(userDetail.getUsername()) && !isTokenExpired(token) // && !isCreatedBeforeLastPasswordReset(created, userDetail.getLastPasswordResetDate()) ); } public String generateRefreshToken(UserDetail userDetail) { Map claims = generateClaims(userDetail); // 只授于更新 token 的权限 String roles[] = new String[] { JwtUtils.ROLE_REFRESH_TOKEN }; claims.put(CLAIM_KEY_AUTHORITIES, JSONObject.toJSON(roles)); return generateRefreshToken(userDetail.getUsername(), claims); } public void putToken(String userName, String token) { tokenMap.put(userName, token); } public void deleteToken(String userName) { tokenMap.remove(userName); } public boolean containToken(String userName, String token) { if (userName != null && tokenMap.containsKey(userName) && tokenMap.get(userName).equals(token)) { return true; } return false; } private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } private Date generateExpirationDate(long expiration) { return new Date(System.currentTimeMillis() + expiration * 1000); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { return (lastPasswordReset != null && created.before(lastPasswordReset)); } private Map generateClaims(UserDetail userDetail) { Map claims = new HashMap<>(16); claims.put(CLAIM_KEY_USER_ID, userDetail.getId()); return claims; } private String generateAccessToken(String subject, Map claims) { return generateToken(subject, claims, accessTokenExpiration); } private List authoritiesToArray(Collection authorities) { List list = new ArrayList<>(); for (GrantedAuthority ga : authorities) { list.add(ga.getAuthority()); } return list; } private String generateRefreshToken(String subject, Map claims) { return generateToken(subject, claims, refreshTokenExpiration); } private String generateToken(String subject, Map claims, long expiration) { return Jwts.builder().setClaims(claims).setSubject(subject).setId(UUID.randomUUID().toString()) .setIssuedAt(new Date()).setExpiration(generateExpirationDate(expiration)) .compressWith(CompressionCodecs.DEFLATE).signWith(SIGNATURE_ALGORITHM, secret).compact(); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/util/PageResult.java ================================================ package com.xiao.custom.config.web.auth.util; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 分页结果DO * * @author Joetao */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class PageResult { private int page; private int rows; private int total; private T data; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/util/ResultCode.java ================================================ package com.xiao.custom.config.web.auth.util; /** * @author Joetao * 状态码 * Created by jt on 2018/3/8. */ public enum ResultCode { /* 请求返回状态码和说明信息 */ SUCCESS(200, "成功"), BAD_REQUEST(400, "参数或者语法不对"), UNAUTHORIZED(401, "认证失败"), LOGIN_ERROR(401, "登陆失败,用户名或密码无效"), FORBIDDEN(403, "禁止访问"), NOT_FOUND(404, "请求的资源不存在"), OPERATE_ERROR(405, "操作失败,请求操作的资源不存在"), TIME_OUT(408, "请求超时"), SERVER_ERROR(500, "服务器内部错误"), ; private int code; private String msg; ResultCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/auth/util/ResultJson.java ================================================ package com.xiao.custom.config.web.auth.util; import lombok.Data; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.io.Serializable; /** * @author Joetao * RESTful API 返回类型 * Created at 2018/3/8. */ @Data public class ResultJson implements Serializable { private static final long serialVersionUID = 783015033603078674L; private int code; private String msg; private T data; public static ResultJson ok() { return ok(""); } public static ResultJson ok(Object o) { return new ResultJson(ResultCode.SUCCESS, o); } public static ResultJson failure(ResultCode code) { return failure(code, ""); } public static ResultJson failure(ResultCode code, Object o) { return new ResultJson(code, o); } public ResultJson(ResultCode resultCode) { setResultCode(resultCode); } public ResultJson(ResultCode resultCode, T data) { setResultCode(resultCode); this.data = data; } public void setResultCode(ResultCode resultCode) { this.code = resultCode.getCode(); this.msg = resultCode.getMsg(); } @Override public String toString() { return "{" + "\"code\":" + code + ", \"msg\":\"" + msg + '\"' + ", \"data\":\"" + data + '\"' + '}'; } public static void main(String[] args) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); System.out.println(passwordEncoder.encode("admin123")); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/commo/Constants.java ================================================ package com.xiao.custom.config.web.commo; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/12/20 14:00 * @since JDK 1.8 */ public interface Constants { String CONFIG_SERVICE = "config-center-service"; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/config/AppControllerAdvice.java ================================================ package com.xiao.custom.config.web.config; import com.alibaba.fastjson.JSONObject; import com.netflix.hystrix.exception.HystrixRuntimeException; import com.xiao.custom.config.web.exception.ExceptionEnum; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.springcloud.demo.common.gloab.interceptor.advice.DefaultControllerAdvice; import com.xiao.springcloud.demo.common.gloab.response.ErrorResponseData; import com.xiao.springcloud.demo.common.gloab.response.ResponseData; import com.xiao.springcloud.demo.common.gloab.response.SuccessResponseData; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * 全局包裹返回值 * * @author zhdong * Date 2018/9/2 */ @Slf4j @ControllerAdvice public class AppControllerAdvice extends DefaultControllerAdvice implements ResponseBodyAdvice { /** * 拦截服务调用异常 */ @ExceptionHandler(HystrixRuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody @Override public ResponseData hystrixRuntimeException(HystrixRuntimeException e) { log.info("系统异常:", e); Throwable cause = e.getCause(); //return new ErrorResponseData(e.getCode(), e.getErrorMessage()); if (cause instanceof CommonException) { return serviceException((CommonException) cause); } log.info("服务远程调用异常:", e); return ErrorResponseData .error(ExceptionEnum.REQUEST_TIMEOUT.getCode(), ExceptionEnum.REQUEST_TIMEOUT.getMessage()); } @Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; } /** * 封装返回结果 * * @param returnValue * @param methodParameter * @param mediaType * @param aClass * @param serverHttpRequest * @param serverHttpResponse * @return */ @Override public Object beforeBodyWrite(Object returnValue, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //如果已经是ResponseData,直接返回 if (returnValue instanceof ResponseData) { return returnValue; } else if (returnValue instanceof String) { try { return JSONObject.toJSON(SuccessResponseData.success(returnValue)); } catch (Exception e) { log.error("返回结果转换json异常", e); return ErrorResponseData.error("返回结果转换json异常"); } } return SuccessResponseData.success(returnValue); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/ClientInfoController.java ================================================ package com.xiao.custom.config.web.controller; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; import com.xiao.custom.config.web.feign.client.ClientInfoFeign; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: 客户端服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 15:55 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/api/config/clientInfo") @Slf4j public class ClientInfoController { @Autowired private ClientInfoFeign clientInfoFeign; /** * 分页查询客户端信息 * * @param query * @return */ @RequestMapping("/page") public PageInfo pageQuery(@RequestBody ClientHostInfoQuery query) { return clientInfoFeign.pageQuery(query); } /** * [简要描述]:删除数据
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/3/27 - 15:46 **/ @RequestMapping("/del") public boolean deleteById(Long id) { if (null != id) { return clientInfoFeign.deleteById(id); } else { log.error("删除失败,客户端注解ID为空!"); } return false; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/ConfigGroupController.java ================================================ package com.xiao.custom.config.web.controller; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.web.feign.config.ConfigGroupFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/12/21 14:26 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/api/config/configGroup") public class ConfigGroupController { @Autowired private ConfigGroupFeign configGroupFeign; @RequestMapping(value = "/page") public PageInfo page(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { return configGroupFeign.page(configItemGroupQuery); } @RequestMapping(value = "/delete/{ids}") public Integer delete(@PathVariable("ids") String ids) { return configGroupFeign.delete(ids); } @RequestMapping(value = "/save") public Boolean save(@RequestBody ConfigItemGroupDto configItemGroupDto) { return configGroupFeign.save(configItemGroupDto); } @RequestMapping(value = "/update") public Boolean update(@RequestBody ConfigItemGroupDto configItemGroupDto) { return configGroupFeign.update(configItemGroupDto); } @RequestMapping(value = "/isRefGroup") public PageInfo pageRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery) { return configGroupFeign.pageRefConfigItemWithGroup(configItemQuery); } @RequestMapping(value = "/batchDelete/{groupId}/{itemIds}") public Boolean batchDelete(@PathVariable("itemIds") String itemIds, @PathVariable("groupId") Long groupId) { return configGroupFeign.batchDelete(groupId, itemIds); } @RequestMapping(value = "/notRefGroup") public PageInfo pageNotRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery) { return configGroupFeign.pageNotRefConfigItemWithGroup(configItemQuery); } @RequestMapping(value = "/batchSave/{groupId}/{itemIds}") public Boolean batchSave(@PathVariable("itemIds") String itemIds, @PathVariable("groupId") Long groupId) { return configGroupFeign.batchSave(groupId, itemIds); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/ConfigItemController.java ================================================ package com.xiao.custom.config.web.controller; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.web.feign.config.ConfigItemFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]:配置项管理 * [详细描述]: * * @author jun.liu * @version 1.0, 2018/12/20 11:43 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/api/config/configItem") public class ConfigItemController { @Autowired private ConfigItemFeign configItemService; @RequestMapping(value = "/page") public PageInfo pageConfigItem(@RequestBody ConfigItemQuery configItemQuery) { return configItemService.pageConfigItem(configItemQuery); } @RequestMapping(value = "/batchDelete/{ids}") public Integer enableOrDisable(@PathVariable("ids") String ids) { return configItemService.enableOrDisable(ids); } @RequestMapping(value = "/save") public Boolean save(@RequestBody ConfigItemDto configItemDto) { return configItemService.save(configItemDto); } @RequestMapping(value = "/update") public Boolean update(@RequestBody ConfigItemDto configItemDto) { return configItemService.update(configItemDto); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/IndexController.java ================================================ package com.xiao.custom.config.web.controller; /** * [简要描述]: * [详细描述]: * * @author zhdong * @version 1.0, 2018/11/21 * @since JDK 1.8 */ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 首页转发 * @author zhdong * Date 2018/8/23 */ @Controller public class IndexController { @Value("${omni.channel.admin.page.index:index/index}") private String indexPage; @RequestMapping(value = "/") public String index(HttpServletRequest req, HttpServletResponse resp) throws IOException { return indexPage; } @RequestMapping(value = "/index") public String index2(HttpServletRequest req, HttpServletResponse resp) throws IOException { return indexPage; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/RegionController.java ================================================ package com.xiao.custom.config.web.controller; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.query.RegionQuery; import com.xiao.custom.config.web.feign.region.RegionFeign; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * [简要描述]: 区域Controller * [详细描述]: * * @author mjye * @version 1.0, 2018/12/20 15:31 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/region") @Slf4j public class RegionController { @Autowired private RegionFeign regionFeign; /** * [简要描述]:分页查询区域信息 * [详细描述]:
* * @param regionQuery : 分页查询对象 * @return com.github.pagehelper.PageInfo * mjye 2018/12/20 - 15:52 **/ @RequestMapping(value = "/queryRegion") public PageInfo pageConfigItem(@RequestBody RegionQuery regionQuery) { return regionFeign.queryRegion(regionQuery); } /** * [简要描述]:删除区域 * [详细描述]:
* * @return java.lang.Boolean * mjye 2018/12/20 - 17:28 **/ @RequestMapping(value = "/delectRegion/{ids}") public Boolean delectRegion(@PathVariable("ids") String ids) { if (null == ids) { log.info("删除区域信息失败,id不能为空"); throw new RuntimeException("参数不能为空"); } return regionFeign.delete(ids); } // /** // * [简要描述]:更新区域信息 // * [详细描述]:
// * @param regionDto : // * @return java.lang.Boolean // * mjye 2018/12/20 - 17:29 // **/ // @PostMapping(value="/updateRegion") // public Boolean updateRegion(@RequestBody RegionDto regionDto){ // if(null == regionDto.getId() || StringUtils.isBlank(regionDto.getRegionDesc()) || StringUtils.isBlank(regionDto.getRegionName()) // || null == regionDto.getCreateTime()) { // log.info("更改区域信息失败,参数不能为空"); // throw new RuntimeException("参数不能为空"); // } // return regionFeign.updateRegion(regionDto); // } /** * [简要描述]:新增or修改区域信息 * [详细描述]:
* * @param regionDto : * @return java.lang.Boolean * mjye 2018/12/20 - 17:31 **/ @PostMapping(value = "/addRegion") public Boolean addRegion(@RequestBody RegionDto regionDto) { if (null == regionDto.getId()) { if (StringUtils.isBlank(regionDto.getRegionDesc()) || StringUtils.isBlank(regionDto.getRegionName())) { log.info("新增区域信息失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } return regionFeign.addRegion(regionDto); } else { if (StringUtils.isBlank(regionDto.getRegionDesc()) || StringUtils.isBlank(regionDto.getRegionName())) { log.info("更改区域信息失败,参数不能为空"); throw new RuntimeException("参数不能为空"); } return regionFeign.updateRegion(regionDto); } } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/ServerHostConfigController.java ================================================ package com.xiao.custom.config.web.controller; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; import com.xiao.custom.config.web.feign.server.ServerHostConfigFeign; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]:服务器管理 * [详细描述]: * * @author jyqiao * @version 1.0, 2018/12/20 * @since JDK 1.8 */ @RestController @RequestMapping(value = "/api/serverHostConfig") @Slf4j public class ServerHostConfigController { @Autowired private ServerHostConfigFeign serverHostConfigFeign; //查询服务器配置信息 @RequestMapping(value = "/page") public PageInfo pageConfigItem(@RequestBody ServerHostConfigQuery serverHostConfigQuery) { return serverHostConfigFeign.pageServerHostConfig(serverHostConfigQuery); } //更改服务器配置信息 @RequestMapping(value = "/updateServerHostConfig") public Boolean updateServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto) { return serverHostConfigFeign.updateServerHostConfig(serverHostConfigDto); } //添加服务器配置信息 @RequestMapping(value = "/addServerHostConfig") public Boolean addServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto) { return serverHostConfigFeign.addServerHostConfig(serverHostConfigDto); } //删除服务器配置信息 @RequestMapping(value = "/delectServerHostConfig/{id}") public int delectServerHostConfig(@PathVariable("id") String id) { long ids = Long.parseLong(id); return serverHostConfigFeign.delectServerHostConfig(ids); } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/app/AppManagerController.java ================================================ package com.xiao.custom.config.web.controller.app; import cn.hutool.core.collection.CollectionUtil; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.query.AppQuery; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.web.controller.app.vo.ApplicationVo; import com.xiao.custom.config.web.controller.app.vo.RegionVo; import com.xiao.custom.config.web.feign.app.ApplicationFeign; import com.xiao.custom.config.web.feign.region.RegionFeign; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/20 10:19 * @since JDK 1.8 */ @RestController @RequestMapping("/appManager") @Slf4j public class AppManagerController { @Autowired private ApplicationFeign applicationFeign; @Autowired private RegionFeign regionFeign; @PostMapping("/pageQuery") public PageInfo pageQuery(@RequestBody AppQuery appQuery) { return applicationFeign.pageApplicationConfig(appQuery); } @RequestMapping("/queryAllRegion") public List queryAllRegion() { List regionVos; List regionDtos = regionFeign.selectRgion(); if (CollectionUtil.isNotEmpty(regionDtos)) { regionVos = new ArrayList<>(regionDtos.size()); for (RegionDto regionDto : regionDtos) { regionVos.add(convertVo(regionDto)); } } else { regionVos = null; } return regionVos; } @RequestMapping("/save") public boolean saveApplication(@RequestBody ApplicationVo applicationVo) { return applicationFeign.save(convertDto(applicationVo)); } @RequestMapping("/update") public Boolean update(@RequestBody ApplicationVo applicationVo) { if (applicationVo.getId() == null) { return false; } else { return applicationFeign.update(convertDto(applicationVo)); } } //删除应用,以及包括他所属下管理的所有配置 @RequestMapping("/delete") public boolean deleteApplication(Long id) { if (null != id) { return applicationFeign.delete(id); } return false; } /** * [简要描述]:发布配置
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/1/30 - 10:50 **/ @RequestMapping("/refresh") public boolean refresh(Long id) { if (null != id) { return applicationFeign.refresh(id); } return false; } @RequestMapping("/queryItemGroup") public PageInfo pageRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { if (null == configItemGroupQuery.getAppId()) { log.error("应用ID不能为空!"); new PageInfo<>(); } return this.applicationFeign.pageRefGroupWithApp(configItemGroupQuery); } /** * [简要描述]:应用ID和组ID删除对应关系
* [详细描述]:
* * @param appId : 应用ID * @param itemGroupId : 组ID * @return boolean * llxiao 2018/12/25 - 17:38 **/ @RequestMapping("delItemGroup") public boolean delItemGroup(Long appId, Long itemGroupId) { if (null != appId && null != itemGroupId) { return applicationFeign.batchUnBind(String.valueOf(itemGroupId), appId); } return false; } /** * [简要描述]:获取未绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 15:31 **/ @RequestMapping(value = "/notRefApp") public PageInfo pageNotRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery) { if (configItemGroupQuery.getAppId() == null) { return new PageInfo<>(); } return this.applicationFeign.pageNotRefGroupWithApp(configItemGroupQuery); } /** * [简要描述]:绑定应用
* [详细描述]:
* * @param groupIds : * @param appId : * @return java.lang.Boolean * llxiao 2019/1/2 - 14:48 **/ @RequestMapping(value = "/batchSaveRef") public Boolean batchSave(@RequestParam("groupIds") String groupIds, @RequestParam("appId") Long appId) { if (StringUtils.isEmpty(groupIds) || null == appId) { return false; } return this.applicationFeign.batchSave(groupIds, appId); } /** * [简要描述]:应用查询关联的私有配置属性
* [详细描述]:
* * @param applicationConfigQuery : * @return com.github.pagehelper.PageInfo * llxiao 2019/1/7 - 15:24 **/ @RequestMapping("/queryPrivateConfig") public PageInfo pageQuery(@RequestBody ApplicationConfigQuery applicationConfigQuery) { if (null == applicationConfigQuery.getApplicationId()) { return new PageInfo<>(); } return this.applicationFeign.queryPrivateConfig(applicationConfigQuery); } //新增私有配置项 @RequestMapping("/addPrivateItem") public Boolean savePrivateConfig(@RequestBody ApplicationConfigDto applicationConfigDto) { if (null == applicationConfigDto.getApplicationId()) { return false; } return this.applicationFeign.savePrivateConfig(applicationConfigDto); } //修改私有配置项 @RequestMapping("/updatePrivateItem") public Boolean updatePrivateConfig(@RequestBody ApplicationConfigDto applicationConfigDto) { if (null == applicationConfigDto.getId()) { return false; } return this.applicationFeign.updatePrivateConfig(applicationConfigDto); } //删除私有配置项 @RequestMapping("/delPrivateItem") public Boolean delPrivateConfig(Long id) { if (null == id) { return false; } return this.applicationFeign.delPrivateConfig(id); } private ApplicationDto convertDto(ApplicationVo applicationVo) { ApplicationDto applicationConfigDto = new ApplicationDto(); applicationConfigDto.setId(applicationVo.getId()); applicationConfigDto.setApplication(applicationVo.getApplication()); applicationConfigDto.setApplicationName(applicationVo.getApplicationName()); applicationConfigDto.setLabel(applicationVo.getLabel()); applicationConfigDto.setProfile(applicationVo.getProfile()); applicationConfigDto.setCreateTime(new Date()); applicationConfigDto.setUpdateTime(new Date()); applicationConfigDto.setRegionId(applicationVo.getRegion()); applicationConfigDto.setGroupIds(applicationVo.getConfGroupIds()); return applicationConfigDto; } private RegionVo convertVo(RegionDto regionDto) { RegionVo regionVo = new RegionVo(); regionVo.setLabel(regionDto.getRegionName()); regionVo.setValue(regionDto.getId()); return regionVo; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/app/vo/ApplicationVo.java ================================================ package com.xiao.custom.config.web.controller.app.vo; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/21 20:53 * @since JDK 1.8 */ @Data public class ApplicationVo { private Long id; private String application; private String applicationName; private Long region; private String label; private String profile; private String confGroupIds; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/controller/app/vo/RegionVo.java ================================================ package com.xiao.custom.config.web.controller.app.vo; import lombok.Data; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/21 17:11 * @since JDK 1.8 */ @Data public class RegionVo { private String label; private Long value; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/dto/ServerHostConfigDto.java ================================================ package com.xiao.custom.config.web.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * Created by Mybatis Generator on 2018/11/23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ServerHostConfigDto { // private Long id; //IP地址 private String serverHost; //服务描述 private String serverDesc; //关联区域 private Long regionId; //关联区域名称 private String regionName; // private Date createTime; // private Date updateTime; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/exception/ExceptionEnum.java ================================================ package com.xiao.custom.config.web.exception; import com.xiao.springcloud.demo.common.exception.AbstractServiceException; /** * @author zhdong * Date 2018/9/2 */ public enum ExceptionEnum implements AbstractServiceException { REQUEST_TIMEOUT(7500001,"请求超时"), PARAM_REQUIRED(7500002,"参数必填"), EXCEL_WRITER_IO(7500003,"写入excel出错"), EXCEL_READ_IO(750004,"读取excel出错"), EXCEL_EXPORT_OVER_MAX(7500005,"导出超过最大数量"), NO_LOGIN(7500006,"未登录"), EXCEL_DOWN_ERROR(7500007,"下载excel报错"); ExceptionEnum(Integer code, String message) { this.code = code; this.message = message; } private Integer code; private String message; @Override public Integer getCode() { return code; } @Override public String getMessage() { return message; } } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/app/ApplicationFeign.java ================================================ package com.xiao.custom.config.web.feign.app; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ApplicationConfigDto; import com.xiao.custom.config.pojo.dto.ApplicationDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.query.AppQuery; import com.xiao.custom.config.pojo.query.ApplicationConfigQuery; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/20 10:20 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping("/application") public interface ApplicationFeign { /** * [简要描述]:分页获取
* [详细描述]:
* * @param appQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 10:20 **/ @RequestMapping(value = "page") PageInfo pageApplicationConfig(@RequestBody AppQuery appQuery); /** * [简要描述]:新增应用
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * jun.liu 2018/11/28 - 10:29 **/ @RequestMapping(value = "/save") Boolean save(@RequestBody ApplicationDto applicationConfigDto); /** * [简要描述]:删除应用
* [详细描述]:
* * @param id : 主键ID * @return java.lang.Boolean * llxiao 2018/12/24 - 10:24 **/ @RequestMapping(value = "/delete/{id}") Boolean delete(@PathVariable("id") Long id); /** * [简要描述]:应用更新配置
* [详细描述]:
* * @param id : 应用主键ID * @return boolean * llxiao 2019/1/30 - 10:52 **/ @RequestMapping("/refresh") boolean refresh(@RequestParam("id") Long id); /** * [简要描述]:更新配置
* [详细描述]:
* * @param applicationConfigDto : 更新 * @return java.lang.Boolean * llxiao 2018/12/24 - 19:22 **/ @RequestMapping(value = "/update") Boolean update(@RequestBody ApplicationDto applicationConfigDto); /** * [简要描述]:获取已绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 14:59 **/ @RequestMapping(value = "/isRefApp") PageInfo pageRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery); /** * [简要描述]:删除绑定
* [详细描述]:
* * @param groupIds : * @param appId : * @return java.lang.Boolean * jun.liu 2018/11/28 - 15:39 **/ @RequestMapping(value = "/batchDeleteRef") Boolean batchUnBind(@RequestParam("groupIds") String groupIds, @RequestParam("appId") Long appId); /** * [简要描述]:获取未绑定该应用的配置组
* [详细描述]:
* * @param configItemGroupQuery : * @return com.github.pagehelper.PageInfo * jun.liu 2018/11/28 - 15:31 **/ @RequestMapping(value = "/notRefApp") PageInfo pageNotRefGroupWithApp(@RequestBody ConfigItemGroupQuery configItemGroupQuery); /** * [简要描述]:绑定应用
* [详细描述]:
* * @param groupIds : * @param appId : * @return java.lang.Boolean * llxiao 2019/1/2 - 14:48 **/ @RequestMapping(value = "/batchSaveRef") Boolean batchSave(@RequestParam("groupIds") String groupIds, @RequestParam("appId") Long appId); /** * [简要描述]:查收私有属性
* [详细描述]:
* * @param applicationConfigQuery * @return java.lang.Boolean * llxiao 2019/1/7 - 17:01 **/ @RequestMapping("/queryPrivateConfig") PageInfo queryPrivateConfig(ApplicationConfigQuery applicationConfigQuery); /** * [简要描述]:新增一条私有配置
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * llxiao 2019/1/7 - 17:06 **/ @RequestMapping("/savePrivateConfig") Boolean savePrivateConfig(ApplicationConfigDto applicationConfigDto); /** * [简要描述]:更新一条私有配置信息
* [详细描述]:
* * @param applicationConfigDto : * @return java.lang.Boolean * llxiao 2019/1/7 - 17:06 **/ @RequestMapping("/updatePrivateConfig") Boolean updatePrivateConfig(ApplicationConfigDto applicationConfigDto); /** * [简要描述]:删除某项私有属性
* [详细描述]:
* * @param id : 私有属性主键ID * @return java.lang.Boolean * llxiao 2019/1/8 - 9:20 **/ @RequestMapping("/delPrivateConfig") Boolean delPrivateConfig(@RequestParam("id") Long id); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/auth/AuthFeign.java ================================================ package com.xiao.custom.config.web.feign.auth; import com.xiao.custom.config.pojo.entity.AuthUser; import com.xiao.custom.config.pojo.entity.Role; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * [简要描述]: 鉴权服务 * [详细描述]: * * @author llxiao * @version 1.0, 2019/5/8 17:56 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping(value = "/auth") public interface AuthFeign { /** * 根据用户名查找用户 * * @param username * @return */ @RequestMapping("/findByUsername") AuthUser findByUsername(@RequestParam("username") String username); /** * 创建新用户 * * @param userDetail */ @RequestMapping("/insert") void insert(@RequestBody AuthUser userDetail); /** * 创建用户角色 * * @param userId * @param roleId * @return */ @RequestMapping("/insertRole") int insertRole(@RequestParam("userId") long userId, @RequestParam("roleId") long roleId); /** * 根据角色id查找角色 * * @param roleId * @return */ @RequestMapping("/findRoleById") Role findRoleById(long roleId); /** * 根据用户id查找该用户角色 * * @param userId * @return */ @RequestMapping("/findRoleByUserId") Role findRoleByUserId(long userId); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/client/ClientInfoFeign.java ================================================ package com.xiao.custom.config.web.feign.client; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ClientHostInfoDto; import com.xiao.custom.config.pojo.query.ClientHostInfoQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/3/27 15:52 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping("/clientInfo") public interface ClientInfoFeign { /** * 分页查询客户端信息 * * @param query * @return */ @RequestMapping("/page") PageInfo pageQuery(@RequestBody ClientHostInfoQuery query); /** * [简要描述]:删除数据
* [详细描述]:
* * @param id : * @return boolean * llxiao 2019/3/27 - 15:46 **/ @RequestMapping("/del") boolean deleteById(@RequestParam("id") Long id); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/config/ConfigGroupFeign.java ================================================ package com.xiao.custom.config.web.feign.config; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.dto.ConfigItemGroupDto; import com.xiao.custom.config.pojo.query.ConfigItemGroupQuery; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/12/21 14:26 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping(value = "/configItemGroup") public interface ConfigGroupFeign { @RequestMapping(value = "/page") PageInfo page(@RequestBody ConfigItemGroupQuery configItemGroupQuery); @RequestMapping(value = "/delete/{ids}") Integer delete(@PathVariable("ids") String ids); @RequestMapping(value = "/save") Boolean save(@RequestBody ConfigItemGroupDto configItemGroupDto); @RequestMapping(value = "/update") Boolean update(@RequestBody ConfigItemGroupDto configItemGroupDto); @RequestMapping(value = "/isRefGroup") PageInfo pageRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery); @RequestMapping(value = "/batchDelete/{groupId}/{itemIds}") Boolean batchDelete(@PathVariable("groupId") Long groupId, @PathVariable("itemIds") String itemIds); @RequestMapping(value = "/notRefGroup") PageInfo pageNotRefConfigItemWithGroup(@RequestBody ConfigItemQuery configItemQuery); @RequestMapping(value = "/batchSave/{groupId}/{itemIds}") Boolean batchSave(@PathVariable("groupId") Long groupId, @PathVariable("itemIds") String itemIds); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/config/ConfigItemFeign.java ================================================ package com.xiao.custom.config.web.feign.config; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ConfigItemDto; import com.xiao.custom.config.pojo.query.ConfigItemQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * [简要描述]: * [详细描述]: * * @author jun.liu * @version 1.0, 2018/12/20 13:58 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping(value = "/configItem") public interface ConfigItemFeign { @RequestMapping(value = "/page") PageInfo pageConfigItem(@RequestBody ConfigItemQuery configItemQuery); @RequestMapping(value = "/batchDelete/{ids}") Integer enableOrDisable(@PathVariable("ids") String ids); @RequestMapping(value = "/save") Boolean save(@RequestBody ConfigItemDto configItemDto); @RequestMapping(value = "/update") Boolean update(@RequestBody ConfigItemDto configItemDto); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/region/RegionFeign.java ================================================ package com.xiao.custom.config.web.feign.region; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.RegionDto; import com.xiao.custom.config.pojo.query.RegionQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author mjye * @version 1.0, 2018/12/20 15:39 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping(value = "/region") public interface RegionFeign { //分页查询区域 @RequestMapping(value = "/queryRegion") PageInfo queryRegion(@RequestBody RegionQuery regionQuery); //删除区域 @PostMapping(value="/delectRegion/{ids}") Boolean delete(@PathVariable("ids") String ids); //更新区域信息 @PostMapping(value="/updateRegion") Boolean updateRegion(@RequestBody RegionDto regionDto); //新增区域信息 @PostMapping(value="/addRegion") Boolean addRegion(@RequestBody RegionDto regionDto); //查询所有的 Region @RequestMapping(value = "/selectRgion") List selectRgion(); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/java/com/xiao/custom/config/web/feign/server/ServerHostConfigFeign.java ================================================ package com.xiao.custom.config.web.feign.server; import com.github.pagehelper.PageInfo; import com.xiao.custom.config.pojo.dto.ServerHostConfigDto; import com.xiao.custom.config.pojo.query.ServerHostConfigQuery; import com.xiao.custom.config.web.commo.Constants; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; /** * [简要描述]: * [详细描述]: * * @author jyqiao * @version 1.0, 2018/12/20 13:58 * @since JDK 1.8 */ @FeignClient(value = Constants.CONFIG_SERVICE) @RequestMapping(value = "/serverHostConfig") public interface ServerHostConfigFeign { //查询服务器配置信息 @RequestMapping(value = "/queryServerHost") PageInfo pageServerHostConfig(@RequestBody ServerHostConfigQuery serverHostConfigQuery); //添加服务器配置信息 @RequestMapping(value = "/addServerHostConfig") Boolean addServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto); //更改服务器配置信息 @RequestMapping(value = "/updateServerHostConfig") Boolean updateServerHostConfig(@RequestBody ServerHostConfigDto serverHostConfigDto); //删除服务器配置信息 @RequestMapping(value = "/delectServerHostConfig/{id}") int delectServerHostConfig(@PathVariable("id") Long id); } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/application.properties ================================================ ##########设置编码############ spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8 spring.http.multipart.maxFileSize=10MB spring.http.multipart.maxRequestSize=10MB spring.mvc.view.prefix=/pages/ spring.mvc.view.suffix=.html message.service.web.page.index=index/index #Fegin 超时配置 ribbon.ReadTimeout=60000 ribbon.ConnectTimeout=60000 ribbon.MaxAutoRetries=0 ribbon.MaxAutoRetriesNextServer=1 ######鉴权 #使用session,对接web前端 spring.session.store-type=none security.basic.enabled=false #登录Url config.center.loginUrl=/login ##匿名访问Url集 config.center.anonymous.urls=/health,/pages/support/support.html,/login**,/images/**,/plugin/**,/pages/**/*.css,/mock/**,/favicon.ico,/user/auth/login,/error/**,/login #加密密钥 jwt.secret=mySecret #token有效期一天 jwt.expiration=86400 #hystrix.command.default.execution.timeout.enabled=false #feign.hystrix.enabled=false ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/application.yml ================================================ eureka: instance: instance-id: ${spring.cloud.client.ipAddress}:${server.port} prefer-ip-address: true ##设置心跳的周期间隔(默认90s)[如果10s没响应默认服务宕机] #lease-expiration-duration-in-seconds: 10 #设置心跳时间间隔(默认30s)[心跳时间2s] #lease-renewal-interval-in-seconds: 2 client: serviceUrl: defaultZone: http://localhost:9000/eureka/ #开启健康检查(需要spring-boot-starter-actuator依赖) #healthcheck: #enable: true spring: application: name: config-web cloud: #开启重试机制,它默认是关闭的 loadbalancer: retry: enable: true # 开启熔断功能 feign: #hystrix: #enabled: true compression: #请求GZIP压缩 request: enabled: true #支持压缩类型 mime-types: text/xml,application/xml,application/json #压缩数据大小的下限 min-request-size: 2048 #响应gzip压缩 response: enabled: true # hystrix超时设置,断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试 hystrix: command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/bootstrap.yml ================================================ server: port: 9002 spring: cloud: config: uri: http://localhost:9000/config/ profile: @env@ label: master name: config-center-web application: name: config-center-web #logging.level.root: debug logging.level.com.netflix.eureka.registry: ERROR ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/mock/index.js ================================================ //模拟菜单数据 Mock.mock(/api\/user\/menuList/, new MockResult([ { "id": "#1", "menuId": "#1", "name": "首页", "url": "#home/home.html", "iconCls": "iconfont icon-home", "componentId": "admin-home" }, {"id": "2", "menuId": "2", "name": "应用管理", "url": "#app/app.html", "iconCls": "iconfont icon-gaiicon-"}, { "id": "3", "menuId": "3", "name": "配置管理", "iconCls": "iconfont icon-peizhi", children: [ { "id": "31", "menuId": "3-31", "name": "配置项管理", "url": "#config/configitem.html", "iconCls": "el-icon-setting" }, { "id": "32", "menuId": "3-32", "name": "配置组管理", "url": "#configgroup/configgroup.html", "iconCls": "el-icon-setting" } ] }, {"id": "4", "menuId": "4", "name": "区域管理", "url": "#region/region.html", "iconCls": "iconfont icon-quyu"}, {"id": "5", "menuId": "5", "name": "服务管理", "url": "#server/serverlist.html", "iconCls": "iconfont el-icon-document"}, // { // "id": "5", "menuId": "5", "name": "演示", "iconCls": "iconfont icon-yanshi", // children: [ // { // "id": "51", // "menuId": "5-51", // "name": "查询演示", // "url": "#template/template.html", // "iconCls": "el-icon-search" // }, // ] // }, ])); //应用数据 (function () { var pager = new App.Pager() delete pager.list; pager["list|1-100"] = [ { "id|1-1000000": 0, "name": "消息服务", "desc": "这是应用描述。。。。", "person|1": [Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname(), Mock.Random.cname()], "creator": "toga", "createTime": new Date(), "envList": [ { "profile": "dev", "url": "http://localhost:7775" }, { "profile": "test", "url": "http://localhost:7775" } ] } ]; Mock.mock(/api\/app\/query/, new MockResult(pager)) })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/mock/mock.js ================================================ // 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果 Mock.setup({ timeout: 500 }) MockResult = function(data){ this.code = 200 this.data = data this.message = '请求成功' this.success = true } MockPageResult = function(data){ this.code = 200 this.data = new App.Pager(data) this.message = '请求成功' this.success = true } Mock.mock(/api\/mpc\/shopdec\/catalog\/pageSubject.?/,{"code":200,"data":{"endRow":4,"firstPage":1,"hasNextPage":false,"hasPreviousPage":false,"isFirstPage":true,"isLastPage":true,"lastPage":1,"list":[{"createTime":"2018-10-30 11:13:26","creator":"admin","endTime":"2018-11-30 00:00:00","id":19,"pageNum":1,"pageSize":10,"shopId":"21","shopName":"北京王府井店","sort":1,"startTime":"2018-10-30 00:00:00","status":0,"subjectName":"首页","updateTime":"2018-11-11 10:31:39","updator":"admin"},{"createTime":"2018-10-30 15:31:21","creator":"admin","endTime":"2018-11-29 00:00:00","id":20,"pageNum":1,"pageSize":10,"shopId":"21","shopName":"北京王府井店","sort":2,"startTime":"2018-10-30 00:00:00","status":0,"subjectName":"纯粹自然","updateTime":"2018-11-07 19:39:05","updator":"admin"},{"createTime":"2018-11-07 19:37:38","creator":"admin","endTime":"2029-12-31 00:00:00","id":21,"pageNum":1,"pageSize":10,"shopId":"21","shopName":"北京王府井店","sort":3,"startTime":"2018-11-07 00:00:00","status":0,"subjectName":"纯梦空间","updateTime":"2018-11-07 19:39:00","updator":"admin"},{"createTime":"2018-11-08 09:18:03","creator":"admin","endTime":"2018-11-30 00:00:00","id":22,"pageNum":1,"pageSize":10,"shopId":"21","shopName":"北京王府井店","sort":4,"startTime":"2018-11-08 00:00:00","status":0,"subjectName":"智慧药房","updateTime":"2018-11-15 10:13:19","updator":"admin"}],"navigateFirstPage":1,"navigateLastPage":1,"navigatePages":8,"navigatepageNums":[1],"nextPage":0,"pageNum":1,"pageSize":10,"pages":1,"prePage":0,"size":4,"startRow":1,"total":4},"message":"请求成功","success":true}) Mock.mock(/api\/mpc\/shopdec\/sowingMap\/query.?/,{"code":200,"data":{"endRow":4,"firstPage":1,"hasNextPage":false,"hasPreviousPage":false,"isFirstPage":true,"isLastPage":true,"lastPage":1,"list":[{"createTime":"2018-11-02 16:08:40","creator":"admin","endTime":"2018-12-29 00:00:00","id":13,"pageNum":1,"pageSize":10,"picUrl":"http://omni-test.oss-cn-shenzhen.aliyuncs.com/omni/others/picture/d3de5103-a0e2-4d76-a64b-7f50303c91f2.jpg","shopCode":"10000","shopId":21,"shopName":"北京王府井店","showType":0,"sort":1,"startTime":"2018-11-02 00:00:00","status":0,"subjectId":19,"subjectName":"首页","updateTime":"2018-11-02 16:08:40","updator":"admin"},{"createTime":"2018-11-02 16:07:33","creator":"admin","endTime":"2018-12-31 00:00:00","id":12,"pageNum":1,"pageSize":10,"picUrl":"http://omni-test.oss-cn-shenzhen.aliyuncs.com/omni/others/picture/80a999c4-fd14-41d1-8453-25999564668e.jpg","shopCode":"10000","shopId":21,"shopName":"北京王府井店","showType":0,"sort":1,"startTime":"2018-11-02 00:00:00","status":0,"subjectId":19,"subjectName":"首页","updateTime":"2018-11-03 18:05:16","updator":"admin"},{"createTime":"2018-11-02 16:09:04","creator":"admin","endTime":"2018-12-28 00:00:00","id":14,"pageNum":1,"pageSize":10,"picUrl":"http://omni-test.oss-cn-shenzhen.aliyuncs.com/omni/others/picture/741d1f71-aff7-49ae-bbc0-4b078c5096c5.jpg","shopCode":"10000","shopId":21,"shopName":"北京王府井店","showType":0,"sort":3,"startTime":"2018-11-02 00:00:00","status":0,"subjectId":19,"subjectName":"首页","updateTime":"2018-11-02 16:09:04","updator":"admin"},{"createTime":"2018-11-02 16:09:36","creator":"admin","endTime":"2018-12-29 00:00:00","id":15,"pageNum":1,"pageSize":10,"picUrl":"http://omni-test.oss-cn-shenzhen.aliyuncs.com/omni/others/picture/98bd454c-ed7a-4eee-9da5-98a303b174bf.jpg","shopCode":"10000","shopId":21,"shopName":"北京王府井店","showType":0,"sort":4,"startTime":"2018-11-02 00:00:00","status":0,"subjectId":20,"subjectName":"纯粹自然","updateTime":"2018-11-15 11:36:49","updator":"admin"}],"navigateFirstPage":1,"navigateLastPage":1,"navigatePages":8,"navigatepageNums":[1],"nextPage":0,"pageNum":1,"pageSize":10,"pages":1,"prePage":0,"size":4,"startRow":1,"total":4},"message":"请求成功","success":true}); Mock.mock(contextPath+'api/demo/tempate',function(){ var data = { list:[{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] } return new MockPageResult(data); }) Mock.mock(contextPath+'api/demo/submit',function() { return new MockResult(); }) ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/add.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/add.js ================================================ (() => { App.moule({ data: function () { return { appData: { id:"", application: '', applicationName: '', label: 'master', profile: '', region: '', regionName:'' }, update: false, tabStyle: {}, // 区域 regions: [], //校验规则 rules: { application: [ {required: true, message: '请输入应用名称', trigger: 'blur'}, {min: 3, max: 128, message: '长度在 3 到 128 个字符', trigger: 'blur'} ], profile: [ {required: true, message: '请选择一个环境', trigger: 'change'} ], region: [ {required: true, message: '请选择一个区域', trigger: 'change'} ] } } }, activated() { // App.success("接收传过来的数据为:" + JSON.stringify(editData)); this.$nextTick(() => { this.$set(this.tabStyle, 'height', (App.MainVueApp.pageHeight - 150) + "px") this.$set(this.tabStyle, 'overflow', "auto") }) }, methods: { submit(formName) { console.log(formName); this.$refs[formName].validate((valid) => { if (valid) { App.post('/appManager/save').setData(this.appData).callSuccess((res) => { // console.log(res.success); if (res.success) { App.success('保存成功'); App.closeCurrentTagNav() } else { App.error("添加应用错误,对应的应用、环境已经存在"); } }) } else { return false; } }); }, //关联配置项组 relation() { // TODO } }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { // console.log('模板编译挂载完成事件'); // 加载区域信息 App.request("/appManager/queryAllRegion").callSuccess((res) => { // 无区域可用,提示添加区域 if (res.data.length == 0) { //调整添加区域 App.error("暂无区域可选择,请先添加区域!!"); //App.openModule("addRegion","添加区域","template/detail.html"); } else { this.regions = res.data; } }) }, /* 组件未被激活 类似小程序ondestroy */ deactivated :function() { // console.log('未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/app.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/app.js ================================================ (() => { App.moule({ data: function () { return { prop: "我是属性", tableHeight: 0, deleteModal: false, selectionData: [], searchForm: { application: '', profile: '', applicationName: '' }, pager: new App.Pager(), } }, methods: { //选中事件 selectChange(v) { this.selectionData = v; }, search() { //后台接口加载数据 this.loadData(); }, //选择删除事件 deleteBatch() { //获取选择记录 if (!this.selectionData.length) { App.error('请选择记录'); return; } // this.deleteModal = true; this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var _this = this; App.request("appManager/delete", {"id": _this.selectionData[0].id}) .callSuccess((res) => { if (res.data) { App.success('删除成功!'); _this.loadData(); } else { App.error("删除失败!"); } }); }); }, confirmDelete() { this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var _this = this; App.request("appManager/delete", {"id": _this.selectionData[0].id}) .callSuccess((res) => { if (res.data) { App.success('删除成功!'); _this.loadData(); } else { App.error("删除失败!"); } }); }); }, edit(row) { //传递数据 App.putData("editData", row); App.putData("editType", "update"); App.openModule("edit", "信息编辑", "app/detail.html"); }, //关联数据 relation(row) { //传递数据 App.putData("relationData", row); App.openModule("config", "配置管理", "app/configgroup.html"); }, del(row) { this.$set(this, 'selectionData', [row]); // this.deleteModal = true; this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var _this = this; App.request("appManager/delete", {"id": _this.selectionData[0].id}) .callSuccess((res) => { if (res.data) { App.success('删除成功!'); _this.loadData(); } else { App.error("删除失败!"); } }); }); }, //发布配置,动态更新 refresh(row) { this.$confirm('确定发布配置?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var _this = this; App.request("appManager/refresh", {"id": row.id}) .callSuccess((res) => { if (res.data) { App.success('发布成功!'); } else { App.error("发布失败,确认应用在线,有问题请联系管理员!"); } }); }); }, create() { //传递数据-新增 App.openModule("add", "新增应用配置", "app/add.html"); }, loadData: function () { //后台接口加载数据 App.request('appManager/pageQuery').post().setData(this.searchForm).callSuccess((res) => { //兼容manggo 的pager和github的pager this.pager = new App.Pager(res.data); }) }, /*重置查询*/ resetForm: function () { var that = this; that.searchForm = {}; that.tableCurrentPage = 1; that.loadData(); }, }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { // console.log('模板编译挂载完成事件'); //后台接口加载数据 this.loadData(); }, /* 组件更新完成事件 */ updated: function () { // console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { // console.log('组件被激活'); this.loadData(); }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { // console.log('组件注销'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/configgroup.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/configgroup.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], configModel: false, activeName: 'itemGroup', //配置组中的配置项列表数据 configPager: new App.Pager(), /*数据表格里的数据*/ tableData: [{ id: '', groupName: '', groupDesc: '', creatTime: '', updateTime: '', }], pager: new App.Pager(), appId: '', /*查询API对应的form表单*/ searchForm: { appId: '', groupName: '', groupDesc: '' }, /***********私有配置相关************/ privateModel: false, privateSelData: [], // 私有配置列表 privatePager: new App.Page(), //查询表单 priSearchForm: { applicationId: '', itemKey: '', itemDesc: '' }, // 新增/修改参数 privTabDate: { id: '', applicationId: '', itemKey: '', itemValue: '', itemDesc: '' }, rules: { itemKey: [{required: true, message: '请输入配置项键', trigger: "blur"}], itemValue: [{required: true, message: '请输入配置项值', trigger: "blur"}] }, isUpdate: false } }, methods: { /* tab事件*/ handleClick(tab, event) { console.log(tab, event); }, /*查询API*/ findApi() { var that = this; this.searchForm.appId = this.appId; // console.log(that.searchForm); //后台接口加载数据 App.request('appManager/queryItemGroup').post().setData(that.searchForm).callSuccess((res) => { //兼容manggo 的pager和github的pager that.pager = new App.Pager(res.data); }) }, //查询该项目组下所有配置 configDetail(data) { debugger //配置项列表 var that = this; //查询当前组下的所有配置 App.request('/api/config/configGroup/isRefGroup').post().setData({ "groupId": data.id, 'pageSize': 100 }).callSuccess((res) => { that.configPager = new App.Pager(res.data); that.configModel = true; }) }, selectChagne(v) { this.selectionData = v; }, del(datas) { var that = this; if ( !that.selectionData.length && !datas ) { App.error('请选择记录'); return; } this.$confirm('此操作将永久删除该配置组, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // var selData = datas ? [datas] : that.selectionData; // var newData = ''; // selData.forEach(function (t) { // newData = newData + t.id + ","; // }); // var groupIds = newData.substr(0, newData.length - 1); var groupId = datas.id; App.request("appManager/delItemGroup", { itemGroupId: groupId, appId: this.appId }).post().callSuccess(function (resp) { if (resp) { App.success('删除成功'); } else { App.error('删除失败'); } that.findApi(); }); }); }, open(title, data) { var that = this; that.title = title; if (data != null) { that.createForm = data; that.groupId = data.id; } else { that.createForm = {}; } that.dialogFormVisible = true; }, addConfGroup() { App.putData("appId", this.appId); App.openModule("app/refconfiggroup", "添加配置组", "app/refconfiggroup.html") }, //修改配置跳转 updateConfig(data) { App.putData("configItem", data); App.openModule("#config/configitem.html", "配置项管理", "config/configitem.html") }, /*****************************私有配置列表**********************************/ //添加私有配置 addPrivateConf() { this.isUpdate = false; this.privateModel = true; }, //查找私有配置 findPrivateConf() { var url = "appManager/queryPrivateConfig"; this.priSearchForm.applicationId = this.appId; var _this = this; App.post(url).setData(this.priSearchForm).callSuccess((res) => { console.log(res) _this.privatePager = new App.Pager(res.data); }) }, // 修改私有配置 editPrivate(row) { var url = "appManager/updatePrivateItem"; this.privateModel = true; this.privTabDate.id = row.id; this.privTabDate.applicationId = row.applicationId; this.privTabDate.itemDesc = row.itemDesc; this.privTabDate.itemKey = row.itemKey; this.privTabDate.itemValue = row.itemValue; this.isUpdate = true; }, //删除某项私有属性 delPrivate(row) { if (row) { var url = "appManager/delPrivateItem?id=" + row.id; var that = this; this.$confirm('此操作将永久删除该配置项, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { App.request(url).callSuccess(function (resp) { if (resp.data) { App.success('删除成功!'); that.findPrivateConf(); } else { App.error("删除失败!") } }); }); } }, selPrivateChange(v) { this.privateSelData = v; }, //提交私有配置 submitPrivate() { var that = this; this.privTabDate.applicationId = this.appId; this.$refs['privTabDate'].validate((valid) => { if (valid) { debugger var url = "appManager/addPrivateItem"; if (that.isUpdate) { url = "appManager/updatePrivateItem"; } App.post(url).setData(that.privTabDate).callSuccess((res) => { // console.log(res) if (res.data) { if (that.isUpdate) { App.success('更新成功') } else { App.success('保存成功') } that.cleanPriTab(); that.privateModel = false; that.findPrivateConf(); } else { App.error('操作失败') } that.isUpdate = false; }) } else { return false; } }); }, cleanPriTab() { this.privTabDate.id = ''; this.privTabDate.applicationId = ''; this.privTabDate.itemValue = ''; this.privTabDate.itemKey = ''; this.privTabDate.itemDesc = ''; } }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { // console.log('模板编译挂载完成事件'); }, /* 组件更新完成事件 */ updated: function () { // console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { // console.log('组件被激活'); var appData = App.getData("relationData"); this.activeName = 'itemGroup'; this.isUpdate = false; if (appData) { this.appId = appData.id; App.removeData("relationData"); this.findApi(); this.findPrivateConf(); } else { App.closeCurrentTagNav(); } }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { // console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/detail.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/detail.js ================================================ (() => { App.moule({ data: function () { return { appData: { id: "", application: '', applicationName: '', label: 'master', profile: '', region: '', regionName: '' }, update: false, tabStyle: {}, // 区域 regions: [], //校验规则 rules: { application: [ {required: true, message: '请输入应用名称', trigger: 'blur'}, {min: 3, max: 128, message: '长度在 3 到 128 个字符', trigger: 'blur'} ], profile: [ {required: true, message: '请选择一个环境', trigger: 'change'} ], region: [ {required: true, message: '请选择一个区域', trigger: 'change'} ] } } }, activated() { //接收数据 var editData = App.getData("editData"); //初始化值 this.appData.application = editData.application; this.appData.id = editData.id; this.appData.applicationName = editData.applicationName; this.appData.label = editData.label; this.appData.profile = editData.profile; this.appData.region = editData.regionId; console.log(this.appData) // App.success("接收传过来的数据为:" + JSON.stringify(editData)); this.$nextTick(() => { this.$set(this.tabStyle, 'height', (App.MainVueApp.pageHeight - 150) + "px") this.$set(this.tabStyle, 'overflow', "auto") }) }, methods: { submit(formName) { console.log(formName); debugger console.log(this.appData); this.$refs[formName].validate((valid) => { if (valid) { App.post('/appManager/update').setData(this.appData).callSuccess((res) => { // console.log(res.success); if (res.success) { App.success('保存成功'); App.openModule("detail", "应用管理", "app/app.html"); } else { App.error("添加应用错误,对应的应用、环境已经存在"); } }) } else { App.success('必填参数错误!'); return false; } }); }, //关联配置项组 relation() { // TODO } }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { // console.log('模板编译挂载完成事件'); // 加载区域信息 App.request("/appManager/queryAllRegion").callSuccess((res) => { // 无区域可用,提示添加区域 if (res.data.length == 0) { //调整添加区域 App.error("暂无区域可选择,请先添加区域!!"); //App.openModule("addRegion","添加区域","template/detail.html"); } else { debugger this.regions = res.data; } }) }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { // console.log('未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/refconfiggroup.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/app/refconfiggroup.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], configModel: false, //配置组中的配置项列表数据 configPager: new App.Pager(), /*数据表格里的数据*/ tableData: [{ id: '', groupName: '', groupDesc: '', creatTime: '', updateTime: '', }], pager: new App.Pager(), appId: '', /*查询API对应的form表单*/ searchForm: { appId: '', groupName: '', groupDesc: '' }, } }, methods: { /*查询API*/ findApi() { debugger var that = this; this.searchForm.appId = this.appId; //后台接口加载为关联的数据 App.request('appManager/notRefApp').post().setData(that.searchForm).callSuccess((res) => { //兼容manggo 的pager和github的pager that.pager = new App.Pager(res.data); }) }, //查询该项目组下所有配置 configDetail(data) { //配置项列表 var that = this; //查询当前组下的所有配置 App.request('/api/config/configGroup/isRefGroup').post().setData({ "groupId": data.id, 'pageSize': 100 }).callSuccess((res) => { that.configPager = new App.Pager(res.data); that.configModel = true; }) }, selectChagne(v) { this.selectionData = v; }, ref(datas) { debugger var that = this; if (!that.selectionData.length && !datas) { App.error('请选择记录'); return; } var selData = datas ? [datas] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); var postData = {}; postData.appId = this.appId; postData.groupIds = newData.substr(0, newData.length - 1); console.log(postData); App.request('appManager/batchSaveRef', postData).callSuccess((res) => { if (res.data) { App.success('添加成功!'); App.closeCurrentTagNav(); App.putData("appId", this.appId); } }); }, open(title, data) { var that = this; that.title = title; if (data != null) { that.createForm = data; that.groupId = data.id; } else { that.createForm = {}; } that.dialogFormVisible = true; }, //批量关联 batchRef() { console.log(""); this.ref(); }, //修改配置跳转 updateConfig(data) { App.putData("configItem", data); App.openModule("#config/configitem.html", "配置项管理", "config/configitem.html") }, }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { console.log('模板编译挂载完成事件'); }, /* 组件更新完成事件 */ updated: function () { console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { debugger console.log('组件被激活'); this.appId = App.getData("appId"); App.removeData("appId"); this.findApi(); }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/client/client.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/client/client.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, deleteModal: false, /*数据表格里的数据*/ tableData: [{ id: '', applicationClientId: '', application: '', hostIp: '', hostPort: '', status: '', createTime: '', updateTime: '', }], pager: new App.Pager(), /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, /*查询API对应的form表单*/ form: { hostIp: '', application: '', status: '' } } }, methods: { statusFormatter(v) { if (v.status == 0) { return "在线"; } else if (v.status == 1) { return "离线"; } }, /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; App.request("/api/config/clientInfo/page").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; // console.log(resp.data.list); that.tableTotal = resp.data.total; }); }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, selectChagne(v) { this.selectionData = v; }, deleteBatch() { //获取选择记录 if (!this.selectionData.length) { App.error('请选择记录'); return; } this.deleteModal = true; }, confirmDelete() { var _this = this; App.post('/api/server/delect').setData(_this.selectionData) .setLoadArea(_this.$refs.deleteBtn).callSuccess(() => { App.success('删除成功!'); _this.deleteModal = false }); }, del(row) { var that = this; that.$confirm('此操作将永久删除该服务器信息, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var id = row.id; App.request("/api/config/clientInfo/del", {"id": id}) .callSuccess((res) => { if (res.data) { App.success('删除成功'); } else { App.error('删除失败'); } that.findApi(); }); }).catch(() => { that.$message({ type: 'info', message: '已取消删除' }); }); }, }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { }, /* 组件更新完成事件 */ updated: function () { console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { console.log('组件被激活'); this.findApi(); }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/config/configitem.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/config/configitem.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], /*数据表格里的数据*/ tableData: [{ id: '', itemKey: '', itemValue: '', itemDesc: '', createTime: '', updateTime: '', status: '', itemType: '', }], dialogFormVisible: false, pager: new App.Pager(), /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, /*查询API对应的form表单*/ form: { itemKey: '', itemDesc: '', status: '', itemType: '', pageNum: '', pageSize: '', }, createForm: { id: '', itemKey: '', itemValue: '', itemDesc: '', itemType: '', status: 0, }, title: '', rules: { itemKey: [{required: true, message: '请输入配置项键', trigger: "blur"}], itemValue: [{required: true, message: '请输入配置项值', trigger: "blur"}] }, itemId: '', } }, methods: { /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; App.request("/api/config/configItem/page").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }); }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, fomatterStatus(v) { if (v.status == 0) { return "启用"; } else if (v.status == 1) { return "禁用"; } }, fomatterItemType(v) { if (v.itemType == 0) { return "通用"; } else if (v.itemType == 1) { return "开发环境"; } else if (v.itemType == 2) { return "测试环境"; } else if (v.itemType == 3) { return "生产环境"; } else if (v.itemType == 4) { return "其他"; } }, selectChagne(v) { this.selectionData = v; }, del(datas) { var that = this; if (!that.selectionData.length && !datas) { App.error('请选择记录'); return; } this.$confirm('此操作将永久删除该配置项, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var selData = datas ? [datas] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); App.request("/api/config/configItem/batchDelete/" + newData.substr(0, newData.length - 1)).post().setData(newData).callSuccess(function (resp) { if (resp.data == 0) { App.success('删除成功!'); } else { //App.error('部分删除成功,其中' + resp.data + "条配置项已经关联配置组在使用中不能进行删除,必须删除组!"); App.error("不能删除,请先从配置组中删除该项的使用关系!") } that.findApi(); }); }); }, open(title, data) { var that = this; that.title = title; if (data != null) { that.createForm = data; that.itemId = data.id; } else { that.createForm = {}; } that.dialogFormVisible = true; }, submit(formName) { var that = this; this.$refs[formName].validate((valid) => { if (valid) { var url = ''; if (that.itemId == '' || that.itemId == null) { url = 'api/config/configItem/save'; that.createForm.status = 0; if (!that.createForm.itemType) { that.createForm.itemType = 0; } } else { url = 'api/config/configItem/update'; that.createForm.id = that.itemId; } App.post(url).setData(that.createForm).callSuccess((res) => { console.log(res) if (!that.createForm.id) { if (res) { App.success('保存成功') that.dialogFormVisible = false; that.findApi(); } else { App.success('保存失败') } } else { if (res) { App.success('修改成功') that.dialogFormVisible = false; that.findApi(); } else { App.success('修改失败') } } }) } else { return false; } }); }, }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { console.log('模板编译挂载完成事件'); }, /* 组件更新完成事件 */ updated: function () { console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { console.log('组件被激活'); this.findApi(); var itemData = App.getData("configItem"); if (itemData) { App.removeData("configItem"); this.open('修改配置项', itemData); } }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/configgroup.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/configgroup.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], /*数据表格里的数据*/ tableData: [{ id: '', groupName: '', groupDesc: '', creatTime: '', updateTime: '', }], pager: new App.Pager(), /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, dialogFormVisible: false, /*查询API对应的form表单*/ form: { groupName: '', createTime: '', updateTime: '', pageNum: '', pageSize: '', }, createForm: { id: '', groupName: '', groupDesc: '', }, title: '', rules: { groupName: [{required: true, message: '请输入配置组名称', trigger: "blur"}], groupDesc: [{required: true, message: '请输入配置组描述', trigger: "blur"}] }, groupId: '', } }, methods: { /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; App.request("/api/config/configGroup/page").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }); }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, selectChagne(v) { this.selectionData = v; }, del(datas) { var that = this; if ( !that.selectionData.length && !datas ) { App.error('请选择记录'); return; } this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var selData = datas ? [datas] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); App.request("/api/config/configGroup/delete/" + newData.substr(0, newData.length - 1)).post().callSuccess(function (resp) { if (resp.data == 0) { App.success('全部删除成功!'); } else { App.error('部分删除成功,其中' + resp.data + '条已关联应用,不能进行删除,必须先解除应用关联!'); } that.findApi(); }); }); }, create(formName) { var that = this; that.$refs[formName].validate((valid) => { if (valid) { var url = ''; if (that.groupId == '' || that.groupId == null) { url = 'api/config/configGroup/save'; } else { url = 'api/config/configGroup/update'; that.createForm.id = that.groupId; } App.post(url).setData(that.createForm).callSuccess((res) => { console.log(res); if (!that.createForm.id) { if (res) { App.success('保存成功'); that.dialogFormVisible = false; that.findApi(); } else { App.error('保存失败'); } } else { if (res) { App.success('修改成功'); that.dialogFormVisible = false; that.findApi(); } else { App.error('修改失败'); } } }) } else { return false; } }); } , open(title, data) { var that = this; that.title = title; if (data != null) { that.createForm = data; that.groupId = data.id; } else { that.createForm = {}; } that.dialogFormVisible = true; }, refItem(group) { App.putData("editType", group.id); App.openModule("detail", "已关联配置项", "configgroup/detail.html"); } }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted: function () { console.log('模板编译挂载完成事件'); }, /* 组件更新完成事件 */ updated: function () { console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated: function () { console.log('组件被激活'); this.findApi(); }, /* 组件未被激活 类似小程序ondestroy */ deactivated: function () { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/detail.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/detail.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], tableData: [{ id: '', itemKey: '', itemValue: '', itemDesc: '', createTime: '', updateTime: '', status: '', itemType: '', }], form: { groupId: '', itemKey: '', pageNum: '', pageSize: '', }, notform: { groupId: '', itemKey: '', pageNum: '', pageSize: '', }, /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, groupId: '', } }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, activated() { var that = this; //接收数据 var editData = App.getData('editType'); that.groupId = editData; this.findApi(); }, methods: { /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; that.form.groupId = that.groupId; App.request("/api/config/configGroup/isRefGroup").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }); }, selectChagne(v) { this.selectionData = v; }, fomatterStatus(v) { if (v.status == 0) { return "启用"; } else if (v.status == 1) { return "禁用"; } }, fomatterItemType(v) { if (v.itemType == 0) { return "通用"; } else if (v.itemType == 1) { return "开发环境"; } else if (v.itemType == 2) { return "测试环境"; } else if (v.itemType == 3) { return "生产环境"; } else if (v.itemType == 4) { return "其他"; } }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, del(datas) { var that = this; if ( !that.selectionData.length && !datas ) { App.error('请选择记录'); return; } this.$confirm('此操作将删除关联关系, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var selData = datas ? [datas] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); App.request("/api/config/configGroup/batchDelete/" + that.groupId + "/" + newData.substr(0, newData.length - 1)).post().callSuccess(function (resp) { if (resp) { App.success('删除成功'); } else { App.error('删除失败'); } that.findApi(); }); }); }, refItem() { App.putData("editType", this.groupId); App.openModule("detail", "未关联配置项", "configgroup/refdetail.html"); } }, }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/refdetail.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/configgroup/refdetail.js ================================================ (() => { App.moule({ data: function () { return { tableHeight: 0, selectionData: [], tableData: [{ id: '', itemKey: '', itemValue: '', itemDesc: '', createTime: '', updateTime: '', status: '', itemType: '', }], form: { groupId: '', itemKey: '', pageNum: '', pageSize: '', }, notform: { groupId: '', itemKey: '', pageNum: '', pageSize: '', }, /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, groupId: '', } }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, activated() { var that = this; //接收数据 var editData = App.getData('editType'); that.groupId = editData; this.findApi(); }, methods: { /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; that.form.groupId = that.groupId; App.request("/api/config/configGroup/notRefGroup").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }); }, selectChagne(v) { this.selectionData = v; }, fomatterStatus(v) { if (v.status == 0) { return "启用"; } else if (v.status == 1) { return "禁用"; } }, fomatterItemType(v) { if (v.itemType == 0) { return "通用"; } else if (v.itemType == 1) { return "开发环境"; } else if (v.itemType == 2) { return "测试环境"; } else if (v.itemType == 3) { return "生产环境"; } else if (v.itemType == 4) { return "其他"; } }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, refItem(datas) { var that = this; if ( !that.selectionData.length && !datas ) { App.error('请选择记录'); return; } var selData = datas ? [datas] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); App.request("/api/config/configGroup/batchSave/" + that.groupId + "/" + newData.substr(0, newData.length - 1)).post().callSuccess(function (resp) { if (resp) { App.success('添加成功'); } else { App.error('添加失败'); } that.findApi(); }); } }, }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/error/401.html ================================================

Oh~~您没有浏览这个页面的权限
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/error/404.html ================================================
Oh~~您访问的页面不存在~
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/error/500.html ================================================
Oh~~系统错误,请联系管理员~
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/home/home.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/home/home.js ================================================ (()=>{ App.moule({ data :function() { return { prop: "我是属性", dialogVisible: false } }, methods:{ onClick(){ App.openModule("api_ut",'API单元测试','#api_ut/api_ut.html'); }, handleClose(done) { this.$confirm('确认关闭?') .then(_ => { done(); }) .catch(_ => {}); }, onSubmit(){ alert("aa") this.dialogVisible=false; } }, /* 组件创建完成事件 */ created :function(){ console.log('组件创建完成事件'); }, /* 模板编译挂载完成事件 类似小程序onload */ mounted :function(){ console.log('模板编译挂载完成事件'); }, /* 组件更新完成事件 */ updated:function(){ console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated :function(){ console.log('组件被激活'); }, /* 组件未被激活 类似小程序ondestroy */ deactivated :function() { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/index/compents.js ================================================ App.register({ template:'#tags-nav', data :function() { return { tagBodyLeft: 0, rightOffset: 45, outerPadding: 4, light:2 } }, name: 'TagsNav', props: ['list','value'], methods: { handleMouseScroll :function(e) { let type = e.type; let delta = 0; if (type === 'DOMMouseScroll' || type === 'mousewheel') { delta = (e.wheelDelta) ? e.wheelDelta : -(e.detail || 0) * 40 } this.handleScroll(delta) }, handleScroll :function(offset) { const outerWidth = this.$refs.scrollOuter.offsetWidth const bodyWidth = this.$refs.scrollBody.offsetWidth if (offset > 0) { this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset) } else { if (outerWidth < bodyWidth) { if (this.tagBodyLeft < -(bodyWidth - outerWidth)) { this.tagBodyLeft = this.tagBodyLeft } else { this.tagBodyLeft = Math.max(this.tagBodyLeft + offset, outerWidth - bodyWidth) } } else { this.tagBodyLeft = 0 } } }, handleClose :function(e,menuId) { console.log(menuId) this.$emit('on-close', 'single',menuId); }, handleClick :function(item,e) { if ( e.target.tagName == 'I'){ this.handleClose(e,item.menuId); }else{ this.value = item.menuId this.$emit('input', item) } }, handleTagsOption :function(type){ this.$emit('on-close', type); }, moveToHome:function(){ this.tagBodyLeft = 0; }, moveToView :function(tag) { let outerWidth = $(this.$refs.scrollOuter).outerWidth(); let bodyWidth = $(this.$refs.scrollBody).outerWidth(); var tagPos = $(tag).position(); var tagleft = $(tag).position().left + $(tag).outerWidth() + 10; var visableLeft = tagleft + this.tagBodyLeft; var marginLeft = outerWidth-tagleft; if ( tagleft < outerWidth ){ this.tagBodyLeft = 0; return; } //判断是否在可视范围内 if ( visableLeft < outerWidth && visableLeft > 0 ){ //不移动 }else{ if ( marginLeft < 0 ){ this.tagBodyLeft = marginLeft; }else{ this.tagBodyLeft = 0; } } } } }); Vue.component('ue',{ template:'
', data () { return { editor: null } }, props: { eid:{ type: String, }, content: { type: String, default:"" }, config: { type: Object, default: { initialFrameWidth:1000, initialFrameHeight:500 } } }, mounted() { const _this = this; this.editor = UE.getEditor('UEDITOR_'+this.eid, this.config); // 初始化UE this.editor.addListener("ready", function () { _this.editor.setContent(_this.content); // 确保UE加载完成后,放入内容。 }); }, methods: { getUEContent() { // 获取内容方法 return this.editor.getContent() } }, destroyed() { this.editor.destroy(); } }) ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/index/index.css ================================================ html,body{ height:100%;margin: 0px; } #app { width: 100%; height: 100%; font:12px Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif } .main{ height: 100vh; min-width: 800px; min-height: 600px; overflow: hidden; } .main section{ height: 100%; } .main aside{ overflow: visible; height: 100%; } .el-menu{ height: 100%; border-right: none; } .el-main{ padding:10px; } /*********** tags-nav begin ***********/ .tag-nav-wrapper { padding: 0; height: 40px; overflow: hidden; background: #b8babe14; } .tags-nav { position: relative; /*border-top: 1px solid #F0F0F0;*/ /*border-bottom: 1px solid #F0F0F0;*/ -webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; width: 100%; height: 100%; } .tags-nav .ivu-tag{ height: 40px; line-height: 40px; margin: 0px; padding: 0 8px; border-radius: 0px; } .tags-nav .close-con { position: absolute; right: 0; top: 0; height: 100%; width: 50px; background: #fff; text-align: center; z-index: 10; } .tags-nav .btn-con.left-btn { left: 0px; } .tags-nav .btn-con { position: absolute; top: 0px; height: 100%; background: #fff; padding-top: 3px; z-index: 10; } .tags-nav .btn-con button { padding: 6px 4px; line-height: 14px; text-align: center; } .tags-nav .btn-con.right-btn { right: 32px; border-right: 1px solid #F0F0F0; } .tags-nav .btn-con { position: absolute; top: 0px; height: 100%; background: #fff; padding-top: 3px; z-index: 10; } .tags-nav .scroll-outer { position: absolute; left: 0px; right: 0px; top: 0; bottom: 0; border-bottom: solid 1px #e6e6e6; } .tags-nav .scroll-outer .scroll-body { height: calc(100% - 1px); display: inline-block; padding: 1px 4px 0; position: absolute; overflow: visible; white-space: nowrap; -webkit-transition: left .3s ease; transition: left .3s ease; } .main .content-wrapper { height: calc(100% - 80px); overflow: auto; } body .ivu-select-selection>div{ display: flex; width: 100%; overflow: hidden; } .ivu-transfer-list-header-count{ visibility: hidden; } /*********** tags-nav end ***********/ /* main begin*/ .left-sider{ background: #141821; } .left-sider .ivu-layout-sider-trigger{ background: #141821; } .main .logo-con { height: 64px; padding: 10px; } .main .left-sider .ivu-layout-sider-children { overflow-y: scroll; margin-right: -20px; height:92%; } /* main end*/ .loading-spin-container{ display: inline-block; position: fixed; border: 1px solid #eee; z-index: 1000; } .side-menu-wrapper { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .side-menu-wrapper .menu-collapsed { padding-top: 10px; } .side-menu-wrapper .menu-collapsed .ivu-dropdown { width: 100%; } .side-menu-wrapper .menu-collapsed .ivu-dropdown .ivu-dropdown-rel a { width: 100%; } .side-menu-wrapper .menu-collapsed .ivu-tooltip { width: 100%; } .side-menu-wrapper .menu-collapsed .ivu-tooltip .ivu-tooltip-rel { width: 100%; } .side-menu-wrapper .menu-collapsed .ivu-tooltip .ivu-tooltip-popper .ivu-tooltip-content .ivu-tooltip-arrow { border-right-color: #fff; } .side-menu-wrapper .menu-collapsed .ivu-tooltip .ivu-tooltip-popper .ivu-tooltip-content .ivu-tooltip-inner { background: #fff; color: #495060; } .side-menu-wrapper a.drop-menu-a { display: inline-block; padding: 10px 15px; width: 100%; text-align: center; color: #495060; } .menu-title { padding-left:6px; } .sub-drop-menu-a{ color: #515a6e; } .layout-con{ height: 100%; width: 100%; } .menu-item span{ display: inline-block; overflow: hidden; width: 110px; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; transition: width .2s ease .2s; } .menu-item i{ transform: translateX(0px); transition: font-size .2s ease, transform .2s ease; vertical-align: middle; font-size: 16px; } .collapsed-menu span{ width: 0px; transition: width .2s ease; } .collapsed-menu i{ transform: translateX(5px); transition: font-size .2s ease .2s, transform .2s ease .2s; vertical-align: middle; font-size: 22px; } /*消失时间持续3s .fade-enter-active{ transition: all 0s; }*/ /*显示时间持续1.5s .fade-leave-active{ transition: all 0.5s; }*/ /* 主题 */ .ivu-menu-dark { background: #141821; } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item{ display: flex; } .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-item,.ivu-menu-dark.ivu-menu-horizontal .ivu-menu-submenu { color: rgba(255,255,255,.7) } .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-item-active,.ivu-menu-dark.ivu-menu-horizontal .ivu-menu-item:hover,.ivu-menu-dark.ivu-menu-horizontal .ivu-menu-submenu-active,.ivu-menu-dark.ivu-menu-horizontal .ivu-menu-submenu:hover { color: #fff } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-group-title { color: rgba(255,255,255,.36) } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item,.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title { color: rgba(255,255,255,.7) } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu),.ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu):hover,.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu),.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu):hover { background: #141821 } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item:hover,.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title:hover { color: #fff; background: #141821 } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-item-active:not(.ivu-menu-submenu),.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu-title-active:not(.ivu-menu-submenu) { /*color: #2d8cf0*/ border-right: none; color: #fff; background: #2d8cf0!important; } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu .ivu-menu-item:hover { color: #fff; background: 0 0!important } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu .ivu-menu-item-active,.ivu-menu-dark.ivu-menu-vertical .ivu-menu-submenu .ivu-menu-item-active:hover { border-right: none; color: #fff; background: #2d8cf0!important } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-child-item-active>.ivu-menu-submenu-title { color: #fff } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-opened { background: #001529 } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-opened .ivu-menu-submenu-title { background: #001529 } .ivu-menu-dark.ivu-menu-vertical .ivu-menu-opened .ivu-menu-submenu-has-parent-submenu .ivu-menu-submenu-title { background: 0 0 } .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-item-active, .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-item:hover, .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-submenu-active, .ivu-menu-dark.ivu-menu-horizontal .ivu-menu-submenu:hover { color: #2d8cf0!important; } .menu-title{ font-size:12px!important; } .ivu-form-item { margin-bottom: 15px; tical-align: top; zoom: 1; } .common-upload-list{ display: inline-block; width: 60px; height: 60px; text-align: center; line-height: 60px; border: 1px solid transparent; border-radius: 4px; overflow: hidden; background: #fff; position: relative; box-shadow: 0 1px 1px rgba(0,0,0,.2); margin-right: 4px; } .common-upload-list img{ width: 100%; height: 100%; } .common-upload-list-cover{ display: none; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: rgba(0,0,0,.6); } .common-upload-list:hover .common-upload-list-cover{ display: block; } .common-upload-list-cover i{ color: #fff; font-size: 20px; cursor: pointer; margin: 0 2px; } /* 自定义布局组件样式 */ .channel-card{ background: #ededee; padding: 10px 0px 0px 10px; } .channel-card .ivu-card-head{ border:none; padding:10px 10px; padding-bottom:0px; } .channel-card .ivu-divider-horizontal{ margin:15px 0px; } .channel-divider{ height: 1px; } .channel-card .ivu-form-item{ margin-bottom: 15px; } .query-form .ivu-form-item{ margin-bottom: 10px!important; } .channel-card .ivu-card-body{ padding:10px; padding-bottom:5px; } .channel-card .ivu-table td, .ivu-table th{ height:40px; } .channel-card .zhankai-icon{ transform:rotate(90deg); -ms-transform:rotate(90deg); /* IE 9 */ -moz-transform:rotate(90deg); /* Firefox */ -webkit-transform:rotate(90deg); /* Safari 和 Chrome */ -o-transform:rotate(90deg); /* Opera */ } .channel-card .ivu-page{ padding-top:10px; text-align: right; } .ivu-modal-body form .ivu-form-item{ margin-bottom:20px; } .ivu-tooltip-inner{ word-break: break-all; } .nav-tag-body{ display: flex; } .nav-tag{ height: 30px; padding:0px 10px; min-width: 150px; max-width:250px; } .nav-tag-body .nav-tag:first-child{ border-left: 10px solid #444450!important; } .nav-tag-body .nav-tag:nth-child(even){ border-left: 10px solid #141821; border-right: 10px solid #141821; border-bottom: 30px solid #444450; } .nav-tag-body .nav-tag:nth-child(odd){ /*border-left: 10px solid rgb(245, 247, 249);*/ /*border-right: 10px solid #444450;*/ /*border-bottom: 30px solid rgb(245, 247, 249);*/ border-left: 10px solid #141821; border-right: 10px solid #141821; border-bottom: 30px solid #444450; } .nav-tag-start{ width: 16px; height: 30px; } .nav-tag-start>div:first-child{ height: 15px; border-left: 8px solid #2c2b36; border-right: 8px solid #545c64; border-top: 15px solid #2c2b36; } .nav-tag-start>div:last-child{ height: 15px; border-left: 8px solid #2c2b36; border-right: 8px solid #545c64; border-bottom: 15px solid #545c64; } .nav-tag-light-s{ width: 16px; height: 30px; } .nav-tag-light-s>div:first-child{ height: 15px; border-left: 8px solid #2c2b36; border-right: 8px solid #ededee; border-top: 15px solid #2c2b36; } .nav-tag-light-s>div:last-child{ height: 15px; border-left: 8px solid #2c2b36; border-right: 8px solid #ededee; border-bottom: 15px solid #ededee; } .nav-tag-light-e{ width: 15px; height: 30px; } .nav-tag-light-e>div:first-child{ height: 15px; border-left: 8px solid #ededee; border-right: 8px solid #2c2b36; border-top: 15px solid #2c2b36; } .nav-tag-light-e>div:last-child{ height: 15px; border-left: 8px solid #ededee; border-right: 8px solid #2c2b36; border-bottom: 15px solid #ededee; } .nav-tag-light-r{ width: 15px; height: 30px; } .nav-tag-light-r>div:first-child{ height: 15px; border-left: 8px solid #ededee; border-right: 8px solid rgb(68, 68, 80); border-top: 15px solid #2c2b36; } .nav-tag-light-r>div:last-child{ height: 15px; border-left: 8px solid #ededee; border-right: 8px solid rgb(68, 68, 80); border-bottom: 15px solid #ededee; } .nav-tag-light-l{ width: 16px; height: 30px; } .nav-tag-light-l>div:first-child{ height: 15px; border-left: 8px solid rgb(68, 68, 80); border-right: 8px solid #ededee; border-top: 15px solid #2c2b36; } .nav-tag-light-l>div:last-child{ height: 15px; border-left: 8px solid rgb(68, 68, 80); border-right: 8px solid #ededee; border-bottom: 15px solid #ededee; } .nav-tag-span{ width: 16px; height: 30px; } .nav-tag-span>div:first-child{ height: 15px;border-left: 8px solid #545c64; border-right: 8px solid #545c64;border-top: 15px solid #2c2b36; } .nav-tag-span>div:last-child{ height: 15px;border-left: 10px solid #545c64; border-right: 10px solid #545c64;border-bottom: 15px solid #545c64; } .nav-tag-end{ width: 16px; height: 30px; } .nav-tag-end>div:first-child{ height: 15px;border-left: 8px solid #545c64; border-right: 8px solid #2c2b36;border-top: 15px solid #2c2b36; } .nav-tag-end>div:last-child{ height: 15px;border-left: 8px solid #545c64; border-right: 8px solid #2c2b36;border-bottom: 15px solid #545c64; } .nav-light{ background: #ededee; padding: 0px 10px; min-width: 100px; position: relative; color: rgb(113, 114, 116); } .nav-normal{ padding: 0px 10px; min-width: 100px; cursor: pointer; position: relative; background: #545c64; min-width: 100px; color:rgb(198, 202, 209); } .nav-light,.nav-normal span{ height: 30px;line-height: 30px; } .el-form-item__content .edui-default{ line-height:normal; } .close-menu{ position: absolute; color: white; bottom: 0px; cursor: pointer; left: 50%; margin-left:-8px; } .main-aside{ position: relative; background: #383e4b; transition:width 0.5s; } .main-aside>div{ position: relative; } /*el-main .el-tag--info{*/ /*color:#a1a1a1!important;*/ /*}*/ /*el-main .el-tag{*/ /*color: rgb(255, 208, 75);*/ /*}*/ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/index/index.html ================================================ 用户中心
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/index/index.js ================================================ /** * 系统架构 * @author zhdong * @date 2018/8/26 */ /*********全局混入*********/ Vue.mixin({ created(){ this.$getPath = function(url){ return App.getPath(url); } } }); App.start({ el: '#app', data: function() { return { activeName:'', cacheList:[], homeData:{"id":"#1","menuId":"#1","name":"首页","url":"#home/home.html","iconCls":"iconfont icon-home","componentId":"admin-home"}, tagNavList:[{"id":"#1","menuId":"#1","name":"首页","url":"#home/home.html","iconCls":"iconfont icon-home","componentId":"admin-home"}], menuList:[], componentId:'', mappingObject:{}, openedNames:[], pageHeight:0, wrapStyle:{}, collapsed:true, menuWidth:200, } }, created(){ this.pageHeight = $(document).height() this.wrapStyle = "height:"+this.pageHeight+"px;";//"height:"+this.pageHeight+"px;"; //加载本地缓存 var collapsed = window.localStorage.getItem("collapsed"); this.collapsed = eval(collapsed); //显示 if ( !this.collapsed ){ this.menuWidth = 200; }else{ this.menuWidth = 55; } }, mounted(){ var that = this; this.$nextTick(()=>{ App.request('api/user/menuList').callSuccess((res)=>{ this.menuList = res.data; this.menuList.forEach((item)=>{ if ( item.children && item.children.length ){ item.children.forEach((sub)=>{ this.mappingObject[sub.menuId] = sub.url; }) }else{ this.mappingObject[item.menuId] = item.url; } }) $.history.init(function(hash){ that.initHash(hash); }); }); }) }, methods:{ closeMenu(){ this.collapsed = !this.collapsed //显示 if ( !this.collapsed ){ this.menuWidth = 200; }else{ this.menuWidth = 65; } window.localStorage.setItem("collapsed",this.collapsed) }, initHash (hash) { var that = this; var currentHash = that.mappingObject[that.activeName]; currentHash = currentHash ? currentHash.substring(1) : ""; if (hash != "" && hash == currentHash){ return; } var findMenuId; //默认首页 if ( !hash || "#"+hash == that.homeData.url ){ hash = that.homeData.url.substring(1); findMenuId = that.homeData.menuId; }else{ $.each(that.mappingObject,function(r){ if (this == "#"+hash ){ findMenuId = r; } }) //从tabnav里面找 $.each(that.tagNavList,function(){ if (this.url == "#"+hash ){ findMenuId = this.menuId; } }); } //如果找到菜单,没找到菜单打开临时页面 if ( findMenuId ){ that.activeName = findMenuId; }else{ //that.loadModule(hash,new NavObject("#2",hash)); that.activeName ="#1" } }, //添加页面子模块导航 addSubPageTag :function(tabNav){ var exists = false; var that = this; var menuIndex = -1; $.each(this.tagNavList,function(idx){ if ( this.menuId == tabNav.menuId ){ exists = this; //跳出循环 return false; } //当前菜单的下标 if ( that.activeName == this.menuId){ menuIndex = idx; } }) //插入到当前菜单tag的后面 if ( !exists ){ var arySize = this.tagNavList.length; //如果是最后的位置,直接追加 if ( menuIndex == arySize - 1){ this.tagNavList.push(tabNav); }else{ let startList = this.tagNavList.slice(0,menuIndex+1); let endList = this.tagNavList.slice(menuIndex+1,arySize); let newAry = startList.concat(tabNav).concat(endList); this.tagNavList = newAry; } this.activeName = tabNav.menuId; }else{ var componentId = this.convertToComp(exists.url.substring(1));; if ( componentId ){ this.$delete(this.cacheList,this.cacheList.indexOf(componentId)) } exists.name = tabNav.name; this.$nextTick(()=>{ this.activeName = tabNav.menuId; }) } }, //加载模块页面 loadModule :function(url_,menuObj){ var that = this; var cmpId = this.convertToComp(url_); //判断组件是否存在 if ( cmpId ){ var comp = Vue.component(cmpId); if ( comp ){ //不存在则缓存 if ( this.cacheList.indexOf(cmpId) == -1 ){ this.cacheList.push(cmpId); } that.$nextTick(function(){ that.componentId = comp; }) return; } } //加载前清掉组件 that.componentId = ''; App.request({ dataType:"text", url:pageContextPath + url_, success:function(res){ var tmp = $('
').html(res).appendTo('#componentRegister'); var template = $('template:eq(0)', tmp); if (template[0]) { App.currentMoule.template = template.html(); Vue.component(cmpId, App.currentMoule); that.cacheList.push(cmpId)//组件缓存 menuObj.componentId = cmpId; that.$nextTick(function(){ that.componentId = cmpId; //5s后移除减轻页面压力 setTimeout(function(){ template.remove(); },5000) }) } } }).hideLoad(); }, convertToComp(url){ if ( !url ) return ""; if ( url.indexOf('?') != -1 ){ url = url.substring(0,url.indexOf('?')); } url = url.substring(0,url.indexOf('.')); url = url.replace(/\//,'-') return url; }, //添加页面导航 addPageTag :function(menuId,url_){ if ( typeof menuId !== 'string' ){ return []; } var exists = false; var menuObj; //优先从临时导航中查找 $.each(this.tagNavList,function(){ if ( this.menuId == menuId ){ exists = true; menuObj = this; //跳出循环 return false; } }) menuObj = menuObj || this.findMenuByMenuId(menuId) //设置标题 document.title = menuObj.name == '首页' ? '配置中心管理系统':menuObj.name; if ( !exists ){ this.tagNavList.push(menuObj); } this.loadModule(url_,menuObj); }, //根据菜单id找到菜单 findMenuByMenuId :function(menuId){ var that = this; if ( typeof menuId !== 'string' ){ return []; } if ( menuId == this.homeData.menuId){ return this.homeData; } var findData; $.each(this.menuList,function(){ var item = this; if ( item.menuId == menuId ){ findData = item; return false; } //检查子项是否存在 if ( item.children && item.children.length ){ $.each(item.children,function(){ var sub = this; if ( sub.menuId == menuId ){ findData = sub; return false; } }) //跳出外部循环 if ( findData ){ return false; } } }) return findData; }, closeCurrentTagNav :function(item){ if ( !this.activeName || this.activeName == this.homeData.menuId ){ return; } this.closeNav({menuId:this.activeName}); }, closeNav(item){ var that = this; var menuId = item.menuId; var find = -1; $.each(this.tagNavList,function(i_){ if ( this.menuId == menuId){ find = i_; return false; } }) if ( find != -1){ let isCur = that.tagNavList[find].menuId == that.activeName; Vue.delete(that.tagNavList,find); //如果关闭的是当前菜单,则选定下一个菜单 if ( isCur ){ //激活下一个页签,前一个找不到,就找下一个,再找不到就找首页 var menu = that.tagNavList[find-1] || that.tagNavList[find] || that.tagNavList[0]; if ( menu ){ that.activeName = menu.menuId; } } //根据menuId找到url var url = this.mappingObject[menuId]; //获取组件,清除组件缓存 var componentId = url && this.convertToComp(url.substring(1)); if ( componentId ){ this.$delete(this.cacheList,this.cacheList.indexOf(componentId)) } } }, handTags(item){ this.activeName = item.menuId; }, menuSelect(n){ this.activeName = n; } }, watch:{ openedNames(v){ console.log(v) var reallyActiveName = this.activeName.split('@')[0]; var menuIdArray = reallyActiveName.split("-"); var openNames = []; var conect = []; $.each(menuIdArray,function(){ conect.push(this); openNames.push(conect.join('-')); }); }, //监听菜单选择 activeName :function(name) { //选中系统 var that = this; var reallyActiveName = name.split('@')[0]; var menuIdArray = reallyActiveName.split("-"); var openNames = []; var conect = []; $.each(menuIdArray,function(){ conect.push(this); openNames.push(conect.join('-')); }); this.openedNames = openNames; var url_ = this.mappingObject[name]; //菜单中没有就到导航里面找 if ( !url_ ){ //从tabnav里面找 $.each(that.tagNavList,function(){ if (this.menuId == name ){ url_ = this.url; } }); } if ( url_ ){ if ( url_.indexOf('#') == 0 ){ window.location.hash=url_; this.addPageTag(name,url_.substring(1)); } else{ window.open(url_); } } } } }) ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/login/login.html ================================================ 配置中心
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/region/region.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/region/region.js ================================================ (() => { App.moule({ data: function () { return { /*添加区域的弹出框*/ addRegion: false, tableHeight: 0, /*复选框*/ selectionData: [], /*数据表格里的数据*/ tableData: [{ id: '', regionName: '', regionDesc: '', createTime: '', updateTime: '', }], /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, /*查询API对应的form表单*/ form: { regionName: '', createTime: '', updateTime: '', }, /*添加区域的from*/ addRegionForm: { regionName: '', regionDesc: '', }, /*添加表单输入项的校验*/ regionRules: { regionName: [ {required: true, message: '请输入区域名称', trigger: 'blur'} ], regionDesc: [ {required: true, message: '请输入区域描述', trigger: 'blur'} ], } } }, methods: { /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; App.request("/region/queryRegion").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }) }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; this.findApi(); }, /* 添加区域 打开窗口*/ addRegionFrom() { var that = this; if (that.addRegionForm.regionName != null && that.addRegionForm.regionName != "" && that.addRegionForm.regionDesc != null && that.addRegionForm.regionDesc != "") { App.request("/region/addRegion").post().setData(that.addRegionForm).callSuccess(function (resp) { if (resp.data) { that.$message({ type: 'success', message: '成功!' }); } else { that.$message({ type: 'error', message: '失败!' }); } ; that.addRegionClose(); }); } else { that.$message({ showClose: true, message: '区域名称或者区域描述不能为空!', type: 'error' }); } }, // 关闭添加region的dialog addRegionClose() { this.addRegion = false; this.addRegionForm = { regionName: '', regionDesc: '', } this.$refs["addRegionForm"].resetFields(); this.findApi(); }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, selectChagne(v) { this.selectionData = v; }, /*删除API*/ deleteRow(row) { var that = this; if ( !that.selectionData.length && !row ) { App.error('请选择记录'); return; } that.$confirm('此操作将永久删除该区域信息, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var selData = row ? [row] : that.selectionData; var newData = ''; selData.forEach(function (t) { newData = newData + t.id + ","; }); App.request("/region/delectRegion/" + newData.substr(0, newData.length - 1)).post().callSuccess(function (resp) { if (resp.data > 0) { that.$message({ type: 'success', message: '删除成功!' }); that.findApi(); } else if (resp.data == -1) { App.error("已关联应用不能删除,必须先删除应用!") } else { that.$message({ type: 'error', message: '删除失败!' }); } that.findApi(); }); }).catch(() => { that.$message({ type: 'info', message: '已取消删除' }); }); }, /*更新区域*/ handleClick: function (row) { this.addRegion = true; this.addRegionForm = row; }, }, /* 组件被激活 类似小程序onshow */ activated: function () { this.findApi(); }, /* 组件创建完成事件 */ created: function () { this.$nextTick(() => { this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/server/server.js ================================================ (()=>{ App.moule({ data :function() { return { /*添加区域的弹出框*/ addServer: false, tableHeight:0, deleteModal:false, /*数据表格里的数据*/ tableData: [{ id: '', serverHost: '', serverDesc: '', regionName: '', createTime: '', updateTime: '', }], pager:new App.Pager(), /*分页栏的 当前页*/ tableCurrentPage: 1, /*分页栏的 每页行数*/ tablePageSize: 10, /*分页栏的总条数*/ tableTotal: 0, addServerform:{ id: '', serverHost: '', serverDesc: '', regionId: '', regionName:'' }, // 区域 regions: [], /*查询API对应的form表单*/ form: { serverHost: '', serverDesc: '', createTime: '', updateTime: '', pageNum: '', pageSize: '', }, rules: { serverHost: [{required: true, message: '请输入服务器IP', trigger: "blur"}], serverDesc:[{required: true, message: '请输入服务器描述', trigger: "blur"}], regionId: [{required: true, message: '请选择一个区域', trigger: 'change'} ] }, itemId: '', title: '', } }, methods:{ /*查询API*/ findApi() { var that = this; that.form.pageNum = that.tableCurrentPage; that.form.pageSize = that.tablePageSize; App.request("/api/serverHostConfig/page").post().setData(that.form).callSuccess(function (resp) { that.tableData = resp.data.list; that.tableTotal = resp.data.total; }); }, /*查询API 提交的函数*/ onSubmit() { this.tableCurrentPage = 1; this.findApi(); }, /*重置查询*/ resetForm() { var that = this; this.form = {}; that.tableCurrentPage = 1; that.findApi(); }, /*分页栏对应的函数*/ handleSizeChange(val) { this.tablePageSize = val; //console.log(`每页 ${val} 条`); this.findApi(); }, handleCurrentChange(val) { this.tableCurrentPage = val; // console.log(`当前页: ${val}`); this.findApi(); }, selectChagne(v){ this.selectionData = v; }, deleteBatch(){ //获取选择记录 if ( !this.selectionData.length ){ App.error('请选择记录'); return; } this.deleteModal=true; }, confirmDelete(){ var _this = this; App.post('/api/server/delect').setData(_this.selectionData) .setLoadArea(_this.$refs.deleteBtn).callSuccess( ()=> { App.success('删除成功!'); _this.deleteModal = false }); }, del(row) { var that = this; that.$confirm('此操作将永久删除该服务器信息, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { var id=row.id; App.request("/api/serverHostConfig/delectServerHostConfig/" + id).post().callSuccess(function (resp) { debugger; if (resp.data == 1) { App.success('删除成功'); }else{ App.error('删除失败'); } that.findApi(); }); }).catch(() => { that.$message({ type: 'info', message: '已取消删除' }); }); }, open(title, data) { var that = this; that.title = title; if (data != null) { that.addServerform = data; that.itemId = data.id; } else { that.addServerform = {}; } that.addServer = true; }, submit(formName) { var that = this; var url = ''; if (that.itemId == '' || that.itemId == null) { url = '/api/serverHostConfig/addServerHostConfig'; } else { url = '/api/serverHostConfig/updateServerHostConfig'; that.addServerform.id = that.itemId; } if(that.addServerform.serverDesc != null && that.addServerform.serverDesc != "" && that.addServerform.serverHost != null && that.addServerform.serverHost != "" && that.addServerform.regionId != null && that.addServerform.regionId != ""){ App.post(url).setData(this.addServerform).callSuccess((res) => { console.log(res) if (!that.addServerform.id) { if (res) { App.success('保存成功') that.addServer = false; that.findApi(); } else { App.success('保存失败') } } else { if (res) { App.success('修改成功') that.addServer = false; that.itemId =''; that.findApi(); } else { App.success('修改失败') } } })} else{ that.$message({ showClose: true, message: '服务器ip或者区域或者描述不能为空!', type: 'error' }); } }, }, /* 组件创建完成事件 */ created :function(){ this.$nextTick(()=>{ this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted :function(){ App.request("/appManager/queryAllRegion").callSuccess((res) => { // 无区域可用,提示添加区域 if (res.data.length == 0) { //调整添加区域 App.error("暂无区域可选择,请先添加区域!!"); //App.openModule("addRegion","添加区域","template/detail.html"); } else { this.regions = res.data; } }) }, /* 组件更新完成事件 */ updated:function(){ console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated :function(){ console.log('组件被激活'); this.findApi(); }, /* 组件未被激活 类似小程序ondestroy */ deactivated :function() { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/server/serverlist.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/support/code_check.html ================================================ 前台页面代码检查
================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/support/support.html ================================================

你的浏览器版本太低,请升级高版本

推荐使用chrome浏览器68.xx以上版本,FireFox浏览器61.xx以上版本

点击这里,下载chrome浏览器

================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/template/detail.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/template/detail.js ================================================ (()=>{ App.moule({ data :function() { return { form:{}, tabStyle:{} } }, activated(){ //接收数据 var editData = App.getData('editType'); App.success("接收传过来的数据为:"+JSON.stringify(editData)); this.$nextTick(()=>{ this.$set(this.tabStyle,'height',(App.MainVueApp.pageHeight - 150)+"px") this.$set(this.tabStyle,'overflow',"auto") }) }, methods:{ submit(){ App.post('api/demo/submit').setData(this.form).callSuccess((res)=>{ console.log(res) App.success('保存成功') App.closeCurrentTagNav() }) }, }, }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/template/template.html ================================================ ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/pages/template/template.js ================================================ (()=>{ App.moule({ data :function() { return { prop: "我是属性", tableHeight:0, deleteModal:false, selectionData:[], pager:new App.Pager(), } }, methods:{ selectChagne(v){ this.selectionData = v; }, deleteBatch(){ //获取选择记录 if ( !this.selectionData.length ){ App.error('请选择记录'); return; } this.deleteModal=true; }, confirmDelete(){ var _this = this; App.post('/api/template/test').setData(_this.selectionData) .setLoadArea(_this.$refs.deleteBtn).callSuccess( ()=> { App.success('删除成功!'); _this.deleteModal = false }); }, edit(row){ //传递数据 App.putData("editData",row); App.openModule("detail","信息编辑","template/detail.html"); }, del(row){ this.$set(this,'selectionData',[row]); this.deleteModal = true; }, create(){ //传递数据 App.putData("editType","new"); App.openModule("detail","信息创建","template/detail.html"); } }, /* 组件创建完成事件 */ created :function(){ this.$nextTick(()=>{ this.tableHeight = App.MainVueApp.pageHeight - 220 }) }, /* 模板编译挂载完成事件 类似小程序onload */ mounted :function(){ console.log('模板编译挂载完成事件'); App.request('api/demo/tempate').callSuccess((res)=>{ //兼容manggo 的pager和github的pager this.pager = new App.Pager(res.data); }) }, /* 组件更新完成事件 */ updated:function(){ console.log('组件更新完成事件'); }, /* 组件被激活 类似小程序onshow */ activated :function(){ console.log('组件被激活'); }, /* 组件未被激活 类似小程序ondestroy */ deactivated :function() { console.log('组件未激活'); } }); })() ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/plugin/common/common.js ================================================ /** * App应用对象 * @author zhdong * @date 2018/8/26 */ var App = (function ($) { const localPrefix = "LOCAL_DATA_"; //声明app var app = { MainVueApp: null, currentLoading: null, currentMoule:null }; app.start = function(vue){ app.MainVueApp = new Vue(vue); } var localData = {}; /*************** 设置本地缓存 ****************/ app.putData = function(key,data){ localData[key] = data; try{ window.localStorage.setItem(localPrefix+key,JSON.stringify(data)); }catch (e) { console.log("存储异常:"+e) } } app.getData = function(key){ var data = localData[key]; if ( !data ){ try { data = window.localStorage.getItem(localPrefix + key); if (data != null && data != undefined) { return JSON.parse(data); } }catch (e) { console.log("存储异常:"+e) } } return data; } /*************** 设置本地缓存 ****************/ app.removeData = function(key){ delete localData[key]; window.localStorage.removeItem(localPrefix + key); } /* 关闭当前页面 */ app.closeCurrentTagNav = function(){ this.MainVueApp.closeCurrentTagNav(); }; app.isEmpty = function(obj){ return obj == null || obj == undefined || obj == ""; } /* 分页对象 PageInfo返回封装对象*/ app.Pager = function (p) { p = p || {}; //判断是什么page类型 if ( p.content ){ //mongo类型 this.list = p.content || []; this.pageNum = p.number || 1; this.pageSize = p.size || 10; this.pages = p.totalPages || 1; this.total = p.totalElements || 0; }else{ this.list = p.list || []; this.pageNum = p.pageNum || 1; this.pageSize = p.pageSize || 10; this.pages = p.pages || 1; this.total = p.total || 0; } } /* 分页对象 PageImpl返回封装对象*/ app.Page = function (p) { p = p || {}; this.list = p.content || []; this.pageNum = p.pageNum || 1; this.pageSize = p.pageSize || 10; this.pages = p.totalPages || 1; this.total = p.totalElements || 0; } /* 获取oss访问地址 */ app.getOssAccessUrl = function (buck, key) { return "http://" + buck + "." + OSSAccessDomain + "/" + key; } app.getPath = function(url){ url = contextPath + url; if (url.substring(0, 2) == "//") { url = url.substring(1); } return url; } app.requestCache = new Map(); //获取app高度 app.height = function () { return $(App.MainVueApp.$el).height(); } //获取模块高度 app.moduleHeight = function () { var layoutH = document.body.clientHeight - App.MainVueApp.moduleDiff; var tagH = App.MainVueApp.tagsNavHeight; return layoutH - tagH; } //计算高度属性,需要传递hh头高度才能计算得出,默认为0 var computedHeight = function (hh) { var hh = hh || 0; let modulePadding = 18 * 2;//模块内边距 let rang = 10;//误差 let pgH = 32 + modulePadding + rang; var minH = 100; var contentH = app.moduleHeight() - hh - pgH; return contentH < minH ? minH : contentH; }; /* * moduleComp,必须组件中直接传递this * headH 头高度默认0,如果为数组则自动填充多个属性的高度格式 * [ * {prop:'tableAptHeight',hh:'100'} * ] * */ app.acptTableHeight = function (moduleComp, headH) { var work_ = function () { if ($.isArray(headH)) { $.each(headH, function () { if (!(this.prop in moduleComp)) { console.error('你的组建要定义' + this.prop + '属性'); //跳出循环 return false } moduleComp[this.prop] = computedHeight(this.hh || 0); }) } else { moduleComp.tableAptHeight = computedHeight(headH || 0); } if ( navigator.userAgent.indexOf("Firefox") != -1){ App.MainVueApp.moduleHeight = (document.body.clientHeight-60) + 'px'; } }; work_(); //解决resize频繁更新问题 var count_ = 0; $(window).off('resize').on('resize', function () { var self_ = ++count_; if ( self_ < count_){ return; } setTimeout(work_,500) }); } //注册组件 app.register = function (compent) { if (!compent.template) { app.error('组件template为空') } else { return Vue.component(compent.template.substring(1), compent); } } app.moule =function(compent){ this.currentMoule = compent; } //设置主App应用 app.setMainApp = function (vue) { this.MainVueApp = vue; } //显示loadding蒙层 app.showLoadding = function (message, timeout, ajax) { try { this.currentLoading && this.currentLoading.close(); this.currentLoading = this.MainVueApp.$loading({ lock: true, text: message || '加载中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }) } catch (e) { console.log('显示loading错误' + e) } } //隐藏蒙层 app.hideLoading = function () { try { this.currentLoading && this.currentLoading.close(); } catch (e) { console.log('隐藏loading错误' + e) } } //弹出提示toast app.info = function (msg, timeout, closable) { this.MainVueApp.$message({ type:"info", showClose: closable || true, message:msg, duration: timeout || 3*1000, }); } //弹出成功toast app.success = function (msg, timeout, closable) { this.MainVueApp.$message({ type:"success", showClose: closable || true, message:msg, duration: timeout || 3*1000, }); } //弹出警告toast app.warning = function (msg, timeout, closable) { this.MainVueApp.$message({ type:"warning", showClose: closable || true, message:msg, duration: timeout || 3*1000, }); } //弹出错误toast app.error = function (msg, timeout, closable) { this.MainVueApp.$message.error(msg); } //debug app.debug = function (e) { if (DEBUGGER) { console.log(e); } } app.request = function (options, data) { //默认ajax请求 var defOpt = { type: "get", dataType: "json", showLoad: true, cached: false, loadArea:null, setUrl: function (url) { this.url = contextPath + url; if (this.url.substring(0, 2) == "//") { this.url = this.url.substring(1); } return this; }, setType: function (type) { this.type = type; return this; }, setLoadArea(area) { this.loadArea = area; return this; }, post: function () { this.type = "post"; return this; }, useCache: function () { this.cached = true; return this; }, synced: function () { this.async = false; return this; }, setData: function (d) { this.contentType = 'application/json;charset=utf-8'; this.data = JSON.stringify(d); return this; }, hideLoad: function () { this.showLoad = false; return this; }, beforeSend: function (xhr) { if ( this.loadArea ){ this.loadArea.$set(this.loadArea,'loading',true) }else{ this.showLoad && App.showLoadding(null, null, xhr); } this.beforeCallback && this.beforeCallback.call(this,xhr); // 设置token 本地获取token var token = window.localStorage.getItem('M-Auth-Token'); xhr.setRequestHeader('M-Auth-Token', token); }, complete: function () { if ( this.loadArea ){ this.loadArea.$set(this.loadArea,'loading',false) }else{ this.showLoad && App.hideLoading(); } this.completeCallback && this.completeCallback(); }, success: function (res) { if (this.successCallback2) { return this.successCallback2(res); } if (res.success) { //如果激活了缓存,则保存缓存 if ((!this.type || this.type.toLowerCase() == 'get') && this.cached) { app.requestCache.set(this.url,$.extend(true,{},res)) } this.successCallback && this.successCallback(res); } else { if (this.errorCallback2) { return this.errorCallback2(res); } res.message && app.error(res.message); this.errorCallback && this.errorCallback(res); } }, error: function (xhr) { if (xhr.status == 401) { var dataJson = xhr.responseJSON || JSON.parse(xhr.responseText); if (dataJson && dataJson.code == 10060) { app.error('登录超时,请重新登录!'); app.MainVueApp.callLogin(this); return; } } if (this.errorCallback2) { return this.errorCallback2(xhr.responseJSON, xhr); } if (xhr.status == 403) { app.error('您没有浏览该页面的权限'); } else if (xhr.status == 404) { app.error('您访问的页面不存在'); } else if (xhr.status == 500) { var dataJson = xhr.responseJSON || JSON.parse(xhr.responseText); if (dataJson.message) { app.error(dataJson.message); } else { app.error('页面内部错误,请联系管理员'); } } else if (xhr.status == 405) { app.error('请求方法错误'); }else{ console.log(xhr) app.error('无法访问服务器,请检查网络'); } this.errorCallback && this.errorCallback(xhr.responseJSON, xhr); }, callBefore: function (callback) { this.beforeCallback = callback; return this; }, callComplete: function (callback) { this.completeCallback = callback; return this; }, callError: function (callback) { this.errorCallback = callback return this; }, callSuccess: function (callback) { this.successCallback = callback return this; }, handError: function (callback) { this.errorCallback2 = callback return this; }, handSuccess: function (callback) { this.successCallback2 = callback return this; } } var finalOpt; //第一个参数是string,直接引用默认属性,否则为对象类型,直接复制对象 if (typeof options === 'string') { finalOpt = defOpt.setUrl(options); finalOpt.data = convertParam(data); } else { finalOpt = $.extend(defOpt, options) finalOpt.url = contextPath + finalOpt.url; //设置参数 if (data) { //处理数据 finalOpt.data = convertParam(data); } if (finalOpt.url.substring(0, 2) == "//") { finalOpt.url = finalOpt.url.substring(1); } } //延时调用,方便调用对象方法追加属性 setTimeout(function () { //判断是否从缓存中获取 if (finalOpt.cached) { var ps = finalOpt.data ? $.param(finalOpt.data) : ""; var key = finalOpt.url.indexOf('?') == -1 && ps ? finalOpt.url + "?" + ps :finalOpt.url + ps; var exitsCache = app.requestCache.get(key); if (exitsCache) { finalOpt.successCallback && finalOpt.successCallback($.extend(true,{},exitsCache)); return; } } $.ajax(finalOpt) }, 100); return finalOpt; } var convertParam = function(data){ for (var r in data){ var d = data[r]; //将集合转换为正确的数组格式 if ( d && $.isArray(d) && d.length ){ d.forEach((item,i)=>{ data[r+"["+i+"]"] = item; }); delete data[r]; } } return data; } app.post = function(options, data){ return app.request(options, data).post(); } /** * * @param subMenuFlag 子模块标识 ,直接写html的名称吧 * @param url 模块url * @param name 模块名称 */ app.openModule = function (moudelFlag, name, url) { var mainApp = this.MainVueApp; var tabNav = new NavObject(mainApp.activeName + "@" + moudelFlag, "#" + url, name); mainApp.addSubPageTag(tabNav); } app.openResource = function(url){ window.location.hash = url; } return app; })(jQuery) //导航对象 function NavObject(menuId, url, name, iconCls) { this.menuId = menuId; this.name = name; this.url = url; this.iconCls = iconCls; } function JS_UUID() { var uuid = new Date().getTime(); var rdn = Math.floor(Math.random() * 1000000); return uuid.toString(36) + "-" + rdn.toString(36); } Date.prototype.format = function(format) { var date = { "M+": this.getMonth() + 1, "d+": this.getDate(), "h+": this.getHours(), "m+": this.getMinutes(), "s+": this.getSeconds(), "q+": Math.floor((this.getMonth() + 3) / 3), "S+": this.getMilliseconds() }; if (/(y+)/i.test(format)) { format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)); } for (var k in date) { if (new RegExp("(" + k + ")").test(format)) { format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? date[k] : ("00" + date[k]).substr(("" + date[k]).length)); } } return format; } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/plugin/common/env.js ================================================ /** * 环境信息 * @author zhdong * @date 2018/8/26 */ var contextPath = "/";//上下文根 var LOCAL_STORAGE_TABBTN_KEY = "tabBtnStorage";//导航存储key var pageContextPath = contextPath+"pages/";//页面路径 var OSSAccessDomain= "oss-cn-shenzhen.aliyuncs.com"; //调试模式 var DEBUGGER = false; //首页配置 var HOME_NAV_TAB_OBJ = { id: "#1", menuId: "#1", name: "首页", url: "#home/home.html", iconCls: "ios-home-outline", componentId:"admin-home" }; // //浏览器检查,是否支持es6表达式 // try { // //表达式检查 // eval('var a1 = { func(){ }, func2: () => { } };'); // [].findIndex(function(){}) // const aaaaaaaaa = 1; // // // // // } catch (e) { // window.location.href=contextPath+"pages/support/support.html"; // } ================================================ FILE: SpringCloud-Custom-ConfigCenter/custom-config-web/src/main/resources/static/plugin/element/js/index.js ================================================ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue")):"function"==typeof define&&define.amd?define("ELEMENT",["vue"],t):"object"==typeof exports?exports.ELEMENT=t(require("vue")):e.ELEMENT=t(e.Vue)}(this,function(e){return function(e){function t(n){if(i[n])return i[n].exports;var s=i[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,t),s.l=!0,s.exports}var i={};return t.m=e,t.c=i,t.d=function(e,i,n){t.o(e,i)||Object.defineProperty(e,i,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var i=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(i,"a",i),i},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/dist/",t(t.s=93)}([function(e,t){e.exports=function(e,t,i,n,s,r){var o,a=e=e||{},l=typeof e.default;"object"!==l&&"function"!==l||(o=e,a=e.default);var u="function"==typeof a?a.options:a;t&&(u.render=t.render,u.staticRenderFns=t.staticRenderFns,u._compiled=!0),i&&(u.functional=!0),s&&(u._scopeId=s);var c;if(r?(c=function(e){e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,e||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(r)},u._ssrRegister=c):n&&(c=n),c){var d=u.functional,h=d?u.render:u.beforeCreate;d?(u._injectStyles=c,u.render=function(e,t){return c.call(t),h(e,t)}):u.beforeCreate=h?[].concat(h,c):[c]}return{esModule:o,exports:a,options:u}}},function(e,t,i){"use strict";function n(e,t,i){this.$children.forEach(function(s){s.$options.componentName===e?s.$emit.apply(s,[t].concat(i)):n.apply(s,[e,t].concat([i]))})}t.__esModule=!0,t.default={methods:{dispatch:function(e,t,i){for(var n=this.$parent||this.$root,s=n.$options.componentName;n&&(!s||s!==e);)(n=n.$parent)&&(s=n.$options.componentName);n&&n.$emit.apply(n,[t].concat(i))},broadcast:function(e,t,i){n.call(this,e,t,i)}}}},function(t,i){t.exports=e},function(e,t,i){"use strict";function n(){for(var e=arguments.length,t=Array(e),i=0;i=r)return e;switch(e){case"%s":return String(t[n++]);case"%d":return Number(t[n++]);case"%j":try{return JSON.stringify(t[n++])}catch(e){return"[Circular]"}break;default:return e}}),a=t[n];n0&&void 0!==arguments[0]?arguments[0]:"";return String(e).replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")},t.arrayFindIndex=function(e,t){for(var i=0;i!==e.length;++i)if(t(e[i]))return i;return-1});t.arrayFind=function(e,t){var i=d(e,t);return-1!==i?e[i]:void 0},t.coerceTruthyValueToArray=function(e){return Array.isArray(e)?e:e?[e]:[]},t.isIE=function(){return!u.default.prototype.$isServer&&!isNaN(Number(document.documentMode))},t.isEdge=function(){return!u.default.prototype.$isServer&&navigator.userAgent.indexOf("Edge")>-1}},function(e,t,i){"use strict";function n(e,t){if(!e||!t)return!1;if(-1!==t.indexOf(" "))throw new Error("className should not contain space.");return e.classList?e.classList.contains(t):(" "+e.className+" ").indexOf(" "+t+" ")>-1}function s(e,t){if(e){for(var i=e.className,s=(t||"").split(" "),r=0,o=s.length;r-1?"center "+i:i+" center"}},appendArrow:function(e){var t=void 0;if(!this.appended){this.appended=!0;for(var i in e.attributes)if(/^_v-/.test(e.attributes[i].name)){t=e.attributes[i].name;break}var n=document.createElement("div");t&&n.setAttribute(t,""),n.setAttribute("x-arrow",""),n.className="popper__arrow",e.appendChild(n)}}},beforeDestroy:function(){this.doDestroy(!0),this.popperElm&&this.popperElm.parentNode===document.body&&(this.popperElm.removeEventListener("click",a),document.body.removeChild(this.popperElm))},deactivated:function(){this.$options.beforeDestroy[0].call(this)}}},function(e,t,i){"use strict";function n(e,t,i){return function(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};!(i&&i.context&&n.target&&s.target)||e.contains(n.target)||e.contains(s.target)||e===n.target||i.context.popperElm&&(i.context.popperElm.contains(n.target)||i.context.popperElm.contains(s.target))||(t.expression&&e[l].methodName&&i.context[e[l].methodName]?i.context[e[l].methodName]():e[l].bindingFn&&e[l].bindingFn())}}t.__esModule=!0;var s=i(2),r=function(e){return e&&e.__esModule?e:{default:e}}(s),o=i(5),a=[],l="@@clickoutsideContext",u=void 0,c=0;!r.default.prototype.$isServer&&(0,o.on)(document,"mousedown",function(e){return u=e}),!r.default.prototype.$isServer&&(0,o.on)(document,"mouseup",function(e){a.forEach(function(t){return t[l].documentHandler(e,u)})}),t.default={bind:function(e,t,i){a.push(e);var s=c++;e[l]={id:s,documentHandler:n(e,t,i),methodName:t.expression,bindingFn:t.value}},update:function(e,t,i){e[l].documentHandler=n(e,t,i),e[l].methodName=t.expression,e[l].bindingFn=t.value},unbind:function(e){for(var t=a.length,i=0;i1&&void 0!==arguments[1]?arguments[1]:1;return new Date(e.getFullYear(),e.getMonth(),e.getDate()-t)}),v=(t.nextDate=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)},t.getStartDateOfMonth=function(e,t){var i=new Date(e,t,1),n=i.getDay();return 0===n?m(i,7):m(i,n)},t.getWeekNumber=function(e){if(!h(e))return null;var t=new Date(e.getTime());t.setHours(0,0,0,0),t.setDate(t.getDate()+3-(t.getDay()+6)%7);var i=new Date(t.getFullYear(),0,4);return 1+Math.round(((t.getTime()-i.getTime())/864e5-3+(i.getDay()+6)%7)/7)},t.getRangeHours=function(e){var t=[],i=[];if((e||[]).forEach(function(e){var t=e.map(function(e){return e.getHours()});i=i.concat(c(t[0],t[1]))}),i.length)for(var n=0;n<24;n++)t[n]=-1===i.indexOf(n);else for(var s=0;s<24;s++)t[s]=!1;return t},t.getRangeMinutes=function(e,t){var i=new Array(60);return e.length>0?e.forEach(function(e){var s=e[0],r=e[1],o=s.getHours(),a=s.getMinutes(),l=r.getHours(),u=r.getMinutes();o===t&&l!==t?n(i,a,60,!0):o===t&&l===t?n(i,a,u+1,!0):o!==t&&l===t?n(i,0,u+1,!0):ot&&n(i,0,60,!0)}):n(i,0,60,!0),i},t.range=function(e){return Array.apply(null,{length:e}).map(function(e,t){return t})},t.modifyDate=function(e,t,i,n){return new Date(t,i,n,e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds())}),g=t.modifyTime=function(e,t,i,n){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),t,i,n,e.getMilliseconds())},b=(t.modifyWithTimeString=function(e,t){return null!=e&&t?(t=f(t,"HH:mm:ss"),g(e,t.getHours(),t.getMinutes(),t.getSeconds())):e},t.clearTime=function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate())},t.clearMilliseconds=function(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),0)},t.limitTimeRange=function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"HH:mm:ss";if(0===t.length)return e;var n=function(e){return r.default.parse(r.default.format(e,i),i)},s=n(e),o=t.map(function(e){return e.map(n)});if(o.some(function(e){return s>=e[0]&&s<=e[1]}))return e;var a=o[0][0],l=o[0][0];return o.forEach(function(e){a=new Date(Math.min(e[0],a)),l=new Date(Math.max(e[1],a))}),v(s1&&void 0!==arguments[1]?arguments[1]:1,i=e.getFullYear(),n=e.getMonth();return y(e,i-t,n)},t.nextYear=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,i=e.getFullYear(),n=e.getMonth();return y(e,i+t,n)},t.extractDateFormat=function(e){return e.replace(/\W?m{1,2}|\W?ZZ/g,"").replace(/\W?h{1,2}|\W?s{1,3}|\W?a/gi,"").trim()},t.extractTimeFormat=function(e){return e.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?y{2,4}/g,"").trim()}},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0,t.PopupManager=void 0;var s=i(2),r=n(s),o=i(10),a=n(o),l=i(111),u=n(l),c=i(44),d=n(c),h=i(5),f=1,p=void 0,m=function e(t){return 3===t.nodeType&&(t=t.nextElementSibling||t.nextSibling,e(t)),t};t.default={props:{visible:{type:Boolean,default:!1},openDelay:{},closeDelay:{},zIndex:{},modal:{type:Boolean,default:!1},modalFade:{type:Boolean,default:!0},modalClass:{},modalAppendToBody:{type:Boolean,default:!1},lockScroll:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!1},closeOnClickModal:{type:Boolean,default:!1}},beforeMount:function(){this._popupId="popup-"+f++,u.default.register(this._popupId,this)},beforeDestroy:function(){u.default.deregister(this._popupId),u.default.closeModal(this._popupId),this.restoreBodyStyle()},data:function(){return{opened:!1,bodyPaddingRight:null,computedBodyPaddingRight:0,withoutHiddenClass:!0,rendered:!1}},watch:{visible:function(e){var t=this;if(e){if(this._opening)return;this.rendered?this.open():(this.rendered=!0,r.default.nextTick(function(){t.open()}))}else this.close()}},methods:{open:function(e){var t=this;this.rendered||(this.rendered=!0);var i=(0,a.default)({},this.$props||this,e);this._closeTimer&&(clearTimeout(this._closeTimer),this._closeTimer=null),clearTimeout(this._openTimer);var n=Number(i.openDelay);n>0?this._openTimer=setTimeout(function(){t._openTimer=null,t.doOpen(i)},n):this.doOpen(i)},doOpen:function(e){if(!this.$isServer&&(!this.willOpen||this.willOpen())&&!this.opened){this._opening=!0;var t=m(this.$el),i=e.modal,n=e.zIndex;if(n&&(u.default.zIndex=n),i&&(this._closing&&(u.default.closeModal(this._popupId),this._closing=!1),u.default.openModal(this._popupId,u.default.nextZIndex(),this.modalAppendToBody?void 0:t,e.modalClass,e.modalFade),e.lockScroll)){this.withoutHiddenClass=!(0,h.hasClass)(document.body,"el-popup-parent--hidden"),this.withoutHiddenClass&&(this.bodyPaddingRight=document.body.style.paddingRight,this.computedBodyPaddingRight=parseInt((0,h.getStyle)(document.body,"paddingRight"),10)),p=(0,d.default)();var s=document.documentElement.clientHeight0&&(s||"scroll"===r)&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.computedBodyPaddingRight+p+"px"),(0,h.addClass)(document.body,"el-popup-parent--hidden")}"static"===getComputedStyle(t).position&&(t.style.position="absolute"),t.style.zIndex=u.default.nextZIndex(),this.opened=!0,this.onOpen&&this.onOpen(),this.doAfterOpen()}},doAfterOpen:function(){this._opening=!1},close:function(){var e=this;if(!this.willClose||this.willClose()){null!==this._openTimer&&(clearTimeout(this._openTimer),this._openTimer=null),clearTimeout(this._closeTimer);var t=Number(this.closeDelay);t>0?this._closeTimer=setTimeout(function(){e._closeTimer=null,e.doClose()},t):this.doClose()}},doClose:function(){this._closing=!0,this.onClose&&this.onClose(),this.lockScroll&&setTimeout(this.restoreBodyStyle,200),this.opened=!1,this.doAfterClose()},doAfterClose:function(){u.default.closeModal(this._popupId),this._closing=!1},restoreBodyStyle:function(){this.modal&&this.withoutHiddenClass&&(document.body.style.paddingRight=this.bodyPaddingRight,(0,h.removeClass)(document.body,"el-popup-parent--hidden")),this.withoutHiddenClass=!0}}},t.PopupManager=u.default},function(e,t,i){"use strict";t.__esModule=!0;var n=i(186),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t){var i=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=i)},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0,t.i18n=t.use=t.t=void 0;var s=i(102),r=n(s),o=i(2),a=n(o),l=i(103),u=n(l),c=i(104),d=n(c),h=(0,d.default)(a.default),f=r.default,p=!1,m=function(){var e=Object.getPrototypeOf(this||a.default).$t;if("function"==typeof e&&a.default.locale)return p||(p=!0,a.default.locale(a.default.config.lang,(0,u.default)(f,a.default.locale(a.default.config.lang)||{},{clone:!0}))),e.apply(this,arguments)},v=t.t=function(e,t){var i=m.apply(this,arguments);if(null!==i&&void 0!==i)return i;for(var n=e.split("."),s=f,r=0,o=n.length;r=t.length)break;s=t[n++]}else{if(n=t.next(),n.done)break;s=n.value}var r=s,o=r.target.__resizeListeners__||[];o.length&&o.forEach(function(e){e()})}};t.addResizeListener=function(e,t){r||(e.__resizeListeners__||(e.__resizeListeners__=[],e.__ro__=new s.default(o),e.__ro__.observe(e)),e.__resizeListeners__.push(t))},t.removeResizeListener=function(e,t){e&&e.__resizeListeners__&&(e.__resizeListeners__.splice(e.__resizeListeners__.indexOf(t),1),e.__resizeListeners__.length||e.__ro__.disconnect())}},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,i){var n=i(80),s=i(57);e.exports=Object.keys||function(e){return n(e,s)}},function(e,t,i){"use strict";t.__esModule=!0,t.default=function(e){return{methods:{focus:function(){this.$refs[e].focus()}}}}},function(e,t,i){"use strict";t.__esModule=!0;var n=i(116),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;var s=i(5),r=function(){function e(){n(this,e)}return e.prototype.beforeEnter=function(e){(0,s.addClass)(e,"collapse-transition"),e.dataset||(e.dataset={}),e.dataset.oldPaddingTop=e.style.paddingTop,e.dataset.oldPaddingBottom=e.style.paddingBottom,e.style.height="0",e.style.paddingTop=0,e.style.paddingBottom=0},e.prototype.enter=function(e){e.dataset.oldOverflow=e.style.overflow,0!==e.scrollHeight?(e.style.height=e.scrollHeight+"px",e.style.paddingTop=e.dataset.oldPaddingTop,e.style.paddingBottom=e.dataset.oldPaddingBottom):(e.style.height="",e.style.paddingTop=e.dataset.oldPaddingTop,e.style.paddingBottom=e.dataset.oldPaddingBottom),e.style.overflow="hidden"},e.prototype.afterEnter=function(e){(0,s.removeClass)(e,"collapse-transition"),e.style.height="",e.style.overflow=e.dataset.oldOverflow},e.prototype.beforeLeave=function(e){e.dataset||(e.dataset={}),e.dataset.oldPaddingTop=e.style.paddingTop,e.dataset.oldPaddingBottom=e.style.paddingBottom,e.dataset.oldOverflow=e.style.overflow,e.style.height=e.scrollHeight+"px",e.style.overflow="hidden"},e.prototype.leave=function(e){0!==e.scrollHeight&&((0,s.addClass)(e,"collapse-transition"),e.style.height=0,e.style.paddingTop=0,e.style.paddingBottom=0)},e.prototype.afterLeave=function(e){(0,s.removeClass)(e,"collapse-transition"),e.style.height="",e.style.overflow=e.dataset.oldOverflow,e.style.paddingTop=e.dataset.oldPaddingTop,e.style.paddingBottom=e.dataset.oldPaddingBottom},e}();t.default={name:"ElCollapseTransition",functional:!0,render:function(e,t){var i=t.children;return e("transition",{on:new r},i)}}},function(e,t,i){"use strict";t.__esModule=!0;var n=i(165),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";function n(e){return null!==e&&"object"===(void 0===e?"undefined":r(e))&&(0,o.hasOwn)(e,"componentOptions")}function s(e){return e&&e.filter(function(e){return e&&e.tag})[0]}t.__esModule=!0;var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.isVNode=n,t.getFirstComponentChild=s;var o=i(4)},function(e,t){var i=e.exports={version:"2.4.0"};"number"==typeof __e&&(__e=i)},function(e,t,i){var n=i(37);e.exports=function(e){if(!n(e))throw TypeError(e+" is not an object!");return e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t){var i=0,n=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+n).toString(36))}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,i){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}t.__esModule=!0;var s=i(294),r=n(s),o=i(306),a=n(o),l="function"==typeof a.default&&"symbol"==typeof r.default?function(e){return typeof e}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":typeof e};t.default="function"==typeof a.default&&"symbol"===l(r.default)?function(e){return void 0===e?"undefined":l(e)}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":void 0===e?"undefined":l(e)}},function(e,t,i){"use strict";t.__esModule=!0;var n=t.NODE_KEY="$treeNodeId";t.markNodeData=function(e,t){t&&!t[n]&&Object.defineProperty(t,n,{value:e.id,enumerable:!1,configurable:!1,writable:!1})},t.getNodeKey=function(e,t){return e?t[e]:t[n]},t.findNearestComponent=function(e,t){for(var i=e;i&&"BODY"!==i.tagName;){if(i.__vue__&&i.__vue__.$options.name===t)return i.__vue__;i=i.parentNode}return null}},function(e,t,i){"use strict";function n(e){return void 0!==e&&null!==e}function s(e){return/([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi.test(e)}t.__esModule=!0,t.isDef=n,t.isKorean=s},function(e,t,i){"use strict";t.__esModule=!0,t.default=function(){if(s.default.prototype.$isServer)return 0;if(void 0!==r)return r;var e=document.createElement("div");e.className="el-scrollbar__wrap",e.style.visibility="hidden",e.style.width="100px",e.style.position="absolute",e.style.top="-9999px",document.body.appendChild(e);var t=e.offsetWidth;e.style.overflow="scroll";var i=document.createElement("div");i.style.width="100%",e.appendChild(i);var n=i.offsetWidth;return e.parentNode.removeChild(e),r=t-n};var n=i(2),s=function(e){return e&&e.__esModule?e:{default:e}}(n),r=void 0},function(e,t,i){"use strict";function n(e,t){if(!r.default.prototype.$isServer){if(!t)return void(e.scrollTop=0);for(var i=[],n=t.offsetParent;n&&e!==n&&e.contains(n);)i.push(n),n=n.offsetParent;var s=t.offsetTop+i.reduce(function(e,t){return e+t.offsetTop},0),o=s+t.offsetHeight,a=e.scrollTop,l=a+e.clientHeight;sl&&(e.scrollTop=o-e.clientHeight)}}t.__esModule=!0,t.default=n;var s=i(2),r=function(e){return e&&e.__esModule?e:{default:e}}(s)},function(e,t,i){"use strict";t.__esModule=!0;var n=n||{};n.Utils=n.Utils||{},n.Utils.focusFirstDescendant=function(e){for(var t=0;t=0;t--){var i=e.childNodes[t];if(n.Utils.attemptFocus(i)||n.Utils.focusLastDescendant(i))return!0}return!1},n.Utils.attemptFocus=function(e){if(!n.Utils.isFocusable(e))return!1;n.Utils.IgnoreUtilFocusChanges=!0;try{e.focus()}catch(e){}return n.Utils.IgnoreUtilFocusChanges=!1,document.activeElement===e},n.Utils.isFocusable=function(e){if(e.tabIndex>0||0===e.tabIndex&&null!==e.getAttribute("tabIndex"))return!0;if(e.disabled)return!1;switch(e.nodeName){case"A":return!!e.href&&"ignore"!==e.rel;case"INPUT":return"hidden"!==e.type&&"file"!==e.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},n.Utils.triggerEvent=function(e,t){var i=void 0;i=/^mouse|click/.test(t)?"MouseEvents":/^key/.test(t)?"KeyboardEvent":"HTMLEvents";for(var n=document.createEvent(i),s=arguments.length,r=Array(s>2?s-2:0),o=2;o col");if(e.length){var t=this.tableLayout.getFlattenColumns(),i={};t.forEach(function(e){i[e.id]=e});for(var n=0,s=e.length;n col[name=gutter]"),i=0,n=t.length;i0?n:i)(e)}},function(e,t,i){var n=i(56)("keys"),s=i(39);e.exports=function(e){return n[e]||(n[e]=s(e))}},function(e,t,i){var n=i(16),s=n["__core-js_shared__"]||(n["__core-js_shared__"]={});e.exports=function(e){return s[e]||(s[e]={})}},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t){e.exports=!0},function(e,t){e.exports={}},function(e,t,i){var n=i(23).f,s=i(20),r=i(25)("toStringTag");e.exports=function(e,t,i){e&&!s(e=i?e:e.prototype,r)&&n(e,r,{configurable:!0,value:t})}},function(e,t,i){t.f=i(25)},function(e,t,i){var n=i(16),s=i(35),r=i(59),o=i(62),a=i(23).f;e.exports=function(e){var t=s.Symbol||(s.Symbol=r?{}:n.Symbol||{});"_"==e.charAt(0)||e in t||a(t,e,{value:o.f(e)})}},function(e,t,i){"use strict";t.__esModule=!0;var n=i(395),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";t.__esModule=!0,t.default=function(e,t){if(!s.default.prototype.$isServer){var i=function(e){t.drag&&t.drag(e)},n=function e(n){document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",e),document.onselectstart=null,document.ondragstart=null,r=!1,t.end&&t.end(n)};e.addEventListener("mousedown",function(e){r||(document.onselectstart=function(){return!1},document.ondragstart=function(){return!1},document.addEventListener("mousemove",i),document.addEventListener("mouseup",n),r=!0,t.start&&t.start(e))})}};var n=i(2),s=function(e){return e&&e.__esModule?e:{default:e}}(n),r=!1},function(e,t,i){"use strict";t.__esModule=!0;var n=i(100),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=i(114),s=i.n(n),r=i(115),o=i(0),a=o(s.a,r.a,!1,null,null,null);t.default=a.exports},function(e,t){e.exports=function(e,t,i,n){function s(){function s(){o=Number(new Date),i.apply(l,c)}function a(){r=void 0}var l=this,u=Number(new Date)-o,c=arguments;n&&!r&&s(),r&&clearTimeout(r),void 0===n&&u>e?s():!0!==t&&(r=setTimeout(n?a:s,void 0===n?e-u:e))}var r,o=0;return"boolean"!=typeof t&&(n=i,i=t,t=void 0),s}},function(e,t,i){"use strict";t.__esModule=!0;var n=i(67),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";t.__esModule=!0;var n=i(142),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";t.__esModule=!0,t.default={inject:["rootMenu"],computed:{indexPath:function(){for(var e=[this.index],t=this.$parent;"ElMenu"!==t.$options.componentName;)t.index&&e.unshift(t.index),t=t.$parent;return e},parentMenu:function(){for(var e=this.$parent;e&&-1===["ElMenu","ElSubmenu"].indexOf(e.$options.componentName);)e=e.$parent;return e},paddingStyle:function(){if("vertical"!==this.rootMenu.mode)return{};var e=20,t=this.$parent;if(this.rootMenu.collapse)e=20;else for(;t&&"ElMenu"!==t.$options.componentName;)"ElSubmenu"===t.$options.componentName&&(e+=20),t=t.$parent;return{paddingLeft:e+"px"}}}}},function(e,t,i){"use strict";t.__esModule=!0;var n=i(171),s=function(e){return e&&e.__esModule?e:{default:e}}(n);s.default.install=function(e){e.component(s.default.name,s.default)},t.default=s.default},function(e,t,i){"use strict";t.__esModule=!0;var n=i(5);t.default={bind:function(e,t,i){var s=null,r=void 0,o=function(){return i.context[t.expression].apply()},a=function(){new Date-r<100&&o(),clearInterval(s),s=null};(0,n.on)(e,"mousedown",function(e){0===e.button&&(r=new Date,(0,n.once)(document,"mouseup",a),clearInterval(s),s=setInterval(o,100))})}}},function(e,t,i){"use strict";t.__esModule=!0,t.getRowIdentity=t.getColumnByCell=t.getColumnByKey=t.getColumnById=t.orderBy=t.getCell=void 0;var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},s=i(4),r=(t.getCell=function(e){for(var t=e.target;t&&"HTML"!==t.tagName.toUpperCase();){if("TD"===t.tagName.toUpperCase())return t;t=t.parentNode}return null},function(e){return null!==e&&"object"===(void 0===e?"undefined":n(e))}),o=(t.orderBy=function(e,t,i,n,o){if(!t&&!n&&(!o||Array.isArray(o)&&!o.length))return e;i="string"==typeof i?"descending"===i?-1:1:i&&i<0?-1:1;var a=n?null:function(i,n){return o?(Array.isArray(o)||(o=[o]),o.map(function(t){return"string"==typeof t?(0,s.getValueByPath)(i,t):t(i,n,e)})):("$key"!==t&&r(i)&&"$value"in i&&(i=i.$value),[r(i)?(0,s.getValueByPath)(i,t):i])},l=function(e,t){if(n)return n(e.value,t.value);for(var i=0,s=e.key.length;it.key[i])return 1}return 0};return e.map(function(e,t){return{value:e,index:t,key:a?a(e,t):null}}).sort(function(e,t){var n=l(e,t);return n||(n=e.index-t.index),n*i}).map(function(e){return e.value})},t.getColumnById=function(e,t){var i=null;return e.columns.forEach(function(e){e.id===t&&(i=e)}),i});t.getColumnByKey=function(e,t){for(var i=null,n=0;nl;)n(a,i=t[l++])&&(~r(u,i)||u.push(i));return u}},function(e,t,i){var n=i(82);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==n(e)?e.split(""):Object(e)}},function(e,t){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,t,i){var n=i(53);e.exports=function(e){return Object(n(e))}},function(e,t,i){"use strict";var n=i(59),s=i(51),r=i(85),o=i(22),a=i(20),l=i(60),u=i(298),c=i(61),d=i(301),h=i(25)("iterator"),f=!([].keys&&"next"in[].keys()),p=function(){return this};e.exports=function(e,t,i,m,v,g,b){u(i,t,m);var y,_,C,x=function(e){if(!f&&e in M)return M[e];switch(e){case"keys":case"values":return function(){return new i(this,e)}}return function(){return new i(this,e)}},w=t+" Iterator",k="values"==v,S=!1,M=e.prototype,$=M[h]||M["@@iterator"]||v&&M[v],E=$||x(v),D=v?k?x("entries"):E:void 0,T="Array"==t?M.entries||$:$;if(T&&(C=d(T.call(new e)))!==Object.prototype&&(c(C,w,!0),n||a(C,h)||o(C,h,p)),k&&$&&"values"!==$.name&&(S=!0,E=function(){return $.call(this)}),n&&!b||!f&&!S&&M[h]||o(M,h,E),l[t]=E,l[w]=p,v)if(y={values:k?E:x("values"),keys:g?E:x("keys"),entries:D},b)for(_ in y)_ in M||r(M,_,y[_]);else s(s.P+s.F*(f||S),t,y);return y}},function(e,t,i){e.exports=i(22)},function(e,t,i){var n=i(36),s=i(299),r=i(57),o=i(55)("IE_PROTO"),a=function(){},l=function(){var e,t=i(79)("iframe"),n=r.length;for(t.style.display="none",i(300).appendChild(t),t.src="javascript:",e=t.contentWindow.document,e.open(),e.write("
输入发送消息:
================================================ FILE: SpringCloud-Mybatis/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Mybatis 4.2.1.RELEASE 1.8 1.1.7 1.7.5 2.3.3 UTF-8 org.mybatis.generator mybatis-generator-core 1.3.2 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 1.2.0 org.projectlombok lombok 1.16.18 true org.apache.maven.plugins maven-compiler-plugin 3.1 ${java.version} ${java.version} ${project.build.sourceEncoding} true 128m 256m org.mybatis.generator mybatis-generator-maven-plugin 1.3.2 src/main/resources/generatorConfig.xml true true mysql mysql-connector-java 8.0.28 com.purcotton 0.0.1 purcotton-mybatis-generator ================================================ FILE: SpringCloud-Mybatis/src/main/java/com/xiao/springcloud/mybatis/generator/plugin/LombokPlugin.java ================================================ package com.xiao.springcloud.mybatis.generator.plugin; import org.mybatis.generator.api.IntrospectedColumn; import org.mybatis.generator.api.IntrospectedTable; import org.mybatis.generator.api.PluginAdapter; import org.mybatis.generator.api.dom.java.Field; import org.mybatis.generator.api.dom.java.Interface; import org.mybatis.generator.api.dom.java.Method; import org.mybatis.generator.api.dom.java.TopLevelClass; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; public class LombokPlugin extends PluginAdapter { @Override public boolean validate(List list) { return true; } @Override public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) { //添加domain的import topLevelClass.addImportedType("lombok.Data"); topLevelClass.addImportedType("lombok.Builder"); topLevelClass.addImportedType("lombok.NoArgsConstructor"); topLevelClass.addImportedType("lombok.AllArgsConstructor"); //添加domain的注解 topLevelClass.addAnnotation("@Data"); topLevelClass.addAnnotation("@Builder"); topLevelClass.addAnnotation("@NoArgsConstructor"); topLevelClass.addAnnotation("@AllArgsConstructor"); //添加domain的注释 topLevelClass.addJavaDocLine("/**"); topLevelClass.addJavaDocLine("* Created by Mybatis Generator on " + date2Str(new Date())); topLevelClass.addJavaDocLine("*/"); return true; } @Override public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) { //Mapper文件的注释 interfaze.addJavaDocLine("/**"); interfaze.addJavaDocLine("* Created by Mybatis Generator on " + date2Str(new Date())); interfaze.addJavaDocLine("*/"); return true; } @Override public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { //不生成getter return false; } @Override public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { //不生成setter return false; } @Override public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) { field.addJavaDocLine("//" + introspectedColumn.getRemarks()); return true; } private String date2Str(Date date) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); return sdf.format(date); } } ================================================ FILE: SpringCloud-Mybatis/src/main/resources/generatorConfig.xml ================================================ ================================================ FILE: SpringCloud-Provider/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-Provider org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-stream-binder-kafka org.springframework.cloud spring-cloud-sleuth-stream com.google.guava guava 29.0-jre org.springframework.boot spring-boot-starter-cache de.codecentric spring-boot-admin-starter-client 1.5.6 org.springframework.boot spring-boot-starter-actuator org.jolokia jolokia-core ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Provider/src/main/java/com/xiao/skywalking/provider/ProviderApp.java ================================================ /* * Winner * 文件名 :ProviderApp.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @SpringBootApplication // @EnableEurekaClient 表明自己是一个eurekaclient @EnableEurekaClient //开启springboot-cache @EnableCaching public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class, args); } } ================================================ FILE: SpringCloud-Provider/src/main/java/com/xiao/skywalking/provider/controller/SkywalkingController.java ================================================ /* * Winner * 文件名 :SkywalkingController.java * 创建人 :llxiao * 创建时间:2018年3月30日 */ package com.xiao.skywalking.provider.controller; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年3月30日 */ @RestController public class SkywalkingController { @RequestMapping(path = "/skywalking") // cacheable注解,一般建议放在service层 @Cacheable(value = "skywalking", key = "'skywalking'.concat({#hello})", sync = true) public String skywalking(@RequestParam("hello") String hello) { return hello; } } ================================================ FILE: SpringCloud-Provider/src/main/java/com/xiao/skywalking/provider/local/cache/SpringGuavaCacheConfig.java ================================================ package com.xiao.skywalking.provider.local.cache; import com.google.common.cache.CacheBuilder; import org.springframework.cache.CacheManager; import org.springframework.cache.guava.GuavaCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; /** * [简要描述]: guava+springcache实现本地缓存 * [详细描述]: * * @author llxiao * @version 1.0, 2018/9/29 17:28 * @since JDK 1.8 */ @Configuration public class SpringGuavaCacheConfig { @Bean public CacheManager cacheManager() { GuavaCacheManager cacheManager = new GuavaCacheManager(); cacheManager // 3S过期时间,初始容量1000个,最大10000个 .setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).initialCapacity(1000) .maximumSize(10000)); return cacheManager; } } ================================================ FILE: SpringCloud-Provider/src/main/resources/application.yml ================================================ #注册中心 eureka: client: serviceUrl: defaultZone: http://localhost:1111/eureka/ server: port: 1112 #服务名称 spring: application: name: provider-1112 # #服务链路跟踪到zipkin服务 # zipkin: # base-url: http://localhost:1115 ## zipkin 链路跟踪配置 sleuth: enabled: true #采样率,越高会有性能影响 sampler: percentage: 1.0 cloud: ## kafka zk配置 配合zipkin stream: kafka: binder: brokers: 192.168.206.203:9092 zkNodes: 192.168.206.203:2181 #springboot-admin 1.5.6集成使用 boot: admin: auto-registration: true # springboot admin的地址 url: http://localhost:8080 api-path: instances client: prefer-ip: true #不使用鉴权 management: security: enabled: false ================================================ FILE: SpringCloud-Quartz-JobService/README.md ================================================ **SpringCloud + Quartz 实现任务调度,可配置,可通过API操作** 1. 实现思路:
主要是通过``SchedulerFactoryBean``将Quartz 集成到spring容器中,然后开放API,注册到eureka上
[参考文章1](https://blog.csdn.net/pengjunlee/article/details/78965877)
[参考文章2](https://blog.csdn.net/beliefer/article/details/51578546)
2. TaskSchedulerFactory 用于接入quartz到spring容器中
JobConfig 初始化SchedulerFactoryBean和Scheduler
JobManager 动态添加、删除、停止、恢复定时任务
ServiceTaskExecuteJob 主要业务逻辑实现,可在该实现类中调用定时调用远程方法
================================================ FILE: SpringCloud-Quartz-JobService/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Quartz-JobService 2.3.2 2.13.4.1 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.cloud spring-cloud-starter-eureka junit junit org.projectlombok lombok provided org.springframework.boot spring-boot-starter-aop org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-config org.apache.commons commons-lang3 3.6 org.quartz-scheduler quartz ${quartz.version} org.springframework.boot spring-boot-starter-cache org.apache.curator curator-framework 2.8.0 com.alibaba fastjson 1.2.83 org.apache.httpcomponents httpclient 4.5.13 com.fasterxml.jackson.core jackson-databind ${jackson.version} com.fasterxml.jackson.dataformat jackson-dataformat-xml ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 ${jackson.version} ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/JobServiceApplication.java ================================================ package com.xiao.springcloud.job; import org.springframework.boot.SpringApplication; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/10 11:10 * @since JDK 1.8 */ public class JobServiceApplication { public static void main(String[] args) { SpringApplication.run(JobServiceApplication.class, args); } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/config/JobConfig.java ================================================ package com.xiao.springcloud.job.config; import org.quartz.Scheduler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; @Configuration public class JobConfig { @Autowired private TaskSchedulerFactory taskSchedulerFactory; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean bean = new SchedulerFactoryBean(); bean.setJobFactory(taskSchedulerFactory); return bean; } @Bean public Scheduler scheduler() { return schedulerFactoryBean().getScheduler(); } // @Bean // public HttpClient httpClient() // { // HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); // return httpClientBuilder.build(); // } // // @Bean // public ClientHttpRequestFactory clientHttpRequestFactory() // { // return new HttpComponentsClientHttpRequestFactory(httpClient()); // } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/config/TaskSchedulerFactory.java ================================================ package com.xiao.springcloud.job.config; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.AdaptableJobFactory; import org.springframework.stereotype.Component; /** * 自定义的 TaskSchedulerFactory * @Date: 2018/11/2 16:08 * @Description: */ @Component public class TaskSchedulerFactory extends AdaptableJobFactory { /** * 需要使用这个BeanFactory对Qurartz创建好Job实例进行后续处理. */ @Autowired private AutowireCapableBeanFactory capableBeanFactory; @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { // 首先,调用父类的方法创建好Quartz所需的Job实例 Object jobInstance = super.createJobInstance(bundle); // 然后,使用BeanFactory为创建好的Job实例进行属性自动装配并将其纳入到Spring容器的管理之中,属于Spring的技术范畴. capableBeanFactory.autowireBean(jobInstance); return jobInstance; } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/entity/TaskConfigDocument.java ================================================ package com.xiao.springcloud.job.entity; import lombok.Data; import lombok.ToString; @Data @ToString public class TaskConfigDocument { private String id; /** 任务说明 **/ private String description; /** 是否锁定(0:否;1:是) **/ private Integer isLock = 0; /** 任务名称 **/ private String name; /** 任务执行URL **/ private String url; /** 模块 ModelEnum定义 **/ private String module; /** * 运行状态: 0 停止 1运行 2删除 */ private Integer status; /** 创建人 **/ private String creator; /** 创建时间 **/ private String createTime; /** 更新时间 **/ private String updateTime; /** 最后更新人 **/ private String updater; /** 运行频次 **/ private String cronExp; /** 是否自动启动:0否 1是 **/ private Integer autoStart; /** 最新启动时间:启动时候设置**/ private String latestStartTime; /** 最后运行时间:停止时候设置**/ private String lastRunTime; } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/job/ServiceTaskExecuteJob.java ================================================ package com.xiao.springcloud.job.job; import com.xiao.springcloud.job.entity.TaskConfigDocument; import com.xiao.springcloud.job.quartz.JobManager; import org.quartz.*; /** * [简要描述]: 具体任务实现 * [详细描述]: * * @author llxiao * @version 1.0, 2018/12/10 11:53 * @since JDK 1.8 */ public class ServiceTaskExecuteJob implements Job { /** *

* Called by the {@link Scheduler} when a {@link Trigger} * fires that is associated with the Job. *

* *

* The implementation may wish to set a * {@link JobExecutionContext#setResult(Object) result} object on the * {@link JobExecutionContext} before this method exits. The result itself * is meaningless to Quartz, but may be informative to * {@link JobListener}s or * {@link TriggerListener}s that are watching the job's * execution. *

* * @param context * @exception JobExecutionException if there is an exception while executing the job. */ @Override public void execute(JobExecutionContext context) throws JobExecutionException { TaskConfigDocument task = (TaskConfigDocument) context.getMergedJobDataMap().get(JobManager.JOB_KEY); // 具体业务逻辑 } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/quartz/JobManager.java ================================================ package com.xiao.springcloud.job.quartz; import com.xiao.springcloud.job.entity.TaskConfigDocument; import com.xiao.springcloud.job.job.ServiceTaskExecuteJob; import com.xiao.springcloud.job.util.CronExpUtil; import org.quartz.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.stereotype.Service; /** * [简要描述]:任务管理
* [详细描述]:
* llxiao 2018/12/10 - 13:47 **/ @Service public class JobManager implements InitializingBean { public static final String JOB_KEY = "SCHEDULED_JOB"; private static Logger logger = LoggerFactory.getLogger(JobManager.class); @Autowired private SchedulerFactoryBean schedulerFactoryBean; /** * 添加任务 * * @param task */ public void addJob(TaskConfigDocument task) { boolean cronExp = CronExpUtil.validator(task.getCronExp()); if (cronExp) { try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); logger.info("add job " + task.getName()); TriggerKey triggerKey = TriggerKey.triggerKey(task.getName(), task.getModule()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); if (trigger == null) { JobDetail jobDetail = JobBuilder.newJob(ServiceTaskExecuteJob.class) .withIdentity(task.getName(), task.getModule()).build(); jobDetail.getJobDataMap().put(JOB_KEY, task); //表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCronExp()); //按新的cronExpression表达式构建一个新的trigger trigger = TriggerBuilder.newTrigger().withIdentity(task.getName(), task.getModule()) .withSchedule(scheduleBuilder).build(); scheduler.scheduleJob(jobDetail, trigger); } else { //if (!trigger.getCronExpression().equals(task.getCronExp())) { // Trigger已存在,那么更新相应的定时设置 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCronExp()); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder) .build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } logger.info("add job " + task.getName() + " finished"); } catch (Exception e) { logger.error("add job '{}' error: {}", task.getName(), e.getMessage()); throw new RuntimeException(e); } } else { // 表达式非法或者已过期 直接停止or删除 this.pauseJob(task); // this.deleteJob(task); } } /** * 停止任务 * * @param task */ public void pauseJob(TaskConfigDocument task) { try { logger.info("stop job " + task.getName()); Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(task.getName(), task.getModule()); scheduler.pauseJob(jobKey); logger.info("stop job " + task.getName() + " finished"); } catch (SchedulerException e) { throw new RuntimeException(e); } } /** * 恢复任务 * * @param task */ public void resumeJob(TaskConfigDocument task) { try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(task.getName(), task.getModule()); scheduler.resumeJob(jobKey); } catch (Exception e) { throw new RuntimeException(e); } } /** * 删除任务 * * @param task */ public void deleteJob(TaskConfigDocument task) { try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(task.getName(), task.getModule()); scheduler.deleteJob(jobKey); } catch (Exception e) { throw new RuntimeException(e); } } /** * 立即执行 * * @param task */ public void runNow(TaskConfigDocument task) { try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); JobKey jobKey = JobKey.jobKey(task.getName(), task.getModule()); scheduler.triggerJob(jobKey); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void afterPropertiesSet() throws Exception { // 系统启动读取所有待启动的,然后执行启动任务 //读取所有的taskConfig addJob // addJob(task); } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/java/com/xiao/springcloud/job/util/CronExpUtil.java ================================================ package com.xiao.springcloud.job.util; import org.apache.commons.lang3.StringUtils; import org.quartz.CronExpression; import org.quartz.CronTrigger; import org.quartz.impl.triggers.CronTriggerImpl; import java.text.ParseException; import java.util.Date; /** * [简要描述]: quartz表达式校验 * [详细描述]: * * @author llxiao * @version 1.0, 2019/7/2 18:52 * @since JDK 1.8 */ public class CronExpUtil { /** * [简要描述]:Quartz 表达式校验
* [详细描述]:
* 1. 校验表达式格式

* 2. 校验表达式合法性,以及是否可用

* * @param cronExp : * @return boolean * llxiao 2019/7/2 - 18:53 **/ public static boolean validator(String cronExp) { boolean flag = false; if (StringUtils.isNotEmpty(cronExp)) { // 校验表达式的格式是否正确 flag = CronExpression.isValidExpression(cronExp); if (flag) { CronTrigger cronTrigger = new CronTriggerImpl(); try { // 校验 表达式是否有效。比如过期的,失效的等 ((CronTriggerImpl) cronTrigger).setCronExpression(cronExp); Date date = ((CronTriggerImpl) cronTrigger).computeFirstFireTime(null); flag = date != null && date.after(new Date()); } catch (ParseException e) { flag = false; } } } return flag; } } ================================================ FILE: SpringCloud-Quartz-JobService/src/main/resources/application.yml ================================================ server: port: 6666 ================================================ FILE: SpringCloud-Quartz-JobService/src/main/resources/bootstrap.yml ================================================ spring: #配置中心 #cloud: #config: #uri: #profile: @env@ #label: master #name: job-servcie application: name: job-servcie ================================================ FILE: SpringCloud-Redisson/README.md ================================================ spring data 集成redisson,使用redisTemplate
参考[Redisson官方文档](https://github.com/redisson/redisson/tree/master/redisson-spring-data) ================================================ FILE: SpringCloud-Redisson/pom.xml ================================================ 4.0.0 com.purcotton.cache redisson-test 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.redisson redisson-spring-data-18 3.10.0 org.redisson redisson 3.10.0 ================================================ FILE: SpringCloud-Redisson/src/main/java/com/xiao/spring/cloud/redisson/SpringDataRedissonApplication.java ================================================ package com.xiao.spring.cloud.redisson; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/19 09:48 * @since JDK 1.8 */ @SpringBootApplication public class SpringDataRedissonApplication { public static void main(String[] args) { SpringApplication.run(SpringDataRedissonApplication.class, args); } } ================================================ FILE: SpringCloud-Redisson/src/main/java/com/xiao/spring/cloud/redisson/config/SpringDataRedissonConfig.java ================================================ package com.xiao.spring.cloud.redisson.config; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.data.redis.core.RedisTemplate; import java.io.IOException; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/19 09:48 * @since JDK 1.8 */ @Configuration public class SpringDataRedissonConfig { @Bean public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) { return new RedissonConnectionFactory(redisson); } @Bean(destroyMethod = "shutdown") public RedissonClient redisson(@Value("classpath:/redisson.yml") Resource configFile) throws IOException { Config config = Config.fromYAML(configFile.getInputStream()); return Redisson.create(config); } @Bean public RedisTemplate redisTemplate(RedissonConnectionFactory redissonConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redissonConnectionFactory); // 自定义的各种序列化 // redisTemplate.setKeySerializer(stringRedisSerializer); // redisTemplate.setValueSerializer(jdkSerializationRedisSerializer); // redisTemplate.setHashKeySerializer(stringRedisSerializer); // redisTemplate.setHashValueSerializer(stringRedisSerializer); return redisTemplate; } } ================================================ FILE: SpringCloud-Redisson/src/main/resources/application.yml ================================================ ================================================ FILE: SpringCloud-Redisson/src/main/resources/redisson.yml ================================================ # 线程池数量,默认值: 当前处理核数量 * 2 ##threads: 0 # Netty线程池数量,默认值: 当前处理核数量 * 2 ##nettyThreads: 0 # Redisson的对象编码类是用于将对象进行序列化和反序列化,默认:JsonJacksonCodec ##codec: ! {} # 传输模式 默认NIO # 可选参数: # TransportMode.NIO, # TransportMode.EPOLL - 需要依赖里有netty-transport-native-epoll包(Linux) # TransportMode.KQUEUE - 需要依赖里有 netty-transport-native-kqueue包(macOS) ##transportMode: "NIO" # 监控锁的看门狗超时,单位:毫秒,默认3000ms.该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况 ##lockWatchdogTimeout: 3000 # 保持订阅发布顺序 ##keepPubSubOrder: true # 单节点模式 singleServerConfig: idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 10000 timeout: 3000 retryAttempts: 3 retryInterval: 1500 reconnectionTimeout: 3000 failedAttempts: 3 subscriptionsPerConnection: 5 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 connectionMinimumIdleSize: 4 connectionPoolSize: 8 database: 12 address: redis://192.168.206.204:6379 dnsMonitoringInterval: 5000 ================================================ FILE: SpringCloud-Redisson/src/test/java/com/xiao/spring/cloud/redisson/SpringDataRedissonApplicationTest.java ================================================ package com.xiao.spring.cloud.redisson; import org.junit.Assert; 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.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/1/19 09:50 * @since JDK 1.8 */ @RunWith(SpringRunner.class) @SpringBootTest public class SpringDataRedissonApplicationTest { @Autowired private RedisTemplate redisTemplate; @Test public void testSimple() { redisTemplate.opsForValue().set("test", "test11111"); // System.out.println(redisTemplate.opsForValue().get("test")); Assert.assertEquals("Spring data 集成Redisson测试失败!", "test11111", redisTemplate.opsForValue().get("test")); } @Test public void testBatch() { RedisSerializer keySer = redisTemplate.getKeySerializer(); RedisSerializer valueSer = redisTemplate.getValueSerializer(); RedisSerializer hashKeySer = redisTemplate.getHashKeySerializer(); RedisSerializer hasValSer = redisTemplate.getHashValueSerializer(); // 不带返回值使用pipeline 关键是使用第三个参数 pipeline redisTemplate.execute(redisConnection -> { redisConnection.set(keySer.serialize("test"), valueSer.serialize("hello pipeline")); return null; }, true, true); // 带返回值的 List list = redisTemplate.executePipelined((RedisCallback) redisConnection -> { byte[] value = redisConnection.get(keySer.serialize("test")); return (String) valueSer.deserialize(value); }); } } ================================================ FILE: SpringCloud-SearchService/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-SearchService org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.cloud spring-cloud-starter-sleuth org.projectlombok lombok true net.logstash.logback logstash-logback-encoder 4.11 com.github.danielwegener logback-kafka-appender 0.1.0 runtime com.alibaba fastjson 1.2.83 org.elasticsearch.client transport ${elasticsearch.version} org.apache.commons commons-lang3 3.4 org.apache.logging.log4j log4j-api 2.17.1 org.apache.logging.log4j log4j-core 2.17.1 org.apache.commons commons-lang3 3.6 com.google.guava guava 29.0-jre org.springframework.boot spring-boot-starter-cache cn.hutool hutool-core 4.1.12 com.xiao.skywalking.demo SpringCloud-Common 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-test test org.mockito mockito-all 1.10.19 test ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/SearchApplication.java ================================================ package com.xiao.spring.cloud.search; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * [简要描述]:主启动类 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 15:07 * @since JDK 1.8 */ @SpringBootApplication @EnableEurekaClient public class SearchApplication { public static void main(String[] args) { SpringApplication.run(SearchApplication.class, args); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/ElasticSearchDoc.java ================================================ /* * Winner * 文件名 :ElasticSearchDoc.java * 创建人 :llxiao * 创建时间:2018年1月11日 */ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.Date; /** * [简要描述]:Elastic search 存储文档
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月11日 * @since Purcotton-Search B01 */ @Data public class ElasticSearchDoc { /** * 文档id */ private String id; /** * 关键字(商品表的卖点+货品表的商品标题+货品表的规格) */ private String keyWords; /** * 商品全局唯一SPU */ private String commodityNo; /** * 商品业务SPU编码 */ private String commodityCode; /** * 默认SKU编号(全局唯一) */ private String defProdNo; /** * 默认SKU业务编码 */ private String defProdCode; /** * 品牌编号 */ private String brandNo; /** * 品牌名称 */ private String brandName; /** * 标题,商品标题,SKU名称 */ private String title; /** * 子标题(卖点) */ private String subTitle; private String skuTitle; /** * 扩展属性(JSON列表存储(组-名称-值)) */ private String extProps; /** * JSON列表存储(SKU规格属性) */ private String skuProps; /** * 产地 */ private String productArea; /** * 牌价 */ private Double orgPrice; /** * 售价(售价由调价刷新) */ private Double salePrice; /** * 商品运营分类编号(多个用逗号隔开) */ private String oprtCatNo; /** * 运营分类名称(以"-"隔开,多个以","隔开) */ private String oprtCatName; /** * 标签集合 * JSON列表存储(名称-颜色) */ private String labels; /** * 上架时间 */ private Date saleTime; /** * 商品首图 */ private String picUrl; /** * 库存(库存定时刷新) */ private Long stock; /** * 是否海淘(0否,1是) */ private Integer haitao; /** * 销量 */ private Long salesVolume; /** * 索引名称/或店铺名称 */ private String index; //*****************************以下是预留字段**************************** /** * 商品分类编号 */ private String commoCatNo; /** * 新品 1新品,0默认 */ private Integer newly = 0; /** * 评论数量 */ private Integer comments; /** * 活动属性 折扣信息 */ private String discountRate; //****************************用来聚合的字段*********************************** //保存三级分类 private String categoryName; //JSON列表存储 private String defSkuProp; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/PaginationDo.java ================================================ /* * Winner * 文件名 :PaginationDo.java * 创建人 :llxiao * 创建时间:2018年1月12日 */ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.List; /** * [简要描述]:查询结果实体类
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月12日 * @since Purcotton-Search B01 */ @Data public class PaginationDo { /** * 查询数据结果 */ private List results; /** * 总数 */ private int total; /** * 页显示数量 */ private int pageSize; /** * 当前页数 */ private int pageNo; /** * 查询所用时间(单位:毫秒) */ private long took; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchBrandDo.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; /** * [简要描述]: ES品牌 * [详细描述]: * * @author mjye * @version 1.0, 2019-02-28 15:06 * @since JDK 1.8 */ @Data public class SearchBrandDo { /** * 品牌编号 */ private String brandNo; /** * 品牌名称 */ private String brandName; /** * 品牌图片URL */ private String brandUrl; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchCategoryDo.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; /** * [简要描述]: 分类 * [详细描述]: 聚合菜单使用 * * @author mjye * @version 1.0, 2019-03-02 14:29 * @since JDK 1.8 */ @Data public class SearchCategoryDo { private String categoryName; private String categoryNo; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchCommoPropOptionDto.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; /** * [简要描述]: 商品详情页规格 * [详细描述]: * * @author mjye * @version 1.0, 2019-02-23 16:45 * @since JDK 1.8 */ @Data public class SearchCommoPropOptionDto { //属性选项编号 private String optionNo; //属性选项值名称 private String optionNoName; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchCommodityPropertyDo.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.Set; /** * [简要描述]: ES商品属性 * [详细描述]: * * @author mjye * @version 1.0, 2019-02-23 14:45 * @since JDK 1.8 */ @Data public class SearchCommodityPropertyDo { //属性编码 private String propertyNo; //属性名称(颜色、尺码等) private String propertyName; //属性选项 private Set setList; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchCommodityResultDo.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.List; /** * [简要描述]: 查询商品返回组装结果 * [详细描述]: * * @author mjye * @version 1.0, 2019-03-01 14:17 * @since JDK 1.8 */ @Data public class SearchCommodityResultDo { private List commodityList; /** * 总数 */ private int total; /** * 页显示数量 */ private int pageSize; /** * 当前页数 */ private int pageNo; /** * 总页数 */ private int pageTotal; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchLogDo.java ================================================ /* * Winner * 文件名 :SearchLogDo.java * 创建人 :llxiao * 创建时间:2018年2月23日 */ package com.xiao.spring.cloud.search.dto; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.time.LocalDateTime; /** * [简要描述]:搜索日志数据
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月23日 * @since Purcotton-Search B01 */ public class SearchLogDo { /** * 正常搜索 */ public static final int SUCCESS_SEARCH = 0; /** * 没有结果的搜索 */ public static final int NOT_RESULT_SEARCH = 1; /** * 错误的搜索 */ public static final int ERROR_RESULT_SEARCH = 2; /** * 未分词 */ public static final int UN_PARTICIPLE = 0; /** * IK_SMART分词 */ public static final int IK_SMART = 1; /** * IK_MAX_WORD分词 */ public static final int IK_MAX_WORD = 2; /** * ES默认分词,全拆 */ public static final int ES_DEFAULT = 3; /** * 主键 */ private Long id; /** * 搜索关键字 */ private String keyWords; /** * 分词关键字 */ private String participle; /** * 分词状态,0未分词1标准分词2普通分词 */ private int participleStats; /** * 搜索查询语句 */ private String searchQueryDSL; /** * 搜索返回结果 */ private String result; /** * 搜索状态0成功1失败 */ private int searchStats; /** * 搜索开始时间 */ private LocalDateTime startTime; /** * 搜索结束时间 */ private LocalDateTime endTime; /** * 搜索花费时间 */ private Long costTime; /** * 异常信息 */ private String exceptionMsg; /** * 请求参数 */ private String request; /** * 返回id属性 * * @return id属性 */ public Long getId() { return id; } /** * 设置id属性 * * @param id * id属性 */ public void setId(Long id) { this.id = id; } /** * 返回keyWords属性 * * @return keyWords属性 */ public String getKeyWords() { return keyWords; } /** * 设置keyWords属性 * * @param keyWords * keyWords属性 */ public void setKeyWords(String keyWords) { this.keyWords = keyWords; } /** * 返回result属性 * * @return result属性 */ public String getResult() { return result; } /** * 设置result属性 * * @param result * result属性 */ public void setResult(String result) { this.result = result; } /** * 返回searchStats属性 * * @return searchStats属性 */ public int getSearchStats() { return searchStats; } /** * 设置searchStats属性 * * @param searchStats * searchStats属性 */ public void setSearchStats(int searchStats) { this.searchStats = searchStats; } /** * 返回startTime属性 * * @return startTime属性 */ public LocalDateTime getStartTime() { return startTime; } /** * 设置startTime属性 * * @param startTime * startTime属性 */ public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; } /** * 返回endTime属性 * * @return endTime属性 */ public LocalDateTime getEndTime() { return endTime; } /** * 设置endTime属性 * * @param endTime * endTime属性 */ public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; } /** * 返回costTime属性 * * @return costTime属性 */ public Long getCostTime() { return costTime; } /** * 设置costTime属性 * * @param costTime * costTime属性 */ public void setCostTime(Long costTime) { this.costTime = costTime; } /** * 返回searchQueryDSL属性 * * @return searchQueryDSL属性 */ public String getSearchQueryDSL() { return searchQueryDSL; } /** * 设置searchQueryDSL属性 * * @param searchQueryDSL * searchQueryDSL属性 */ public void setSearchQueryDSL(String searchQueryDSL) { this.searchQueryDSL = searchQueryDSL; } /** * 返回exceptionMsg属性 * * @return exceptionMsg属性 */ public String getExceptionMsg() { return exceptionMsg; } /** * 设置exceptionMsg属性 * * @param exceptionMsg * exceptionMsg属性 */ public void setExceptionMsg(String exceptionMsg) { this.exceptionMsg = exceptionMsg; } /** * 返回request属性 * * @return request属性 */ public String getRequest() { return request; } /** * 设置request属性 * * @param request * request属性 */ public void setRequest(String request) { this.request = request; } /** * 返回participle属性 * * @return participle属性 */ public String getParticiple() { return participle; } /** * 设置participle属性 * * @param participle * participle属性 */ public void setParticiple(String participle) { this.participle = participle; } /** * 返回participleStats属性 * * @return participleStats属性 */ public int getParticipleStats() { return participleStats; } /** * 设置participleStats属性 * * @param participleStats * participleStats属性 */ public void setParticipleStats(int participleStats) { this.participleStats = participleStats; } /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @return * @see * Object#toString() */ @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchMenusDo.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.List; import java.util.Set; /** * [简要描述]:C端筛选聚合菜单 * [详细描述]: * * @author mjye * @version 1.0, 2019-02-23 10:46 * @since JDK 1.8 */ @Data public class SearchMenusDo { /** * 品牌 **/ private Set brandName; /** * 运营分类 */ private Set oprtCatName; /** * 价格区间 */ private List price; /** * SKU规格属性 */ private Set skuProps; /** * 商品扩展属性 */ private Set extProps; /** * 查询所用时间(单位:毫秒) */ private long took; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchRequestDo.java ================================================ /* * Winner * 文件名 :SearchRequestDo.java * 创建人 :llxiao * 创建时间:2018年1月15日 */ package com.xiao.spring.cloud.search.dto; import lombok.Data; import java.util.List; /** * [简要描述]:请求查询接口参数
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月15日 * @since Purcotton-Search B01 */ @Data public class SearchRequestDo { /** * 关键字 */ private String keyWords; /** * 页数,默认1 */ private Integer pageNo = 1; /** * 也显示条数 10条 */ private Integer pageSize = 10; /** * 排序字段0默认排序,1价格排序,2销量排序,3好评度排序,4上架时间排序 */ private Integer sortFeild = 0; /** * 新品 0否,1是 -1默认不处理 */ private Integer newly = -1; /** * 有货 0有,1无 -1默认不处理 */ private Integer hasStock = -1; /** * 运营分类编号 */ private List categoryNo; /** * 分类编号 * 仅用于从前端分类进入时使用 */ private String oprtCatNo; /** * 排序类型,默认升序ASC * 0: 升序 * 1:降序 */ private int sort = 0; /** * 价格区间 * 小-大,小-大 */ private List rangesPrices; /** * 索引名称 */ private String index; /** * 品牌编码 **/ private List brandNo; /** * SKU规格属性 */ private List skuPropsNo; /** * 商品扩展属性 */ private List extPropsNo; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchResultDo.java ================================================ /* * Winner * 文件名 :SearchResponse.java * 创建人 :llxiao * 创建时间:2018年1月11日 */ package com.xiao.spring.cloud.search.dto; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; /** * [简要描述]:搜索结果
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月11日 * @since Purcotton-Search B01 */ public class SearchResultDo { /** * 返回文档 */ private ElasticSearchDoc doc; /** * 标题高亮字段 */ private String highlightTitle; /** * 子标题高亮字段 */ private String highlightSubTitle; /** * [简要描述]:替换返回的索引文档中高亮字段
* [详细描述]:
* * @return */ public SearchResultDo setHighlight() { if (StringUtils.isNotBlank(highlightTitle)) { doc.setTitle(highlightTitle); } if (StringUtils.isNoneBlank(highlightSubTitle)) { doc.setSubTitle(highlightSubTitle); } return this; } /** * 返回doc属性 * * @return doc属性 */ public ElasticSearchDoc getDoc() { return doc; } /** * 设置doc属性 * * @param doc doc属性 */ public void setDoc(ElasticSearchDoc doc) { this.doc = doc; } /** * 返回highlightTitle属性 * * @return highlightTitle属性 */ public String getHighlightTitle() { return highlightTitle; } /** * 设置highlightTitle属性 * * @param highlightTitle highlightTitle属性 */ public void setHighlightTitle(String highlightTitle) { this.highlightTitle = highlightTitle; } /** * 返回highlightSubTitle属性 * * @return highlightSubTitle属性 */ public String getHighlightSubTitle() { return highlightSubTitle; } /** * 设置highlightSubTitle属性 * * @param highlightSubTitle highlightSubTitle属性 */ public void setHighlightSubTitle(String highlightSubTitle) { this.highlightSubTitle = highlightSubTitle; } /** * [简要描述]:
* [详细描述]:
* * @return * @see Object#toString() */ @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/SearchShopWeightDto.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * [简要描述]: * [详细描述]: * * @author mjye * @version 1.0, 2019-02-25 17:24 * @since JDK 1.8 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class SearchShopWeightDto { // id主键 private long id; // 店铺编码 private String shopCode; // 权重因子 private Integer weightFactor; // 权重因子名称 private String weightName; private String englishName; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/dto/ShopPriceRangeDto.java ================================================ package com.xiao.spring.cloud.search.dto; import lombok.Data; /** * [简要描述]:ES店铺商品价格区间 * [详细描述]: * * @author mjye * @version 1.0, 2019-02-23 18:45 * @since JDK 1.8 */ @Data public class ShopPriceRangeDto implements Comparable { private Long id; //店铺编码 private String shopCode; //最低价格 private String floorPrice; //最高价格 private String highestPrice; //价格区间 private String priceRange; //状态 1:启用 2:禁用 默认禁用 private Integer status; //店铺名称 private String shopName; @Override public int compareTo(ShopPriceRangeDto o) { return Integer.parseInt(this.getFloorPrice()) - Integer.parseInt(this.getFloorPrice()); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/client/ElasticSearchClient.java ================================================ /* * Winner * 文件名 :ElasticSearchClient.java * 创建人 :llxiao * 创建时间:2018年1月11日 */ package com.xiao.spring.cloud.search.es.client; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.transport.client.PreBuiltTransportClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; /** * [简要描述]:ElasticSearch client类
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月11日 * @since Purcotton-Search B01 */ @Component public class ElasticSearchClient { /** * 日志记录器 */ private static final Log LOG = LogFactory.getLog(ElasticSearchClient.class); /** * ip端口组成数组长度 */ private static final int IP_AND_PORT_ARRAY_LENGTH = 2; /** * 集群配置名称 */ private static final String ES_CLUSTER_NAME_CONF = "cluster.name"; /** * 自动嗅探配置 */ private static final String ES_SNIFF_CONF = "client.transport.sniff"; /** * es集群主机 */ @Value("${elastic.search.host}") private String esHosts; /** * 集群名称 */ @Value("${elastic.search.cluster.name}") private String clusterName; /** * es 客户端 Transport Client */ private TransportClient transoportClient; /** * client初始化是否OK */ private boolean isOk = false; /** * [简要描述]:服务停止,关闭客户端
* [详细描述]:
* * @exception Exception */ @PreDestroy public void destroy() { LOG.warn("Close elastic search transoport Client beacuase purcotton search server stoped!"); if (isOk) { transoportClient.close(); } } /** * [简要描述]:初始化ES client
* [详细描述]:
* * @exception UnknownHostException */ @SuppressWarnings("resource") @PostConstruct public void init() throws UnknownHostException { if (StringUtils.isNotBlank(esHosts) || esHosts.split(":").length == IP_AND_PORT_ARRAY_LENGTH) { if (LOG.isInfoEnabled()) { LOG.info("ElasticSearch Host configuration:" + esHosts); } String[] ipHost = esHosts.split(":"); // 设置ES实例的名称 Settings esSettings = Settings.builder().put(ES_CLUSTER_NAME_CONF, clusterName) // 自动嗅探整个集群的状态,把集群中其他ES节点的ip添加到本地的客户端列表中 .put(ES_SNIFF_CONF, true).build(); TransportAddress transportAddress = new TransportAddress(new InetSocketAddress(InetAddress .getByName(ipHost[0]), Integer.parseInt(ipHost[1]))); transoportClient = new PreBuiltTransportClient(esSettings).addTransportAddress(transportAddress); isOk = true; } else { LOG.error("Wrong Host configuration for ElasticSearch!"); } } /** * [简要描述]:get TransportClient
* [详细描述]:
* * @return */ public TransportClient getTransportClient() { return this.transoportClient; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/common/AnalyzeType.java ================================================ /* * Winner * 文件名 :AnalyzeType.java * 创建人 :llxiao * 创建时间:2018年1月13日 */ package com.xiao.spring.cloud.search.es.common; /** * [简要描述]:分析器类型
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月13日 * @since Purcotton-Search B01 */ public enum AnalyzeType { /** * IK中文标准分词器,eg:我是中国人 -->"我","是","中国人" */ IK_SMART("ik_smart"), /** * IM中文最大化分词,eg:我是中国人 --> "我","是","中国","中国人" */ IK_MAX_WORD("ik_max_word"), /** * ES默认中文分词器,会将中文分词成一个一个。eg:中国 --> "中","国" */ STANDARD("standard"); private String type; private AnalyzeType(String type) { this.type = type; } /** * 返回type属性 * * @return type属性 */ public String getType() { return type; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/common/ESConstants.java ================================================ /* * Winner * 文件名 :ESConstants.java * 创建人 :llxiao * 创建时间:2018年1月11日 */ package com.xiao.spring.cloud.search.es.common; /** * [简要描述]:ES常量池
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月11日 * @since Purcotton-Search B01 */ public interface ESConstants { /** * 集群配置名称 */ String ES_CLUSTER_NAME_CONF = "cluster.name"; /** * 自动嗅探配置 */ String ES_SNIFF_CONF = "client.transport.sniff"; String IDNEX_DEV_TYPE = "devType"; /** * 分页最大数量 100 */ int MAX_PAGE_SIZE = 100; /** * 获取所有的数量,最大值 */ int GET_ALL_SIZE = 2000; /** * 新品 */ int NEWLY_PRODUCT = 1; /** * 普通 */ int NORMAL_PRODUCT = 0; /** * 常量 0 */ int CONSTANT_ZERO = 0; /** * 常量 1 */ int CONSTANT_ONE = 1; /** * 常量 2 */ int CONSTANT_TWO = 2; /** * 无货 */ int NOT_HAVE_STOCK = 1; /** * 无效,不处理标志 */ int INVALID_FLAG = -1; /** * IK maxword分词器 */ int IK_ANALYZE_TYPE = 1; /** * ES 默认分词器 */ int STANDARD = 0; /** * 将序排序 */ int DESC_SORT = 1; /****************************** ES 字段常量 start **************************/ /** * 标题字段 */ String ES_TITLE_FEILD = "title"; /** * 子标题字段 */ String ES_SUBTITLE_FEILD = "subTitle"; /** * 运营类型 */ String ES_OPRTCATNO_FEILD = "oprtCatName"; /** * 商品品牌 */ String ES_BRAND_NO = "brandNo"; String ES_BRAND_NAME="brandName"; /** * 商品(SPU)编码 */ String ES_COMMODITY_NO = "commodityNo"; /** * 商品标题 */ String ES_TITLE = "title"; /** * 商品卖点 */ String ES_SUB_TITLE = "subTitle"; /** * 运营分类各层级名称 */ String ES_OPRT_CAT_NAME="oprtCatName"; /** * 商品标签 */ String ES_LABELS = "labels"; /** * SKU规格属性 */ String ES_SKU_PROPS = "skuProps"; /** * 商品扩展属性 */ String ES_EXT_PROPS = "extProps"; /** * 货品产地 */ String ES_PRODUCT_AREA = "productArea"; /** * 索引关键字 */ String ES_KEY_WORDS = "keyWords"; /** * 商品分类名称 */ String ES_CATEGORY_NAME = "categoryName"; /** * 商品分类编号 */ String ES_CATEGORY_NO = "oprtCatNo"; /** * 商品编号 */ String COMMON_NO = "commodityNo"; /** * 默认货品编码 */ String DEFPRODNO = "defProdNo"; /** * 库存过滤字段 */ String HAS_STOCK = "hasStock"; /** * 新品字段 */ String NEWLY = "newly"; /** * 售价 */ String SALE_PRICE = "salePrice"; /** * 原价 */ String OLD_PRICE = "oldPrice"; /** * 销量字段 */ String SALES_VOLUME = "salesVolume"; /** * 上架时间字段 */ String SALE_TIME = "saleTime"; /** * 评论数字段 */ String COMMENTS = "comments"; /** * 销售标签名称 */ String SALES_TAG_NAME = "salesTagName"; /****************************** ES 字段常量 end **************************/ /** * 是否使用ES * "on"表示使用ES * "off"表示使用solr */ String USE_ELASTIC_SEARCH = "useElasticSearch"; /** * 分词阈值 搜索到指定值不进行拆词 */ String WORD_SEGMENTATION_THRESHOLD = "thresholdTotal"; /** * ES热词 缓存key */ String ES_IK_HOT_WORDS_KEY = "esIKHotWords"; /** * 关闭elasticsearch 使用solr */ String OFF_ELASTIC_SEARCH = "off"; /** * 开启elasticsearch */ String ON_ELASTIC_SEARCH = "on"; } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/common/OrderField.java ================================================ /* * Winner * 文件名 :OrderField.java * 创建人 :llxiao * 创建时间:2018年1月15日 */ package com.xiao.spring.cloud.search.es.common; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年1月15日 * @since Purcotton-Search B01 */ public enum OrderField { /** * 默认排序 */ DEFAULT(0), /** * 价格排序 */ PRICE(1), /** * 销售排序 */ SALES(2), /** * 好评度排序 */ COMMENTS(3), /** * 上架时间排序 */ SLAESTIME(4); private int type; private OrderField(int type) { this.type = type; } /** * 返回type属性 * * @return type属性 */ public int getType() { return type; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/common/SearchException.java ================================================ package com.xiao.spring.cloud.search.es.common; import com.xiao.springcloud.demo.common.exception.AbstractServiceException; /** * [简要描述]: 搜索服务异常枚举 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/9 10:12 * @since JDK 1.8 */ public enum SearchException implements AbstractServiceException { SUCCESS(0, "成功!"), PARAM_IS_NULL(780000, "参数为空"), NOT_FOUND_DOC(780001, "商品找不到!"), ; SearchException(Integer code, String message) { this.code = code; this.message = message; } /** * 错误码 */ private Integer code; /** * 错误消息 */ private String message; /** * 获取异常的状态码 */ @Override public Integer getCode() { return code; } /** * 获取异常的提示信息 */ @Override public String getMessage() { return message; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/ISearchLogService.java ================================================ /* * Winner * 文件名 :SearchLogService.java * 创建人 :llxiao * 创建时间:2018年2月23日 */ package com.xiao.spring.cloud.search.es.log; import com.xiao.spring.cloud.search.dto.SearchLogDo; /** * [简要描述]:搜索日志服务
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月23日 * @since Purcotton-Search B01 */ public interface ISearchLogService { /** * [简要描述]:添加一个搜索日志
* [详细描述]:
* * @author llxiao * @param searchLog * 搜索日志 */ void addSearchLog(SearchLogDo searchLog); } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/impl/SearchLogServiceImpl.java ================================================ /* * Winner * 文件名 :SearchLogService.java * 创建人 :llxiao * 创建时间:2018年2月23日 */ package com.xiao.spring.cloud.search.es.log.impl; import com.xiao.spring.cloud.search.dto.SearchLogDo; import com.xiao.spring.cloud.search.es.log.ISearchLogService; import com.xiao.spring.cloud.search.es.log.thread.BatchSaveSearchLogThread; import com.xiao.spring.cloud.search.es.log.thread.SearchThreadFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * [简要描述]:搜索日志服务实现类
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月23日 * @since Purcotton-Search B01 */ @Service public class SearchLogServiceImpl implements ISearchLogService { /** * 日志记录 */ private static final Log LOG = LogFactory.getLog(SearchLogServiceImpl.class); /** * 最大1千个有界队列 */ private final ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1000); /** * 批处理数量 */ private static final int BATCH_SIZE = 20; /** * 定时执行任务 */ private ScheduledExecutorService schedule = new ScheduledThreadPoolExecutor(5, new SearchThreadFactory()); @PostConstruct public void init() { // 这个是按照固定的时间来执行,简单来说:到点执行 // initialDelay延迟时间 period周期时间 每个60秒 延迟10秒执行一个线程 schedule.scheduleAtFixedRate(new BatchSaveSearchLogThread(queue, BATCH_SIZE), 10, 60, TimeUnit.SECONDS); // scheduleWithFixedDelay,执行完上一个任务后再执行 } /** * [简要描述]:添加一个搜索日志
* [详细描述]:
* * @param searchLog * @see */ @Override public void addSearchLog(SearchLogDo searchLog) { if (!queue.offer(searchLog)) { // 饱和策略 LOG.warn("日志队列已满..................."); } } /** * [简要描述]:销毁线程池
* [详细描述]:
*/ @PreDestroy public void destroy() { schedule.shutdown(); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/thread/BatchSaveSearchLogThread.java ================================================ /* * Winner * 文件名 :SaveSearchLogThread.java * 创建人 :llxiao * 创建时间:2018年2月23日 */ package com.xiao.spring.cloud.search.es.log.thread; import com.xiao.spring.cloud.search.dto.SearchLogDo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; /** * [简要描述]:批处理搜索日志
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月23日 * @since Purcotton-Search B01 */ public class BatchSaveSearchLogThread implements Runnable { private static final Log LOG = LogFactory.getLog(BatchSaveSearchLogThread.class); /** * 搜索日志 */ private static final Log SEARCH_LOG = LogFactory.getLog("searchLog"); private ArrayBlockingQueue queue; private int batchSize; public BatchSaveSearchLogThread(ArrayBlockingQueue queue, int batchSize) { this.queue = queue; this.batchSize = batchSize; } @Override public void run() { if (null != queue && !queue.isEmpty()) { if (LOG.isInfoEnabled()) { LOG.info("Batch process search log!.........."); } // 队里处理日志 processQueue(); } } /** * [简要描述]:队里处理日志
* [详细描述]:
*/ private void processQueue() { try { List logs = new ArrayList<>(batchSize); SearchLogDo log = queue.poll(); if (queue.size() <= batchSize) { while (null != log) { logs.add(log); log = queue.poll(); } } else { int i = 0; while (null != log && i < batchSize) { logs.add(log); log = queue.poll(); i++; } } if (null != SEARCH_LOG) { processLog(logs); } } catch (RuntimeException e) { // 线程内部捕获RuntimeException异常,记录异常日志 LOG.error(Thread.currentThread().getName() + " RuntimeException", e); } } /** * [简要描述]:批量日志处理
* [详细描述]:
* * @param logs 批量日志 */ private void processLog(List logs) { for (SearchLogDo searchLogDo : logs) { SEARCH_LOG.info(searchLogDo); } } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/thread/SearchLogThread.java ================================================ /* * Winner * 文件名 :SearchLogThread.java * 创建人 :llxiao * 创建时间:2018年2月24日 */ package com.xiao.spring.cloud.search.es.log.thread; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.concurrent.atomic.AtomicInteger; /** * [简要描述]:定制线程,指定线程名称,设置未捕获异常,维护一些统计信息
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月24日 * @since Purcotton-Search B01 */ public class SearchLogThread extends Thread { private static final Log LOG = LogFactory.getLog(SearchLogThread.class); /** * 创建线程数 */ private static final AtomicInteger CREATED = new AtomicInteger(); /** * 运行线程数 */ private static final AtomicInteger ALIVE = new AtomicInteger(); public SearchLogThread(Runnable r, String threadName) { super(r, threadName + '-' + CREATED.incrementAndGet()); setUncaughtExceptionHandler((t, e) -> LOG.error(t.getName() + " UncaughtExceptionHandler", e)); } @Override public void run() { if (LOG.isDebugEnabled()) { LOG.debug("Create thread:" + getName()); } try { ALIVE.incrementAndGet(); super.run(); } finally { ALIVE.decrementAndGet(); if (LOG.isDebugEnabled()) { LOG.debug("Exit thread:" + getName()); } } } /** * 返回created属性 * * @return created属性 */ public static AtomicInteger getCreated() { return CREATED; } /** * 返回alive属性 * * @return alive属性 */ public static AtomicInteger getAlive() { return ALIVE; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/thread/SearchLogThreadPool.java ================================================ /* * Winner * 文件名 :SearchLogThreadPool.java * 创建人 :llxiao * 创建时间:2018年2月24日 */ package com.xiao.spring.cloud.search.es.log.thread; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; /** * [简要描述]:定制线程池
* [详细描述]:通过扩展beforeExcute、afterExcute、terminated方法添加线程池的统计和监控功能
* * @author llxiao * @version 1.0, 2018年2月24日 * @since Purcotton-Search B01 */ public class SearchLogThreadPool extends ThreadPoolExecutor { /** * 日志记录器 */ private static final Log LOG = LogFactory.getLog(SearchLogThreadPool.class); /** * 每个线程存储一个开始时间 */ private final ThreadLocal startTime = new ThreadLocal<>(); /** * 已处理任务数量 */ private final AtomicLong numTasks = new AtomicLong(); /** * 总处理时间 */ private final AtomicLong totalTime = new AtomicLong(); /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @param corePoolSize * @param maximumPoolSize * @param keepAliveTime * @param unit * @param workQueue */ public SearchLogThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @param corePoolSize * @param maximumPoolSize * @param keepAliveTime * @param unit * @param workQueue * @param threadFactory */ public SearchLogThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); } /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @param corePoolSize * @param maximumPoolSize * @param keepAliveTime * @param unit * @param workQueue * @param handler */ public SearchLogThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @param corePoolSize * @param maximumPoolSize * @param keepAliveTime * @param unit * @param workQueue * @param threadFactory * @param handler */ public SearchLogThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } /** * [简要描述]:线程执行之前调用方法
* [详细描述]:出现RuntimeException时不会调用此方法
* * @author llxiao * @param t * @param r * @see ThreadPoolExecutor#beforeExecute(Thread, * Runnable) */ @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); if (LOG.isDebugEnabled()) { LOG.debug(t.getName() + " starting..."); } startTime.set(System.nanoTime()); } /** * [简要描述]:线程执行之后调用方法
* [详细描述]:run正常运行完成或抛出异常会调用该方法,但出现ERROR不会执行此方法
* * @author llxiao * @param r * @param t * @see ThreadPoolExecutor#afterExecute(Runnable, * Throwable) */ @Override protected void afterExecute(Runnable r, Throwable t) { try { long endTime = System.nanoTime(); long taskTime = endTime - startTime.get(); totalTime.addAndGet(taskTime); numTasks.incrementAndGet(); if (LOG.isDebugEnabled()) { LOG.debug("Thread " + r + " end and cost time:" + taskTime); LOG.debug("Throwable:" + t); } } finally { super.afterExecute(r, t); } } /** * [简要描述]:线程池完成关闭时调用方法
* [详细描述]:可用于统计、关闭资源、完成发送通知等,计算线程平均执行时间
* * @author llxiao * @see ThreadPoolExecutor#terminated() */ @Override protected void terminated() { try { LOG.info("Thread terminated: avg time=" + (totalTime.get() / numTasks.get())); } finally { super.terminated(); } } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/log/thread/SearchThreadFactory.java ================================================ /* * Winner * 文件名 :SearchThreadFactory.java * 创建人 :llxiao * 创建时间:2018年2月23日 */ package com.xiao.spring.cloud.search.es.log.thread; import java.util.concurrent.ThreadFactory; /** * [简要描述]:线程工厂
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年2月23日 * @since Purcotton-Search B01 */ public class SearchThreadFactory implements ThreadFactory { private static final String SEARCH_LOG_THRED = "Search-log-thread-"; @Override public Thread newThread(Runnable r) { SearchLogThread t = new SearchLogThread(r, SEARCH_LOG_THRED); return t; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/service/SearchManagerEsImpl.java ================================================ package com.xiao.spring.cloud.search.es.service; import com.alibaba.fastjson.JSON; import com.xiao.springcloud.demo.common.exception.CommonException; import com.xiao.spring.cloud.search.dto.ElasticSearchDoc; import com.xiao.spring.cloud.search.es.client.ElasticSearchClient; import com.xiao.spring.cloud.search.es.common.ESConstants; import com.xiao.spring.cloud.search.es.common.SearchException; import com.xiao.spring.cloud.search.service.SearchManangerService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequestBuilder; import org.elasticsearch.action.get.GetRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.util.*; /** * [简要描述]: 搜索文档管理服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 13:57 * @since JDK 1.8 */ @Slf4j @Service public class SearchManagerEsImpl implements SearchManangerService, ESConstants { /** * es客户端 */ @Autowired private ElasticSearchClient esClient; /** * [简要描述]:创建索引和默认mapping
* [详细描述]:
* * @param index : 索引名称 * @return true成功 * llxiao 2018/10/17 - 19:47 **/ @Override public boolean createIndexMapping(String index) { if (StringUtils.isBlank(index)) { log.error("Create index and mapping error! index name:{}", index); return false; } if (this.existsIndex(index)) { this.deleteIndex(index); } TransportClient client = esClient.getTransportClient(); CreateIndexRequestBuilder cib = client.admin().indices().prepareCreate(index); try { XContentBuilder mapping = createMapping(); cib.addMapping(index, mapping).execute().actionGet(); return true; } catch (Exception e) { log.error("Create index and mapping error! index name:{}", index, e); } return false; } /** * 创建mapping * * @return * @exception IOException */ private XContentBuilder createMapping() throws IOException { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("properties"); mapping.startObject("id").field("type", "text").endObject(); mapping.startObject("keyWords").field("type", "text").endObject(); mapping.startObject("commodityNo").field("type", "keyword").endObject(); mapping.startObject("commodityCode").field("type", "keyword").endObject(); mapping.startObject("defProdNo").field("type", "keyword").endObject(); mapping.startObject("defProdCode").field("type", "keyword").endObject(); mapping.startObject("brandNo").field("type", "keyword").endObject(); //fielddata 解决字段既可以聚合也可以分词查询问题 mapping.startObject("brandName").field("type", "text").field("analyzer", "ik_max_word").field("fielddata", true) .startObject("fields").startObject("raw").field("type", "keyword").endObject().endObject().endObject(); mapping.startObject("title").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("subTitle").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("extProps").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("skuProps").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("orgPrice").field("type", "double").endObject(); mapping.startObject("salePrice").field("type", "double").endObject(); mapping.startObject("productArea").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("oprtCatNo").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("oprtCatName").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("labels").field("type", "text").field("analyzer", "ik_max_word").endObject(); mapping.startObject("saleTime").field("type", "date").endObject(); mapping.startObject("picUrl").field("type", "keyword").endObject(); mapping.startObject("stock").field("type", "integer").endObject(); mapping.startObject("haitao").field("type", "integer").endObject(); mapping.startObject("salesVolume").field("type", "long").endObject(); mapping.startObject("commoCatNo").field("type", "keyword").endObject(); mapping.startObject("newly").field("type", "integer").endObject(); mapping.startObject("comments").field("type", "long").endObject(); mapping.startObject("discountRate").field("type", "keyword").endObject(); mapping.startObject("categoryName").field("type", "keyword").endObject(); mapping.startObject("defSkuProp").field("type", "keyword").endObject(); mapping.endObject().endObject(); return mapping; } /** * [简要描述]:索引是否存在
* [详细描述]:
* * @param index : 索引名称 * @return boolean * llxiao 2018/10/18 - 8:35 **/ @Override public boolean existsIndex(String index) { IndicesExistsRequest inExistsRequest = new IndicesExistsRequest(index); TransportClient client = esClient.getTransportClient(); IndicesExistsResponse inExistsResponse = client.admin().indices().exists(inExistsRequest).actionGet(); return inExistsResponse.isExists(); } /** * [简要描述]:删除索引
* [详细描述]:
* * @param index : 索引名称 * @return boolean * llxiao 2018/10/18 - 8:35 **/ @Override public boolean deleteIndex(String index) { boolean flag = false; if (StringUtils.isNotBlank(index)) { DeleteIndexRequestBuilder deleteIndexRequestBuilder = esClient.getTransportClient().admin().indices() .prepareDelete(index); if (null != deleteIndexRequestBuilder) { DeleteIndexResponse dResponse = deleteIndexRequestBuilder.execute().actionGet(); flag = dResponse.isAcknowledged(); } } return flag; } /** * [简要描述]:根据id查找doc
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 索引文档 */ @Override public ElasticSearchDoc getEsDocById(String id, String index) { if (StringUtils.isBlank(id) || StringUtils.isBlank(index)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "查询失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); GetRequestBuilder prepareGet = client.prepareGet(index, IDNEX_DEV_TYPE, id); Map map = prepareGet.get().getSourceAsMap(); return JSON.parseObject(JSON.toJSONString(map), ElasticSearchDoc.class); } /** * [简要描述]:获取指定索引下的所有文档
* [详细描述]:
* * @param index 索引名称 * @return 所有文档 */ @Override public List getAllDos(String index) { List esDocList = new ArrayList<>(); TransportClient client = esClient.getTransportClient(); long totalHits = client.prepareSearch(index).setTypes(IDNEX_DEV_TYPE).setQuery(QueryBuilders.matchAllQuery()) .get().getHits().getTotalHits(); int totalSize = (int) (totalHits / GET_ALL_SIZE) + 1; SearchResponse searchResponse; SearchHits hits; SearchHit[] searchHits; for (int i = 0; i < totalSize; i++) { searchResponse = client.prepareSearch(index).setTypes(IDNEX_DEV_TYPE) .setQuery(QueryBuilders.matchAllQuery()).setSearchType(SearchType.QUERY_THEN_FETCH) .setFrom(GET_ALL_SIZE * i).setSize(GET_ALL_SIZE).addSort(COMMON_NO, SortOrder.DESC).get(); hits = searchResponse.getHits(); searchHits = hits.getHits(); for (SearchHit searchHit : searchHits) { esDocList.add(JSON.parseObject(searchHit.getSourceAsString(), ElasticSearchDoc.class)); } } return esDocList; } /** * [简要描述]:根据defProdNo查找doc
* [详细描述]:
* * @param defProdNo 产品ID * @param index 索引名称 * @return 产品信息 */ @Override public ElasticSearchDoc getEsDocByDefProdNo(String defProdNo, String index) { if (StringUtils.isBlank(defProdNo) || StringUtils.isBlank(index)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "查询失败,索引名称和货品编号不能为空!"); } List esDocList = new ArrayList<>(); TransportClient client = esClient.getTransportClient(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); QueryBuilder accurateQuery = QueryBuilders.matchQuery(ESConstants.DEFPRODNO, defProdNo); searchBuilder.setQuery(queryBuilder.must(accurateQuery)); SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); String searchSource; for (SearchHit searchHit : hits) { searchSource = searchHit.getSourceAsString(); esDocList.add(JSON.parseObject(searchSource, ElasticSearchDoc.class)); } if (esDocList.size() == 1) { return esDocList.get(0); } return new ElasticSearchDoc(); } /** * [简要描述]:根据商品编号查询出商品
* [详细描述]:
* * @param commoNo 商品ID * @param index 索引名称 * @return 商品信息 */ @Override public List getEsDocByCommoNo(String commoNo, String index) { List esDocList = new ArrayList<>(); if (StringUtils.isBlank(commoNo) || StringUtils.isBlank(index)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "查询失败,索引名称和商品编号不能为空!"); } TransportClient client = esClient.getTransportClient(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); QueryBuilder accurateQuery = QueryBuilders.matchQuery(ESConstants.COMMON_NO, commoNo); searchBuilder.setQuery(queryBuilder.must(accurateQuery)); SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); String searchSource; for (SearchHit searchHit : hits) { searchSource = searchHit.getSourceAsString(); esDocList.add(JSON.parseObject(searchSource, ElasticSearchDoc.class)); } return esDocList; } /** * [简要描述]:添加单个
* [详细描述]:
* * @param doc 文档 * @return 添加状态 */ @Override public boolean addData(ElasticSearchDoc doc) { if (doc == null || StringUtils.isBlank(doc.getId()) || StringUtils.isBlank(doc.getIndex())) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "添加失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); IndexRequestBuilder irb = client.prepareIndex(doc.getIndex(), IDNEX_DEV_TYPE, doc.getId()); irb.setSource(JSON.parseObject(JSON.toJSONString(doc), Map.class)); bulkRequest.add(irb); BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); return false; } return true; } /** * [简要描述]:批量添加数据
* [详细描述]: * * @param docs 索引文档集 * @return 更新结果状态 */ @Override public boolean addDatas(List docs) { if (CollectionUtils.isEmpty(docs)) { log.error("Parameter is null in ManagerService addDatas() method"); return false; } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); String index; IndexRequestBuilder irb; for (ElasticSearchDoc esDoc : docs) { index = esDoc.getIndex(); if (StringUtils.isBlank(index) || StringUtils.isBlank(esDoc.getId())) { continue; } irb = client.prepareIndex(index, IDNEX_DEV_TYPE, esDoc.getId()); irb.setSource(JSON.parseObject(JSON.toJSONString(esDoc), Map.class)); bulkRequest.add(irb); } BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); return false; } return true; } /** * [简要描述]:ID单个更新
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ @Override public boolean updateData(ElasticSearchDoc doc) { if (doc == null || StringUtils.isBlank(doc.getId()) || StringUtils.isBlank(doc.getIndex())) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "更新失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); UpdateRequestBuilder urb = client.prepareUpdate(doc.getIndex(), IDNEX_DEV_TYPE, doc.getId()); urb.setDoc(JSON.parseObject(JSON.toJSONString(doc), Map.class)); bulkRequest.add(urb); BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); } return true; } /** * [简要描述]:批量更新
* [详细描述]:
* * @param docs 索引文档集 * @return 更新状态 */ @Override public boolean updateDatas(List docs) { if (CollectionUtils.isEmpty(docs)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "批量添加失败,商品不能为空"); } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); UpdateRequestBuilder urb; String index; for (ElasticSearchDoc esDoc : docs) { index = esDoc.getIndex(); if (StringUtils.isBlank(index) || StringUtils.isBlank(esDoc.getId())) { continue; } urb = client.prepareUpdate(index, IDNEX_DEV_TYPE, esDoc.getId()); urb.setDoc(JSON.parseObject(JSON.toJSONString(esDoc), Map.class)); bulkRequest.add(urb); } BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); } return true; } /** * [简要描述]:通过id修改单个文档
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ @Override public boolean updateDataById(ElasticSearchDoc doc) { if (null == doc || StringUtils.isBlank(doc.getId()) || StringUtils.isBlank(doc.getIndex())) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "更新失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); if (null != client) { ElasticSearchDoc esDocById = getEsDocById(doc.getId(), doc.getIndex()); Map docMap = JSON.parseObject(JSON.toJSONString(doc), Map.class); Map oldDoc = JSON.parseObject(JSON.toJSONString(esDocById), Map.class); if (docMap.containsKey(ESConstants.SALES_TAG_NAME)) { // 标签更新处理 updateTagNames(oldDoc, docMap); } else { oldDoc.putAll(docMap); } BulkRequestBuilder bulkRequest = client.prepareBulk(); UpdateRequestBuilder urb = client.prepareUpdate(doc.getIndex(), IDNEX_DEV_TYPE, doc.getId()); urb.setDoc(oldDoc); bulkRequest.add(urb); BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); return false; } return true; } return false; } /** * [简要描述]:根据默认货品编码修改
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ @Override public boolean updateDataByDefProdNo(ElasticSearchDoc doc) { if (null == doc || StringUtils.isBlank(doc.getId()) || StringUtils.isBlank(doc.getIndex())) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "更新失败,索引名称和文档ID不能为空!"); } ElasticSearchDoc esDocById = getEsDocByDefProdNo(doc.getId(), doc.getIndex()); if (null == esDocById) { throw new CommonException(SearchException.NOT_FOUND_DOC.getCode(), "更新失败,更新的文档不存在!"); } Map oldMap = JSON.parseObject(JSON.toJSONString(esDocById), Map.class); oldMap.putAll(JSON.parseObject(JSON.toJSONString(doc), Map.class)); String keyId = doc.getId(); TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); UpdateRequestBuilder urb = client.prepareUpdate(doc.getIndex(), IDNEX_DEV_TYPE, keyId); urb.setDoc(oldMap); bulkRequest.add(urb); BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); return false; } return true; } /** * [简要描述]:批量更新库存状态 * [详细描述]:
* * @param allDocs 索引文档集 * @return 更新状态 */ @Override public boolean updateHasStock(List allDocs) { if (CollectionUtils.isEmpty(allDocs)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "批量更新库存失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); UpdateRequestBuilder urb; for (ElasticSearchDoc doc : allDocs) { urb = client.prepareUpdate(doc.getIndex(), IDNEX_DEV_TYPE, doc.getId()); urb.setDoc(doc); bulkRequest.add(urb); } BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); } return true; } /** * [简要描述]:根据id单个删除
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 删除状态 */ @Override public boolean deleteData(String id, String index) { if (StringUtils.isBlank(id) || StringUtils.isBlank(index)) { throw new CommonException(SearchException.PARAM_IS_NULL.getCode(), "删除失败,索引名称和文档ID不能为空!"); } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); DeleteRequestBuilder drb = client.prepareDelete(index, IDNEX_DEV_TYPE, id); bulkRequest.add(drb); BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + "fail Id:" + id); } return true; } /** * [简要描述]:批量删除
* [详细描述]:
* * @param docs 索引ID和索引名称集 * @return 删除状态 */ @Override public boolean deleteDatas(List docs) { if (CollectionUtils.isEmpty(docs)) { log.error("Parameter is null in ManagerService deleteDatas() method"); return false; } TransportClient client = esClient.getTransportClient(); BulkRequestBuilder bulkRequest = client.prepareBulk(); DeleteRequestBuilder drb; for (ElasticSearchDoc doc : docs) { drb = client.prepareDelete(doc.getIndex(), IDNEX_DEV_TYPE, doc.getId()); bulkRequest.add(drb); } BulkResponse bulkResponse = bulkRequest.get(); if (bulkResponse.hasFailures()) { log.error(bulkResponse.getTook() + ""); return false; } return true; } /** * [简要描述]:根据商品编号删除
* [详细描述]:
* * @param commoNo 商品编号 * @param index 索引名称 * @return 删除状态 */ @Override public boolean deleteDatasByCommoNo(String commoNo, String index) { return false; } private void updateTagNames(Map esDocById, Map docMap) { String desTags = (String) docMap.remove(ESConstants.SALES_TAG_NAME); if (StringUtils.isNotBlank(desTags)) { List desTagNames = JSON.parseArray(desTags, String.class); Set dts = new HashSet<>(desTagNames); esDocById.put(ESConstants.SALES_TAG_NAME, JSON.toJSONString(dts)); } else { esDocById.put(ESConstants.SALES_TAG_NAME, ""); } esDocById.putAll(docMap); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/es/service/SearchServiceEsImpl.java ================================================ package com.xiao.spring.cloud.search.es.service; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSON; import com.xiao.spring.cloud.search.dto.*; import com.xiao.spring.cloud.search.es.client.ElasticSearchClient; import com.xiao.spring.cloud.search.es.common.AnalyzeType; import com.xiao.spring.cloud.search.es.common.ESConstants; import com.xiao.spring.cloud.search.es.common.OrderField; import com.xiao.spring.cloud.search.es.log.ISearchLogService; import com.xiao.spring.cloud.search.service.SearchService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.WeightBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.BucketOrder; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.time.Duration; import java.time.LocalDateTime; import java.util.*; /** * [简要描述]: 搜索服务ES实现 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 11:01 * @since JDK 1.8 */ @Service @Slf4j public class SearchServiceEsImpl implements SearchService, ESConstants { /** * 日志记录器 */ private static final Log LOG = LogFactory.getLog(SearchServiceEsImpl.class); @Autowired private ElasticSearchClient esClient; @Autowired private ISearchLogService searchLogService; @Value("${elasticsearch.highlight.pretags}") private String preTags; @Value("${elasticsearch.highlight.posttags}") private String postTags; @Value("${elasticsearch.total}") private int searchTotal; /** * [简要描述]: 因子的权重默认值 * [详细描述]: 当后台对因子未设置权重时,该因子的权重设置为默认值0 **/ private static int weight = 0; /** * 日志返回结果最大长度取值 */ private static final int RESULT_MAX_LENGTH = 5; /** * 分析器 0ES默认分词器,1IK中文分词器 */ @Value("${elasticsearch.analyze.type}") private int analyzeType = 0; /** * [简要描述]:搜索
* [详细描述]:
* * @param searchRequestDo : * @return PaginationDo * llxiao 2018/10/8 - 11:07 **/ @Override public SearchCommodityResultDo search(SearchRequestDo searchRequestDo) { if (LOG.isDebugEnabled() && null != searchRequestDo) { LOG.debug("Start search data for ES with search params:" + searchRequestDo); } // 对空值置为null LocalDateTime startTime = LocalDateTime.now(); SearchLogDo searchLog = new SearchLogDo(); searchLog.setStartTime(startTime); PaginationDo page = new PaginationDo(); TransportClient client = esClient.getTransportClient(); if (null != searchRequestDo && StringUtils.isNotBlank(searchRequestDo.getIndex()) && null != client) { // 处理查询 processSearch(searchRequestDo, page, client); } else { // 防止调用者空指针 page.setResults(new ArrayList<>()); LOG.error("搜索失败,请求参数及索引及EsClient不能为空!"); } processSearchLog(startTime, searchLog, page); return this.paginationDo2CommdityList(page); } @Override @Cacheable(value = "searchMenu", key = "'searchMenu'.concat({#searchRequestDo.index}).concat({#searchRequestDo.keyWords}).concat({#searchRequestDo.oprtCatNo})") public SearchMenusDo searchMenu(SearchRequestDo searchRequestDo) { if (LOG.isDebugEnabled() && null != searchRequestDo) { LOG.debug("Start search data for ES with search params:" + searchRequestDo); } SearchMenusDo menu = new SearchMenusDo(); TransportClient client = esClient.getTransportClient(); if (null != searchRequestDo && StringUtils.isNotBlank(searchRequestDo.getIndex()) && null != client) { // 处理查询 processMenu(searchRequestDo, menu, client); } else { LOG.error("搜索失败,请求参数及索引及EsClient不能为空!"); } return menu; } @Override public long commodityTotal(SearchRequestDo searchRequestDo) { if (LOG.isDebugEnabled() && null != searchRequestDo) { LOG.debug("Start search data for ES with search params:" + searchRequestDo); } LocalDateTime startTime = LocalDateTime.now(); SearchLogDo searchLog = new SearchLogDo(); searchLog.setStartTime(startTime); TransportClient client = esClient.getTransportClient(); Long commodityTotal; // 对空值置为null if (null != searchRequestDo && StringUtils.isNotBlank(searchRequestDo.getIndex()) && null != client) { // 处理查询 commodityTotal = processCommodityTotal(searchRequestDo, client); } else { commodityTotal = 0L; LOG.error("搜索失败,请求参数及索引及EsClient不能为空!"); } processSearchLog(startTime, searchLog, null); return commodityTotal; } private SearchCommodityResultDo paginationDo2CommdityList(PaginationDo page) { SearchCommodityResultDo searchCommodityResultDo = null; List commodityList; if (null != page) { commodityList = new ArrayList<>(); searchCommodityResultDo = new SearchCommodityResultDo(); searchCommodityResultDo.setPageNo(page.getPageNo()); searchCommodityResultDo.setPageSize(page.getPageSize()); searchCommodityResultDo.setTotal(page.getTotal()); for (SearchResultDo result : page.getResults()) { result.getDoc().setExtProps(""); result.getDoc().setSkuProps(""); commodityList.add(result.getDoc()); } searchCommodityResultDo.setCommodityList(commodityList); } searchCommodityResultDo.setPageTotal( (searchCommodityResultDo.getTotal() / searchCommodityResultDo.getPageSize()) + CONSTANT_ONE); return searchCommodityResultDo; } private void processSearchLog(LocalDateTime startTime, SearchLogDo searchLog, PaginationDo page) { try { // 日志数据处理 LocalDateTime endTime = LocalDateTime.now(); searchLog.setEndTime(endTime); searchLog.setResult(resultProcess(page).toString()); // 搜索消耗的时间 Duration duration = Duration.between(startTime, endTime); searchLog.setCostTime(duration.toMillis()); // 日志处理 saveLog(searchLog); } catch (Exception e) { // 日志保存处理异常 不能影响到主流程的查询 LOG.error("Save log error!", e); } } /** * [简要描述]:搜索日志处理
* [详细描述]:
* * @param searchLog */ private void saveLog(SearchLogDo searchLog) { if (LOG.isDebugEnabled()) { LOG.debug("Search log info:" + searchLog.toString()); } searchLogService.addSearchLog(searchLog); } /** * [简要描述]:返回结果处理
* [详细描述]:
* * @param page * @return */ private PaginationDo resultProcess(PaginationDo page) { PaginationDo pd = new PaginationDo(); pd.setPageNo(page.getPageNo()); pd.setPageSize(page.getPageSize()); pd.setTook(page.getTook()); pd.setTotal(page.getTotal()); List result = page.getResults(); if (null != result && result.size() > RESULT_MAX_LENGTH) { pd.setResults(result.subList(0, RESULT_MAX_LENGTH)); } else { pd.setResults(result); } return pd; } /** * [简要描述]: 处理聚和菜单 * [详细描述]:
* * @return void * mjye 2019-02-28 - 16:45 **/ private void processMenu(SearchRequestDo request, SearchMenusDo menu, TransportClient client) { String index = request.getIndex(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); // 设置是否按查询匹配度排序 searchBuilder.setExplain(true); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //权重 QueryBuilder functionScoreQueryBuilder = this.getFunctionScoreQueryBuilder(request); boolQuery.must(functionScoreQueryBuilder); // 设置过滤条件 setFilters(request, boolQuery); searchBuilder.setFetchSource(new String[] { ES_EXT_PROPS, ES_SKU_PROPS }, null); // 设置菜单的品牌、价格、分类 this.setMenuInBrandCategorySale(request, boolQuery, menu); // 设置查询 searchBuilder.setQuery(boolQuery); // 设置排序 setOrder(searchBuilder, request); // 执行搜索 SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); long took = response.getTook().getMillis(); // 普通查询 this.processSearchMenu(hits, menu); menu.setTook(took); if (LOG.isDebugEnabled()) { LOG.debug("Search result:" + menu); } } /** * [简要描述]:查询
* [详细描述]:
* * @param request 查询请求 * @param page 分页组装数据 * 搜索日志 * @param client esClient */ private void processSearch(SearchRequestDo request, PaginationDo page, TransportClient client) { Integer pageNo = request.getPageNo(); Integer pageSize = request.getPageSize(); pageNo = null == pageNo ? 1 : pageNo; page.setPageNo(pageNo); pageSize = null == pageSize ? 1 : pageSize; pageSize = pageSize > MAX_PAGE_SIZE ? MAX_PAGE_SIZE : pageSize; page.setPageSize(pageSize); String index = request.getIndex(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); // 设置是否按查询匹配度排序 searchBuilder.setExplain(true); // 分页信息 searchBuilder.setFrom((pageNo - 1) * pageSize).setSize(pageSize); // FunctionScoreQueryBuilder functionScoreQueryBuilder = this.getFunctionScoreQueryBuilder(request); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //权重 QueryBuilder functionScoreQueryBuilder = this.getFunctionScoreQueryBuilder(request); boolQuery.must(functionScoreQueryBuilder); // 设置过滤条件 setFilters(request, boolQuery); // 设置查询 searchBuilder.setQuery(boolQuery); // 设置高亮 searchBuilder.highlighter(setHighlightBuilder()); // 设置排序 setOrder(searchBuilder, request); // 执行搜索 SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); long took = response.getTook().getMillis(); long total = hits.getTotalHits(); // 普通查询 processSearchResult(page, hits, total); page.setTook(took); if (LOG.isDebugEnabled()) { LOG.debug("Search result:" + page); } } /** * [简要描述]: 获取商品总数 * [详细描述]:
* * @return int * mjye 2019-03-05 - 14:42 **/ private long processCommodityTotal(SearchRequestDo request, TransportClient client) { String index = request.getIndex(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); //权重 QueryBuilder functionScoreQueryBuilder = this.getFunctionScoreQueryBuilder(request); boolQuery.must(functionScoreQueryBuilder); // 设置过滤条件 setFilters(request, boolQuery); // 设置查询 searchBuilder.setQuery(boolQuery); // 执行搜索 SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); return hits.getTotalHits(); } /** * [简要描述]: 设置菜单的品牌名称,分类,价格 * [详细描述]: 聚合
* * @return void * mjye 2019-02-26 - 9:58 **/ private void setMenuInBrandCategorySale(SearchRequestDo request, QueryBuilder query, SearchMenusDo menus) { // 品牌、价格、分类聚合 List brandlist = aggregationFunction(request.getIndex(), query, ES_BRAND_NO); List pricelist = aggregationFunction(request.getIndex(), query, SALE_PRICE); List catelist = aggregationFunction(request.getIndex(), query, ES_CATEGORY_NAME); this.getThreeOprtCat(catelist, menus); } /** * [简要描述]: 对品牌,分类,价格区间进行聚和 * [详细描述]:
* * @param index : 索引 * @param query : 查询条件 * @param column : 聚合字段 * @return java.util.List * mjye 2019-02-28 - 15:29 **/ private List aggregationFunction(String index, QueryBuilder query, String column) { TransportClient client = esClient.getTransportClient(); SearchRequestBuilder searchBuilder = client.prepareSearch(index); if (query == null) { query = QueryBuilders.matchAllQuery(); } // if ("brandName".equals(column)) // { // column = column + ".raw"; // } TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms(column).field(column) .order(BucketOrder.key(true)).size((1 << 31) - 1); searchBuilder.setQuery(query).addAggregation(termsAggregationBuilder); SearchResponse response = searchBuilder.execute().actionGet(); Terms terms = response.getAggregations().get(column); List list = new ArrayList<>(); for (Terms.Bucket bucket : terms.getBuckets()) { list.add(bucket.getKey().toString()); } return list; } /** * [简要描述]:分词查询
* [详细描述]:
* * @param request 查询请求 * @param page 响应数据 * @param client ES客户端 * @param searchBuilder 查询builder * @return took */ private long segmentQuery(SearchRequestDo request, PaginationDo page, TransportClient client, SearchRequestBuilder searchBuilder, SearchMenusDo menus) { // IK_SMART-标准分词 IK_MAX_WORD-最大分词 STANDARD-ES默认分词全拆 // IK_SMART拆词 List openKeywords = this.openedWords(AnalyzeType.IK_SMART, client, request.getKeyWords()); // 拆词查询 searchBuilder.setQuery(setQueryBuilder(openKeywords, request)); SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); long total = hits.getTotalHits(); long took = response.getTook().getMillis(); if (total > 0 && total >= searchTotal) { // 数据处理 processSearchResult(page, hits, total); } else { // IK MAX WORD拆词 openKeywords = this.openedWords(AnalyzeType.IK_MAX_WORD, client, request.getKeyWords()); // 拆词查询 searchBuilder.setQuery(setQueryBuilder(openKeywords, request)); response = searchBuilder.execute().actionGet(); hits = response.getHits(); total = hits.getTotalHits(); took = response.getTook().getMillis(); if (total > 0 && total >= searchTotal) { // 普通拆词查询 processSearchResult(page, hits, total); } else if (STANDARD == this.analyzeType) { // ES默认拆词,全拆 openKeywords = this.openedWords(AnalyzeType.STANDARD, client, request.getKeyWords()); // 拆词查询 searchBuilder.setQuery(setQueryBuilder(openKeywords, request)); response = searchBuilder.execute().actionGet(); hits = response.getHits(); total = hits.getTotalHits(); took = response.getTook().getMillis(); if (total > 0) { // 处理结果 processSearchResult(page, hits, total); } } } return took; } /** * [简要描述]:设置排序
* [详细描述]:
* * @param searchBuilder SearchRequestBuilder * @param request SearchRequestDo */ private void setOrder(SearchRequestBuilder searchBuilder, SearchRequestDo request) { int orderType = request.getSortFeild(); SortOrder sortOrder = SortOrder.ASC; int sort = request.getSort(); if (DESC_SORT == sort) { sortOrder = SortOrder.DESC; } if (OrderField.PRICE.getType() == orderType) { // 价格 searchBuilder.addSort(SALE_PRICE, sortOrder); } else if (OrderField.SALES.getType() == orderType) { // 销量 searchBuilder.addSort(SALES_VOLUME, sortOrder); } else if (OrderField.SLAESTIME.getType() == orderType) { // 上架时间 searchBuilder.addSort(SALE_TIME, sortOrder); } else if (OrderField.COMMENTS.getType() == orderType) { // 好评度 searchBuilder.addSort(COMMENTS, sortOrder); } } /** * [简要描述]:分词查询
* [详细描述]:
* * @param analyze 分词器类型 * @param client ES客户端 * @param content 分词内容 * @return 拆分后的词 */ private List openedWords(AnalyzeType analyze, TransportClient client, String content) { List allWorlds = new ArrayList<>(); if (StringUtils.isNotBlank(content)) { // 指定分词器 AnalyzeResponse response = client.admin().indices().prepareAnalyze(content).setAnalyzer(analyze.getType()) .execute().actionGet(); List tokens = response.getTokens(); if (LOG.isDebugEnabled()) { LOG.debug("ElasticSearch excute analyzeToken result:" + JSON.toJSONString(tokens)); } for (AnalyzeResponse.AnalyzeToken token : tokens) { allWorlds.add(token.getTerm()); } } return allWorlds; } /** * [简要描述]:设置高亮信息
* [详细描述]:
* * @param searchResultDo SearchResultDo * @param searchHit SearchHit */ private void setHighlight(SearchResultDo searchResultDo, SearchHit searchHit) { Text[] text; Map highlightFields = searchHit.getHighlightFields(); if (highlightFields.containsKey(ES_TITLE_FEILD)) { text = highlightFields.get(ES_TITLE_FEILD).getFragments(); if (text.length > 0) { searchResultDo.setHighlightTitle(text[0].string()); } } if (highlightFields.containsKey(ES_SUBTITLE_FEILD)) { text = highlightFields.get(ES_SUBTITLE_FEILD).getFragments(); if (text.length > 0) { searchResultDo.setHighlightSubTitle(text[0].string()); } } } /** * [简要描述]: 处理聚和菜单查询 * [详细描述]:
* * @return void * mjye 2019-02-28 - 16:57 **/ private void processSearchMenu(SearchHits hits, SearchMenusDo menu) { // 数据结果 List searchResponses = new ArrayList<>(); List searchStrs = new ArrayList<>(); SearchResultDo searchResultDo; String searchSource; ElasticSearchDoc esd; // 迭代查询结果 for (SearchHit searchHit : hits) { searchResultDo = new SearchResultDo(); searchSource = searchHit.getSourceAsString(); if (StringUtils.isNotBlank(searchSource)) { searchStrs.add(searchSource); esd = JSON.parseObject(searchSource, ElasticSearchDoc.class); esd.setId(searchHit.getId()); searchResultDo.setDoc(esd); searchResponses.add(searchResultDo); } } if (LOG.isDebugEnabled()) { LOG.debug("Search resource:" + searchStrs); } this.getMenus(searchResponses, menu); } /** * [简要描述]:查询结果处理
* [详细描述]:
* * @param page 分页数据 * @param hits 查询数据 * @param total 查询总数 */ private void processSearchResult(PaginationDo page, SearchHits hits, long total) { page.setTotal((int) total); // 数据结果 List searchResponses = new ArrayList<>(); List searchStrs = new ArrayList<>(); SearchResultDo searchResultDo; String searchSource; ElasticSearchDoc esd; // 迭代查询结果 for (SearchHit searchHit : hits) { searchResultDo = new SearchResultDo(); searchSource = searchHit.getSourceAsString(); if (StringUtils.isNotBlank(searchSource)) { searchStrs.add(searchSource); esd = JSON.parseObject(searchSource, ElasticSearchDoc.class); esd.setId(searchHit.getId()); searchResultDo.setDoc(esd); // 高亮字段 setHighlight(searchResultDo, searchHit); searchResponses.add(searchResultDo); } } if (LOG.isDebugEnabled()) { LOG.debug("Search resource:" + searchStrs); } page.setResults(searchResponses); } /** * [简要描述]:设置高亮信息
* [详细描述]:
* * @return 高亮huilder */ private HighlightBuilder setHighlightBuilder() { HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false); highlightBuilder.preTags(preTags); highlightBuilder.postTags(postTags); // highlightBuilder.field("subTitle"); // highlightBuilder.field("title"); return highlightBuilder; } /** * [简要描述]:设置拆词查询条件
* [详细描述]:
* * @param openKeywords 拆词后的关键词 * @param request 查询条件 * @return QueryBuilder */ private QueryBuilder setQueryBuilder(List openKeywords, SearchRequestDo request) { BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); if (CollectionUtil.isNotEmpty(openKeywords)) { QueryBuilder multiMatch; for (String word : openKeywords) { // 关键字 标签 中多字段查询 multiMatch = QueryBuilders.multiMatchQuery(word, ESConstants.ES_KEY_WORDS, ESConstants.SALES_TAG_NAME) .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX).slop(1).tieBreaker(0.3f); queryBuilder.should(multiMatch); } setFilters(request, queryBuilder); return queryBuilder; } return setQueryBuilder(request); } /** * [简要描述]:设置查询条件
* [详细描述]:
* * @param request 搜索请求 * @return QueryBuilder */ private QueryBuilder setQueryBuilder(SearchRequestDo request) { BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); String keyWords = request.getKeyWords(); // 匹配多个字段 if (StringUtils.isNotBlank(keyWords)) { // 关键字和标签匹配查询 QueryBuilder multiMatch = QueryBuilders .multiMatchQuery(keyWords, ES_BRAND_NO, ES_COMMODITY_NO, ES_TITLE, ES_SUB_TITLE, ES_OPRT_CAT_NAME, ES_LABELS, ES_EXT_PROPS, ES_SKU_PROPS, ES_PRODUCT_AREA) .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX); queryBuilder.should(multiMatch); } // 设置过滤条件 setFilters(request, queryBuilder); return queryBuilder; } /** * [简要描述]:设置过滤查询
* [详细描述]:
* * @param request 搜索请求 * @param queryBuilder BoolQueryBuilder */ private void setFilters(SearchRequestDo request, BoolQueryBuilder queryBuilder) { // 新品不为-1,即有过滤选择 if (INVALID_FLAG != request.getNewly()) { // 新品过滤 queryBuilder.must(QueryBuilders.matchQuery(NEWLY, request.getNewly())); } // 分类编号过滤 // 仅对从分类入口进入商品列表是有效 if (StringUtils.isBlank(request.getKeyWords()) && StringUtils.isNotBlank(request.getOprtCatNo())) { queryBuilder.must(QueryBuilders.matchQuery(ES_CATEGORY_NO, request.getOprtCatNo())); } // 是否有货过滤 if (INVALID_FLAG != request.getHasStock()) { // 无货 queryBuilder.must(QueryBuilders.matchQuery(HAS_STOCK, request.getHasStock())); } // 商品分类过滤查询 if (CollectionUtil.isNotEmpty(request.getCategoryNo())) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); for (String s : request.getCategoryNo()) { boolQueryBuilder.should(QueryBuilders.matchQuery(ES_CATEGORY_NO, s)); } queryBuilder.must(boolQueryBuilder); } // 商品品牌过滤查询 if (CollectionUtil.isNotEmpty(request.getBrandNo())) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); for (String s : request.getBrandNo()) { boolQueryBuilder.should(QueryBuilders.matchQuery(ES_BRAND_NO, s)); } queryBuilder.must(boolQueryBuilder); } // sku规格属性过滤 if (CollectionUtil.isNotEmpty(request.getSkuPropsNo())) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); for (String s : request.getSkuPropsNo()) { boolQueryBuilder.should(QueryBuilders.matchQuery(ES_SKU_PROPS, s)); } queryBuilder.must(boolQueryBuilder); } // 商品扩展属性过滤 if (CollectionUtil.isNotEmpty(request.getExtPropsNo())) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); for (String s : request.getExtPropsNo()) { boolQueryBuilder.should(QueryBuilders.matchQuery(ES_EXT_PROPS, s)); } queryBuilder.must(boolQueryBuilder); } // 价格区间过滤 if (CollectionUtil.isNotEmpty(request.getRangesPrices())) { BoolQueryBuilder priceBoolQuery = QueryBuilders.boolQuery(); for (String rangesPrice : request.getRangesPrices()) { String[] split = rangesPrice.trim().split("-"); Double minPrice = Double.parseDouble(split[CONSTANT_ZERO]); Double maxPrice = Double.parseDouble(split[split.length - CONSTANT_ONE]); if (CONSTANT_TWO == split.length && minPrice < maxPrice) { priceBoolQuery.should(QueryBuilders.rangeQuery(ESConstants.SALE_PRICE).from(minPrice).to(maxPrice)); } } queryBuilder.must(priceBoolQuery); } } /** * [简要描述]: 根绝查询到的结果聚合出筛选菜单 * [详细描述]:
* * @param searchResponses : 查询到的结果 * @return com.purcotton.omni.search.dto.SearchMenusDo * mjye 2019-02-23 - 11:12 **/ private SearchMenusDo getMenus(List searchResponses, SearchMenusDo menus) { if (CollectionUtil.isNotEmpty(searchResponses)) { Set brandName = new HashSet<>(); Set oprtCatName = new HashSet<>(); Set skuProps = new HashSet<>(); Set extProps = new HashSet(); Set price = new HashSet(); // 业务逻辑代码聚合部分 menus.setSkuProps(skuProps); menus.setExtProps(extProps); } return menus; } /** * [简要描述]: 获取商品的三级运营分类 * [详细描述]:
* * @param catelist : 商品的运营分类 * @return java.lang.String * mjye 2019-02-23 - 12:00 **/ private void getThreeOprtCat(List catelist, SearchMenusDo menus) { Set threeOprtCat = null; if (CollectionUtil.isNotEmpty(catelist)) { threeOprtCat = new HashSet<>(); SearchCategoryDo searchCategoryDo; for (String cate : catelist) { List strings1 = Arrays.asList(cate.split(",")); for (String s : strings1) { searchCategoryDo = new SearchCategoryDo(); String[] split = s.split("-"); searchCategoryDo.setCategoryName(split[CONSTANT_ZERO]); searchCategoryDo.setCategoryNo(split[split.length - CONSTANT_ONE]); threeOprtCat.add(searchCategoryDo); } } } menus.setOprtCatName(threeOprtCat); } /** * [简要描述]: 根据权重因子查询 * [详细描述]:
* * @return org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder * mjye 2019-02-23 - 18:14 **/ private QueryBuilder getFunctionScoreQueryBuilder(SearchRequestDo request) { // List searchShopWeightDtos = shopRestService.queryShopWeight(shopWeightDto); // 业务查询权重配置 List searchShopWeightDtos = new ArrayList<>(); String searchContent = request.getKeyWords(); BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]; // 匹配多个字段 if (StringUtils.isNotBlank(request.getKeyWords())) { // 关键字和标签匹配查询 QueryBuilder multiMatch = QueryBuilders .multiMatchQuery(searchContent, ES_TITLE, ES_BRAND_NAME, ES_COMMODITY_NO, ES_SUB_TITLE, ES_OPRT_CAT_NAME, ES_LABELS, ES_EXT_PROPS, ES_SKU_PROPS, ES_PRODUCT_AREA) .type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX); queryBuilder.should(multiMatch); if (CollectionUtil.isNotEmpty(searchShopWeightDtos)) { filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[9]; for (int i = 0; i < searchShopWeightDtos.size(); i++) { queryBuilder.should(QueryBuilders .matchQuery(searchShopWeightDtos.get(i).getEnglishName(), searchContent)); if (ESConstants.CONSTANT_ZERO <= searchShopWeightDtos.get(i).getWeightFactor()) { weight = searchShopWeightDtos.get(i).getWeightFactor(); } filterFunctionBuilders[i] = new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders .termQuery(searchShopWeightDtos.get(i).getEnglishName(), searchContent), new WeightBuilder() .setWeight(weight)); } return QueryBuilders.functionScoreQuery(queryBuilder, filterFunctionBuilders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.MULTIPLY); } } return queryBuilder; } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/rest/SearchManagerRestService.java ================================================ package com.xiao.spring.cloud.search.rest; import com.xiao.springcloud.demo.common.logaspect.LogAnnotation; import com.xiao.spring.cloud.search.dto.ElasticSearchDoc; import com.xiao.spring.cloud.search.service.SearchManangerService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * [简要描述]: 搜索服务数据管理 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 16:22 * @since JDK 1.8 */ @RestController @RequestMapping("/search/manager") @Slf4j public class SearchManagerRestService { /* * 搜索管理服务 */ @Autowired private SearchManangerService searchManangerService; /** * [简要描述]:根据id查找doc
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 索引文档 */ @RequestMapping("/getById") @LogAnnotation public ElasticSearchDoc getEsDocById(String id, String index) { return searchManangerService.getEsDocById(id, index); } /** * [简要描述]:获取指定索引下所有文档
* [详细描述]:
* * @param index 索引名称 * @return 所有文档 */ @RequestMapping("/getAll") @LogAnnotation public List getAllDos(String index) { return searchManangerService.getAllDos(index); } /** * [简要描述]:根据defProdNo查找doc
* [详细描述]:
* * @param defProdNo 产品ID * @return 产品信息 */ @RequestMapping("/getByProdNo") @LogAnnotation public ElasticSearchDoc getEsDocByDefProdNo(String defProdNo, String index) { return searchManangerService.getEsDocByDefProdNo(defProdNo, index); } /** * [简要描述]:根据商品id查询出商品
* [详细描述]:
* * @param commoNo 商品ID * @return 商品信息 */ @RequestMapping("/getByCommodityNo") public List getEsDocByCommoNo(String commoNo, String index) { return searchManangerService.getEsDocByCommoNo(commoNo, index); } /** * [简要描述]:添加单个
* [详细描述]:
* * @param doc 文档 * @return 添加状态 */ @RequestMapping("/addData") @LogAnnotation public boolean addData(@RequestBody ElasticSearchDoc doc) { return this.searchManangerService.addData(doc); } /** * [简要描述]:批量添加数据
* [详细描述]: * * @param docs 索引文档集 * @return 更新结果状态 */ @RequestMapping("/batchAdd") @LogAnnotation public boolean addDatas(@RequestBody List docs) { return this.searchManangerService.addDatas(docs); } /** * [简要描述]:单个更新
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ @RequestMapping("/update") @LogAnnotation public boolean updateData(ElasticSearchDoc doc) { return this.searchManangerService.updateData(doc); } /** * [简要描述]:批量更新
* [详细描述]:
* * @param docs 索引文档集 * @return 更新状态 */ @RequestMapping("/batchUpdate") @LogAnnotation public boolean updateDatas(List docs) { return this.searchManangerService.updateDatas(docs); } /** * [简要描述]:通过id修改单个文档
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ @RequestMapping("/updateById") @LogAnnotation public boolean updateDataById(ElasticSearchDoc doc) { return this.searchManangerService.updateDataById(doc); } /** * [简要描述]:批量更新库存状态 * [详细描述]:
* * @param docs 索引文档集 * @return 更新状态 */ @RequestMapping("/batchUpdateStock") @LogAnnotation() public boolean updateHasStock(List docs) { return this.searchManangerService.updateHasStock(docs); } /** * [简要描述]:根据id单个删除
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 删除状态 */ @RequestMapping("/deleteById") @LogAnnotation public boolean deleteData(String id, String index) { return this.searchManangerService.deleteData(id, index); } /** * [简要描述]:ID批量删除
* [详细描述]:
* * @param docs 索引ID和索引集,主要包含id和Index * @return 删除状态 */ @RequestMapping("/batchDeleteById") public boolean deleteDatas(List docs) { return this.searchManangerService.deleteDatas(docs); } /** * [简要描述]:根据商品编号删除
* [详细描述]:
* * @param commoNo 商品编号 * @param index 索引名称 * @return 删除状态 */ @RequestMapping("/deleteByCommoNo") @LogAnnotation public boolean deleteDataByCommoNo(String commoNo, String index) { return this.searchManangerService.deleteDatasByCommoNo(commoNo, index); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/rest/SearchRestService.java ================================================ package com.xiao.spring.cloud.search.rest; import com.xiao.spring.cloud.search.dto.SearchCommodityResultDo; import com.xiao.spring.cloud.search.dto.SearchMenusDo; import com.xiao.spring.cloud.search.dto.SearchRequestDo; import com.xiao.spring.cloud.search.service.SearchService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: 基于springcloud提供搜索rest服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 16:15 * @since JDK 1.8 */ @RestController @RequestMapping("/search") @Slf4j public class SearchRestService { @Autowired private SearchService searchService; @RequestMapping("/keywords") public SearchCommodityResultDo search(@RequestBody SearchRequestDo searchRequestDo) { //索引为必填 if (StringUtils.isBlank(searchRequestDo.getIndex()) || (StringUtils.isBlank(searchRequestDo.getKeyWords()) && StringUtils.isBlank(searchRequestDo.getOprtCatNo()))) { log.error("搜索的index或者keyWords不能为空!"); return new SearchCommodityResultDo(); } return searchService.search(searchRequestDo); } @RequestMapping("/searchMenu") public SearchMenusDo searchMenu(@RequestBody SearchRequestDo searchRequestDo) { //索引为必填 if (StringUtils.isBlank(searchRequestDo.getIndex()) || (StringUtils.isBlank(searchRequestDo.getKeyWords()) && StringUtils.isBlank(searchRequestDo.getOprtCatNo()))) { log.error("搜索的index或者keyWords不能为空!"); return new SearchMenusDo(); } return searchService.searchMenu(searchRequestDo); } /** * [简要描述]: 根据查询条件获取商品总数 * [详细描述]: * @param searchRequestDo : 查询条件 * @return int * mjye 2019-03-05 - 14:35 **/ @RequestMapping("/commodityTotal") public Long commodityTotal(@RequestBody SearchRequestDo searchRequestDo) { //索引为必填 if (StringUtils.isBlank(searchRequestDo.getIndex()) || (StringUtils.isBlank(searchRequestDo.getKeyWords()) && StringUtils.isBlank(searchRequestDo.getOprtCatNo()))) { log.error("搜索的index或者keyWords不能为空!"); return 0L; } return searchService.commodityTotal(searchRequestDo); } } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/service/SearchManangerService.java ================================================ package com.xiao.spring.cloud.search.service; import com.xiao.spring.cloud.search.dto.ElasticSearchDoc; import java.util.List; /** * [简要描述]: 搜索服务管理 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 09:58 * @since JDK 1.8 */ public interface SearchManangerService { /** * [简要描述]:创建索引和默认mapping
* [详细描述]:
* * @param index * : 索引名称 * @return true成功 * llxiao 2018/10/17 - 19:47 **/ boolean createIndexMapping(String index); /** * [简要描述]:索引是否存在
* [详细描述]:
* * @param index * : 索引名称 * @return boolean * llxiao 2018/10/18 - 8:35 **/ boolean existsIndex(String index); /** * [简要描述]:删除索引
* [详细描述]:
* * @param index * : 索引名称 * @return boolean * llxiao 2018/10/18 - 8:35 **/ boolean deleteIndex(String index); /** * [简要描述]:根据id查找doc
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 索引文档 */ ElasticSearchDoc getEsDocById(String id, String index); /** * [简要描述]:获取指定索引下所有文档
* [详细描述]:
* * @param index * @return 所有文档 */ List getAllDos(String index); /** * [简要描述]:根据defProdNo查找doc
* [详细描述]:
* * @param defProdNo 产品ID * @return 产品信息 */ ElasticSearchDoc getEsDocByDefProdNo(String defProdNo, String index); /** * [简要描述]:根据商品id查询出商品
* [详细描述]:
* * @param commoNo 商品ID * @return 商品信息 */ List getEsDocByCommoNo(String commoNo, String index); /** * [简要描述]:添加单个
* [详细描述]:
* * @param doc 文档 * @return 添加状态 */ boolean addData(ElasticSearchDoc doc); /** * [简要描述]:批量添加数据
* [详细描述]: * * @param docs 索引文档集 * @return 更新结果状态 */ boolean addDatas(List docs); /** * [简要描述]:单个更新
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ boolean updateData(ElasticSearchDoc doc); /** * [简要描述]:批量更新
* [详细描述]:
* * @param docs 索引文档集 * @return 更新状态 */ boolean updateDatas(List docs); /** * [简要描述]:通过id修改单个文档
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ boolean updateDataById(ElasticSearchDoc doc); /** * [简要描述]:根据默认货品编码修改
* [详细描述]:
* * @param doc 索引文档 * @return 更新状态 */ boolean updateDataByDefProdNo(ElasticSearchDoc doc); /** * [简要描述]:批量更新库存状态 * [详细描述]:
* * @param allDocs 索引文档集 * @return 更新状态 */ boolean updateHasStock(List allDocs); /** * [简要描述]:根据id单个删除
* [详细描述]:
* * @param id 索引ID * @param index 索引名称 * @return 删除状态 */ boolean deleteData(String id, String index); /** * [简要描述]:ID批量删除
* [详细描述]:
* * @param docs 索引ID和索引集 * @return 删除状态 */ boolean deleteDatas(List docs); /** * [简要描述]:根据商品编号删除
* [详细描述]:
* * @param commoNo 商品编号 * @param index 索引名称 * @return 删除状态 */ boolean deleteDatasByCommoNo(String commoNo, String index); } ================================================ FILE: SpringCloud-SearchService/src/main/java/com/xiao/spring/cloud/search/service/SearchService.java ================================================ package com.xiao.spring.cloud.search.service; import com.xiao.spring.cloud.search.dto.SearchCommodityResultDo; import com.xiao.spring.cloud.search.dto.SearchMenusDo; import com.xiao.spring.cloud.search.dto.SearchRequestDo; /** * [简要描述]: 搜索服务 * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/8 09:58 * @since JDK 1.8 */ public interface SearchService { /** * [简要描述]:搜索
* [详细描述]:
* * @param searchRequestDo : * @return PaginationDo * llxiao 2018/10/13 - 9:29 **/ SearchCommodityResultDo search(SearchRequestDo searchRequestDo); /** * [简要描述]: 查询聚和菜单 * [详细描述]:
* @param searchRequestDo : 查询条件 * @return com.purcotton.omni.search.dto.SearchMenusDo * mjye 2019-02-28 - 16:41 **/ SearchMenusDo searchMenu(SearchRequestDo searchRequestDo); /** * [简要描述]: 根据查询条件获取商品总数 * [详细描述]:
* @param searchRequestDo : 查询条件 * @return int * mjye 2019-03-05 - 14:38 **/ long commodityTotal(SearchRequestDo searchRequestDo); } ================================================ FILE: SpringCloud-SearchService/src/main/resources/application.yml ================================================ server: port: 7770 elastic: search: #配置集群中的一个节点即可,通过集群名称自动连接其他节点,注意此处的端口为tcp端口不是http的端口,默认会绑定9300 host: 172.16.250.52:9300 cluster: name: purcotton-test-es total: 2 highlight: pretags: posttags: analyze: #0默认分词器,1中文分词器 type: 0 eureka: instance: hostname: localhost instance-id: ${spring.cloud.client.ipAddress}:${server.port} prefer-ip-address: true client: serviceUrl: defaultZone: http://120.77.46.245:8888/eureka/ ================================================ FILE: SpringCloud-SearchService/src/main/resources/bootstrap.yml ================================================ spring: #cloud: #config: #uri: http://120.77.46.245:8808/omni-config-server/ #profile: @env@ #label: master #name: omni-search-service application: name: omni-search-service logstash: kafka-servers: @kafkaServer@ logs-topic: @kafkaLogTopic@ redisson.fileName: redisson-cluster ================================================ FILE: SpringCloud-SearchService/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} utf8 true false true UTF-8 ${kafkaLogTopic} bootstrap.servers=${kafkaServer} block.on.buffer.full=false 0 1000 true ================================================ FILE: SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/SearchApplicationTest.java ================================================ package com.xiao.springcloud.test; import com.xiao.spring.cloud.search.SearchApplication; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; import java.util.Iterator; import java.util.Map; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/9 11:05 * @since JDK 1.8 */ @RunWith(SpringRunner.class) // 单个controller测试 //@SpringBootTest(classes = MockServletContext.class) // 全局 api测试 @SpringBootTest(classes = SearchApplication.class) @WebAppConfiguration public class SearchApplicationTest { protected MockMvc mockMvc; // 是否打印请求信息 protected boolean isPrint; // 全局api测试 @Autowired private WebApplicationContext context; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // 全局api测试 this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); isPrint = false; } /** * [简要描述]:post请求
* [详细描述]:KV和JSON参数二者都存在是优先取KV参数
* * @param url : 请求地址 * @param params : 请求参数 KV * @param jsonParams : 请求JSON参数 * @return org.springframework.test.web.servlet.ResultActions * llxiao 2018/10/9 - 16:44 **/ protected ResultActions testBasePostApi(String url, Map params, String jsonParams) throws Exception { //构建请求 MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post(url); if (MapUtils.isNotEmpty(params)) { // 参数请求 MultiValueMap paramMap = new LinkedMultiValueMap(); Iterator> iterator = params.entrySet().iterator(); Map.Entry entry; while (iterator.hasNext()) { entry = iterator.next(); paramMap.add(entry.getKey(), entry.getValue()); } request.params(paramMap); } else if (StringUtils.isNotBlank(jsonParams)) { request.contentType(MediaType.APPLICATION_JSON_UTF8).content(jsonParams) .accept(MediaType.APPLICATION_JSON_UTF8); } // 发起http请求 ResultActions actions = mockMvc.perform(request); if (isPrint) { // 打印出request和response的详细信息,便于调试。 actions.andDo(MockMvcResultHandlers.print()); } // 期望返回 200 actions.andExpect(MockMvcResultMatchers.status().isOk()); if (MapUtils.isEmpty(params) && StringUtils.isNotBlank(jsonParams)) { // JSON返回处理 actions.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8)); } return actions; } } ================================================ FILE: SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/SearchManagerTest.java ================================================ package com.xiao.springcloud.test; import com.xiao.spring.cloud.search.dto.ElasticSearchDoc; import com.xiao.spring.cloud.search.es.client.ElasticSearchClient; import com.xiao.spring.cloud.search.service.SearchManangerService; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.*; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import java.util.HashMap; import java.util.Map; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/9 11:07 * @since JDK 1.8 */ public class SearchManagerTest extends SearchApplicationTest { @Autowired private SearchManangerService searchManangerService; @Autowired private ElasticSearchClient esClient; @Test public void testGetByCommoNo() throws Exception { this.isPrint = true; String url = "/search/manager/getById"; Map params = new HashMap<>(1); params.put("id", "002000000662"); params.put("index", "purcotton"); // 字符串包含Contains // this.testBasePostApi(url, params, null) // .andExpect(MockMvcResultMatchers.content().string(new Contains("002000000662"))); // json ID处理 this.testBasePostApi(url, params, null).andExpect(MockMvcResultMatchers.jsonPath("$.id").value("002000000662")); } @Test public void testDel() { searchManangerService.deleteData("50101001", "10000"); searchManangerService.deleteData("50101002", "10000"); searchManangerService.deleteData("50101003", "10000"); searchManangerService.deleteData("50101004", "10000"); } /** * [简要描述]:在Index为10000下查找标题包含“IPhone”,优先取“品牌手机”这个分类,销量越高越前,结果随机给用户展示
* [详细描述]:
*

* llxiao 2019/1/31 - 15:44 **/ @Test public void testFilterQuery() { String searchContent = "IPhone"; TransportClient client = esClient.getTransportClient(); String index = "10000"; SearchRequestBuilder searchBuilder = client.prepareSearch(index); //分页 searchBuilder.setFrom(0).setSize(10); //explain为true表示根据数据相关度排序,和关键字匹配最高的排在前面 searchBuilder.setExplain(true); BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); // 搜索 title字段包含IPhone的数据 queryBuilder.must(QueryBuilders.matchQuery("title", searchContent)); FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[3]; //过滤条件1:分类为:品牌手机最重要 -- 权重查询Weight ScoreFunctionBuilder scoreFunctionBuilder = new WeightBuilder(); scoreFunctionBuilder.setWeight(2); QueryBuilder termQuery = QueryBuilders.termQuery("categoryName", "品牌手机"); FunctionScoreQueryBuilder.FilterFunctionBuilder category = new FunctionScoreQueryBuilder.FilterFunctionBuilder(termQuery, scoreFunctionBuilder); filterFunctionBuilders[0] = category; // 过滤条件2:销量越高越排前 --计分查询 FieldValueFactor ScoreFunctionBuilder fieldValueScoreFunction = new FieldValueFactorFunctionBuilder("salesVolume"); ((FieldValueFactorFunctionBuilder) fieldValueScoreFunction).factor(1.2f); FunctionScoreQueryBuilder.FilterFunctionBuilder sales = new FunctionScoreQueryBuilder.FilterFunctionBuilder(fieldValueScoreFunction); filterFunctionBuilders[1] = sales; // 给定每个用户随机展示: --random_score ScoreFunctionBuilder randomScoreFilter = new RandomScoreFunctionBuilder(); ((RandomScoreFunctionBuilder) randomScoreFilter).seed(2); FunctionScoreQueryBuilder.FilterFunctionBuilder random = new FunctionScoreQueryBuilder.FilterFunctionBuilder(randomScoreFilter); filterFunctionBuilders[2] = random; // 多条件查询 FunctionScore FunctionScoreQueryBuilder query = QueryBuilders.functionScoreQuery(queryBuilder, filterFunctionBuilders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM).boostMode(CombineFunction.SUM); searchBuilder.setQuery(query); SearchResponse response = searchBuilder.execute().actionGet(); SearchHits hits = response.getHits(); String searchSource; for (SearchHit hit : hits) { searchSource = hit.getSourceAsString(); System.out.println(searchSource); } // long took = response.getTook().getMillis(); long total = hits.getTotalHits(); System.out.println(total); } } ================================================ FILE: SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/SearchTest.java ================================================ package com.xiao.springcloud.test; import org.junit.Test; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/9 11:07 * @since JDK 1.8 */ public class SearchTest extends SearchApplicationTest { @Test public void testSearch() throws Exception { this.isPrint = true; String json = "{\"index\":\"purcotton\",\"keyWords\":\"棉柔巾\"}"; ResultActions actions = this.testBasePostApi("/search/keywords", null, json) .andExpect(MockMvcResultMatchers.jsonPath("$.pageNo") .value(1)); //使用Json path验证JSON 请参考http://goessner.net/articles/JsonPath/ } } ================================================ FILE: SpringCloud-SearchService/src/test/java/com/xiao/springcloud/test/cache/RedisCacheTest.java ================================================ package com.xiao.springcloud.test.cache; import com.xiao.springcloud.demo.common.cache.service.CacheService; import com.xiao.springcloud.demo.common.cache.service.DistributedService; import com.xiao.springcloud.test.SearchApplicationTest; import org.junit.Test; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import java.util.concurrent.TimeUnit; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/10/11 20:55 * @since JDK 1.8 */ public class RedisCacheTest extends SearchApplicationTest { @Autowired private CacheService cacheService; @Autowired private DistributedService distributedService; @Test public void testSet() { //cacheService.set("cacheTest", "hello redisson"); // System.out.println(cacheService.get("cacheTest")); // cacheService.set("testLock", "100"); System.out.println(cacheService.get("testLock")); } // 上一步设置 testLock的值为100,两台机器或者两个项目 同时执行该分布式测试方法。确定最终结果不会为负数则锁正常 @Test public void testDistributed() { String key = "lock"; //执行的业务代码 for (int i = 0; i < 55; i++) { RLock rLock = this.distributedService.tryLockAutoRelease(key, 60, 60, TimeUnit.SECONDS); int stock = Integer.parseInt(cacheService.get("testLock")); if (stock > 0) { cacheService.set("testLock", (stock - 1) + ""); System.out.println("test2_:lockkey:" + key + ",stock:" + (stock - 1) + ""); } rLock.unlock(); } } } ================================================ FILE: SpringCloud-Sentinel/Readme.MD ================================================ **SpringBoot + SpringCloud + Feign + Sentinel 集成实现接口限流监控** SpringBoot版本:1.5.13.RELEASE
SpringCloud版本:Edgware.SR4
Sentinel[详情介绍](https://github.com/alibaba/Sentinel)版本:0.1.2.RELEASE 是集成了[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)
[注册中心-eureka](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Sentinel/SpringCloud-Sentinel-Eureka)
[服务提供-producer](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Sentinel/SpringCloud-Sentinel-Producer)
[服务消费-consumer](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer)
[Sentinel Dashbord](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Sentinel/dashbord)
Demo使用说明: 1. Dashboard启动: java -Dserver.port=8080 -jar sentinel-dashboard.jar [官网参考](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0) 2. 启动erueka --> 启动Producer --> 启动Consumer 3. 展示图片: ![Producer](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Sentinel/img/sentinel-dashbord-producer.png) ![Consumer](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Sentinel/img/sentinel-dashbord-consumer.png) 简要配置说明: 1. dashboard启动后,客户端接入:
```html properties配置: # Sentinel dashbord 数据交互端口,注意单机多服务部署端口冲突 spring.cloud.sentinel.transport.port=8729 # sentinel dashbord地址 spring.cloud.sentinel.transport.dashboard=192.168.206.212:8880 pom文件引入(自动引入相关依赖包): org.springframework.cloud spring-cloud-alibaba-sentinel 0.1.2.RELEASE ``` 2. producer和consumer使用sentinel仅引入 上一步中的**pom**文件即可 3. feign的支持: ```html pom引入: org.springframework.cloud spring-cloud-starter-openfeign 1.4.7.RELEASE 开启sentinel支持 # 使用阿里 sentinel监控 feign.sentinel.enabled=true ``` 重要提示: 1. Sentinel版本要与springboot和springcloud大版本一致,否则会出现不可预见的异常 2. 接入到dashbord后,要请求下url,然后稍等等个1分钟左右才能到控制台,不要着急 ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/pom.xml ================================================ SpringCloud-Sentinel com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Sentinel-Consumer UTF-8 UTF-8 1.8 1.8 1.8 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-starter-hystrix org.springframework.cloud spring-cloud-starter-ribbon org.springframework.cloud spring-cloud-alibaba-sentinel 0.1.2.RELEASE org.springframework.cloud spring-cloud-starter-openfeign 1.4.7.RELEASE org.springframework.cloud spring-cloud-dependencies Edgware.SR4 pom import ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/java/com/xiao/springcloud/sentinel/consumer/ConsumerApplication.java ================================================ package com.xiao.springcloud.sentinel.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.alibaba.sentinel.annotation.SentinelRestTemplate; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 10:27 * @since JDK 1.8 */ @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } @Bean @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/java/com/xiao/springcloud/sentinel/consumer/api/ConsumerRestService.java ================================================ package com.xiao.springcloud.sentinel.consumer.api; import com.xiao.springcloud.sentinel.consumer.feign.ProducerFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 10:29 * @since JDK 1.8 */ @RestController @RequestMapping("/consumer") public class ConsumerRestService { @Autowired private ProducerFeign producerFeign; @RequestMapping("/timeout") public String timeout(String input) { return producerFeign.timeout(input); } @RequestMapping("/normal") public String normal(String input) { return producerFeign.normal(input); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/java/com/xiao/springcloud/sentinel/consumer/config/FeignConfiguration.java ================================================ package com.xiao.springcloud.sentinel.consumer.config; import com.xiao.springcloud.sentinel.consumer.feign.fallback.ProducerFeignFallBack; import org.springframework.context.annotation.Bean; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 16:11 * @since JDK 1.8 */ //@Configuration public class FeignConfiguration { @Bean public ProducerFeignFallBack feignFallBack() { return new ProducerFeignFallBack(); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/java/com/xiao/springcloud/sentinel/consumer/feign/ProducerFeign.java ================================================ package com.xiao.springcloud.sentinel.consumer.feign; import com.xiao.springcloud.sentinel.consumer.config.FeignConfiguration; import com.xiao.springcloud.sentinel.consumer.feign.fallback.ProducerFeignFallBack; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 10:29 * @since JDK 1.8 */ @Component @FeignClient(name = "sentinel-producer-service", fallback = ProducerFeignFallBack.class, configuration = FeignConfiguration.class) @RequestMapping("/api") public interface ProducerFeign { @RequestMapping("/timeout") String timeout(@RequestParam("input") String input); @RequestMapping("/normal") String normal(@RequestParam("input") String input); } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/java/com/xiao/springcloud/sentinel/consumer/feign/fallback/ProducerFeignFallBack.java ================================================ package com.xiao.springcloud.sentinel.consumer.feign.fallback; import com.xiao.springcloud.sentinel.consumer.feign.ProducerFeign; /** * [简要描述]: fallback * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 10:31 * @since JDK 1.8 */ public class ProducerFeignFallBack implements ProducerFeign { @Override public String timeout(String input) { return "Time out fall back!"; } @Override public String normal(String input) { return "Normal fall back!"; } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/resources/application.properties ================================================ ### sentinel spring.cloud.sentinel.transport.port=8730 # sentinel dashbordַ spring.cloud.sentinel.transport.dashboard=192.168.206.212:8880 # ʹð sentinel feign.sentinel.enabled=true ## رhystrix #feign.hystrix.enabled=false ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/resources/application.yml ================================================ eureka: instance: hostname: localhost instance-id: ${spring.cloud.client.ipAddress}:${server.port} prefer-ip-address: true ##设置心跳的周期间隔(默认90s)[如果10s没响应默认服务宕机] lease-expiration-duration-in-seconds: 10 #设置心跳时间间隔(默认30s)[心跳时间2s] lease-renewal-interval-in-seconds: 2 client: serviceUrl: defaultZone: http://localhost:8888/eureka/ #开启健康检查(需要spring-boot-starter-actuator依赖) #healthcheck: #enable: true feign: #请求和响应进行GZIP压缩,以提高通信效率 compression: request: #配置请求GZIP压缩 enable: true #配置压缩支持的MIME TYPE mime-types: text/xml,application/xml,application/json #配置压缩数据大小的下限 min-request-size: 208 #配置响应GZIP压缩 response: enable: true ribbon: #设置连接超时时间 ConnectTimeout: 600 #设置读取超时时间 ReadTimeout: 2000 #对所有操作请求都进行重试 OkToRetryOnAllOperations: true #切换实例的重试次数 MaxAutoRetriesNextServer: 2 #对当前实例的重试次数 MaxAutoRetries: 1 # 设置针对hello-service服务的连接超时时间 #hello-service.ribbon.ConnectTimeout=600 # 设置针对hello-service服务的读取超时时间 #hello-service.ribbon.ReadTimeout=6000 # 设置针对hello-service服务所有操作请求都进行重试 #hello-service.ribbon.OkToRetryOnAllOperations=true # 设置针对hello-service服务切换实例的重试次数 #hello-service.ribbon.MaxAutoRetriesNextServer=2 # 设置针对hello-service服务的当前实例的重试次数 #hello-service.ribbon.MaxAutoRetries=1 hystrix: # 线程池 threadpool: default: coreSize: 10 command: default: #fallback: # 是否关闭回退方法 #enable: true execution: #关闭超时熔断功能 #timeout: #enable: false isolation: thread: #设置熔断超时时间,默认1S,容易出现 fallback available 异常 timeoutInMilliseconds: 5000 # 设置熔断超时时间,针对接口设置,比如/hello接口 #hystrix.command.hello.execution.isolation.thread.timeoutInMilliseconds=10000 # 关闭熔断功能 #hystrix.command.hello.execution.timeout.enabled=false ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Consumer/src/main/resources/bootstrap.yml ================================================ server: port: 8082 spring: application: name: sentinel-consumer-service ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Eureka/pom.xml ================================================ SpringCloud-Sentinel com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Sentinel-Eureka UTF-8 UTF-8 1.8 1.8 1.8 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka-server org.springframework.cloud spring-cloud-dependencies Edgware.SR4 pom import ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Eureka/src/main/java/com.xiao.springcloud.sentinel.eureka/EurekaApplication.java ================================================ package com.xiao.springcloud.sentinel.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 09:40 * @since JDK 1.8 */ @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Eureka/src/main/resources/bootstrap.yml ================================================ server: port: 8888 spring: application: name: sentinel-eureka-server eureka: instance: hostname: localhost prefer-ip-address: true instance-id: ${spring.cloud.client.ipAddress}:${server.port} client: register-with-eureka: true fetch-registry: false service-url: defaultZone: http://localhost:8888/eureka/ server: ##以下配置,生产环境不建议使用 ###自我保护机制关闭 enable-self-preservation: false ## 清理间隔(单位毫秒,默认是60*1000) eviction-interval-timer-in-ms: 2000 ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/pom.xml ================================================ SpringCloud-Sentinel com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Sentinel-Producer UTF-8 UTF-8 1.8 1.8 1.8 org.springframework.boot spring-boot-starter-web org.slf4j slf4j-log4j12 org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-alibaba-sentinel 0.1.2.RELEASE org.springframework.cloud spring-cloud-dependencies Edgware.SR4 pom import ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/java/com/xiao/springcloud/sentinel/producer/ProducerApplication.java ================================================ package com.xiao.springcloud.sentinel.producer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 09:46 * @since JDK 1.8 */ @SpringBootApplication() @EnableEurekaClient public class ProducerApplication { public static void main(String[] args) { SpringApplication.run(ProducerApplication.class, args); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/java/com/xiao/springcloud/sentinel/producer/api/ProducerRestService.java ================================================ package com.xiao.springcloud.sentinel.producer.api; import com.xiao.springcloud.sentinel.producer.service.ProducerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 09:55 * @since JDK 1.8 */ @RestController @RequestMapping("/api") public class ProducerRestService { @Autowired private ProducerService producerService; @RequestMapping("/timeout") public String timeout(String input) { return this.producerService.timeout(input); } @RequestMapping("/normal") public String normal(String input) { return this.producerService.normal(input); } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/java/com/xiao/springcloud/sentinel/producer/service/ProducerService.java ================================================ package com.xiao.springcloud.sentinel.producer.service; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 09:56 * @since JDK 1.8 */ public interface ProducerService { /** * [简要描述]:超时APi模拟
* [详细描述]:
* * @param input : * @return java.lang.String * llxiao 2019/8/1 - 9:57 **/ String timeout(String input); /** * [简要描述]:正常APi
* [详细描述]:
* * @param input : * @return java.lang.String * llxiao 2019/8/1 - 9:59 **/ String normal(String input); } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/java/com/xiao/springcloud/sentinel/producer/service/impl/ProducerServiceImpl.java ================================================ package com.xiao.springcloud.sentinel.producer.service.impl; import com.xiao.springcloud.sentinel.producer.service.ProducerService; import org.springframework.stereotype.Service; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2019/8/1 09:57 * @since JDK 1.8 */ @Service public class ProducerServiceImpl implements ProducerService { /** * [简要描述]:超时APi模拟
* [详细描述]:
* * @param input : * @return java.lang.String * llxiao 2019/8/1 - 9:57 **/ @Override public String timeout(String input) { try { // 模拟超时2S钟 Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } return "producer timeout: " + input; } /** * [简要描述]:正常APi
* [详细描述]:
* * @param input : * @return java.lang.String * llxiao 2019/8/1 - 9:59 **/ @Override public String normal(String input) { return "producer normal: " + input; } } ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/resources/application.properties ================================================ # Sentinel dashbord ݽ˿ spring.cloud.sentinel.transport.port=8729 # sentinel dashbordַ spring.cloud.sentinel.transport.dashboard=192.168.206.212:8880 ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/resources/application.yml ================================================ ================================================ FILE: SpringCloud-Sentinel/SpringCloud-Sentinel-Producer/src/main/resources/bootstrap.yml ================================================ server: port: 8081 spring: application: name: sentinel-producer-service eureka: instance: hostname: localhost instance-id: ${spring.cloud.client.ipAddress}:${server.port} prefer-ip-address: true ##设置心跳的周期间隔(默认90s)[如果10s没响应默认服务宕机] lease-expiration-duration-in-seconds: 10 #设置心跳时间间隔(默认30s)[心跳时间2s] lease-renewal-interval-in-seconds: 2 client: serviceUrl: defaultZone: http://localhost:8888/eureka/ #开启健康检查(需要spring-boot-starter-actuator依赖) #healthcheck: #enable: true logging: level: debug ================================================ FILE: SpringCloud-Sentinel/dashbord/readme.md ================================================ 启动方式:
java -Dserver.port=8080 -jar sentinel-dashboard.jar [官网参考](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0) ================================================ FILE: SpringCloud-Sentinel/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Sentinel pom SpringCloud-Sentinel-Eureka SpringCloud-Sentinel-Producer SpringCloud-Sentinel-Consumer ================================================ FILE: SpringCloud-Sharding-Sphere/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 3.0.0 3.5.6 1.3.0 8.0.28 SpringCloud-Sharding-Sphere org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test mysql mysql-connector-java ${mysql-connector-java.version} org.mybatis mybatis ${mybatis.version} org.mybatis mybatis-spring ${mybatis-spring.version} org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis-spring.version} io.shardingsphere sharding-jdbc-spring-boot-starter ${sharding-sphere.version} org.projectlombok lombok true org.apache.commons commons-lang3 3.4 com.alibaba druid 1.1.10 ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/SpringBootStarterExample.java ================================================ package com.purcotton.sharding.sphere.demo; import com.purcotton.sharding.sphere.demo.service.CommonService; import com.purcotton.sharding.sphere.demo.service.impl.SpringPojoServiceImpl; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:23 * @since JDK 1.8 */ @MapperScan(basePackages = "com.purcotton.sharding.sphere.demo.repository") @SpringBootApplication public class SpringBootStarterExample { public static void main(final String[] args) { try (ConfigurableApplicationContext applicationContext = SpringApplication .run(SpringBootStarterExample.class, args)) { process(applicationContext); } } private static void process(final ConfigurableApplicationContext applicationContext) { CommonService commonService = getCommonService(applicationContext); // commonService.initEnvironment(); commonService.processSuccess(false); // try // { // commonService.processFailure(); // } // catch (final Exception ex) // { // System.out.println(ex.getMessage()); // commonService.printData(false); // } // finally // { // commonService.cleanEnvironment(); // } } private static CommonService getCommonService(final ConfigurableApplicationContext applicationContext) { return applicationContext.getBean(SpringPojoServiceImpl.class); } } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/entity/Order.java ================================================ package com.purcotton.sharding.sphere.demo.entity; import lombok.Data; import lombok.ToString; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import java.io.Serializable; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:07 * @since JDK 1.8 */ @Data public class Order implements Serializable { private long orderId; private int userId; private String status; @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); } } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/entity/OrderItem.java ================================================ package com.purcotton.sharding.sphere.demo.entity; import lombok.Data; import org.apache.commons.lang3.builder.ToStringBuilder; import java.io.Serializable; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:11 * @since JDK 1.8 */ @Data public class OrderItem implements Serializable { private long orderItemId; private long orderId; private int userId; private String status; @Override public String toString() { return ToStringBuilder.reflectionToString(this); } } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/repository/CommonRepository.java ================================================ package com.purcotton.sharding.sphere.demo.repository; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:12 * @since JDK 1.8 */ public interface CommonRepository { void createTableIfNotExists(); void dropTable(); void truncateTable(); Long insert(T entity); void delete(Long id); List selectAll(); List selectRange(); } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/repository/OrderItemRepository.java ================================================ package com.purcotton.sharding.sphere.demo.repository; import com.purcotton.sharding.sphere.demo.entity.OrderItem; import org.apache.ibatis.annotations.Mapper; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:13 * @since JDK 1.8 */ @Mapper public interface OrderItemRepository extends CommonRepository { } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/repository/OrderRepository.java ================================================ package com.purcotton.sharding.sphere.demo.repository; import com.purcotton.sharding.sphere.demo.entity.Order; import org.apache.ibatis.annotations.Mapper; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:13 * @since JDK 1.8 */ @Mapper public interface OrderRepository extends CommonRepository { } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/service/BasisCommonService.java ================================================ package com.purcotton.sharding.sphere.demo.service; import com.purcotton.sharding.sphere.demo.entity.Order; import com.purcotton.sharding.sphere.demo.entity.OrderItem; import com.purcotton.sharding.sphere.demo.repository.OrderItemRepository; import com.purcotton.sharding.sphere.demo.repository.OrderRepository; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:15 * @since JDK 1.8 */ public abstract class BasisCommonService implements CommonService { @Override public void initEnvironment() { getOrderRepository().createTableIfNotExists(); getOrderItemRepository().createTableIfNotExists(); getOrderRepository().truncateTable(); getOrderItemRepository().truncateTable(); } @Override public void cleanEnvironment() { getOrderRepository().dropTable(); getOrderItemRepository().dropTable(); } @Transactional @Override public void processSuccess(final boolean isRangeSharding) { System.out.println("-------------- Process Success Begin ---------------"); List orderIds = insertData(); printData(isRangeSharding); //deleteData(orderIds); //printData(isRangeSharding); System.out.println("-------------- Process Success Finish --------------"); } @Transactional @Override public void processFailure() { System.out.println("-------------- Process Failure Begin ---------------"); insertData(); System.out.println("-------------- Process Failure Finish --------------"); throw new RuntimeException("Exception occur for transaction test."); } private List insertData() { System.out.println("---------------------------- Insert Data ----------------------------"); List result = new ArrayList<>(10); for (int i = 1; i <= 10; i++) { Order order = newOrder(); order.setUserId(i); order.setStatus("INSERT_TEST"); getOrderRepository().insert(order); OrderItem item = newOrderItem(); item.setOrderId(order.getOrderId()); item.setUserId(i); item.setStatus("INSERT_TEST"); getOrderItemRepository().insert(item); result.add(order.getOrderId()); } return result; } private void deleteData(final List orderIds) { System.out.println("---------------------------- Delete Data ----------------------------"); for (Long each : orderIds) { getOrderRepository().delete(each); getOrderItemRepository().delete(each); } } @Override public void printData(final boolean isRangeSharding) { if (isRangeSharding) { printDataRange(); } else { printDataAll(); } } private void printDataRange() { System.out.println("---------------------------- Print Order Data -----------------------"); for (Object each : getOrderRepository().selectRange()) { System.out.println(each); } System.out.println("---------------------------- Print OrderItem Data -------------------"); for (Object each : getOrderItemRepository().selectRange()) { System.out.println(each); } } private void printDataAll() { System.out.println("---------------------------- Print Order Data -----------------------"); for (Object each : getOrderRepository().selectAll()) { System.out.println(each); } System.out.println("---------------------------- Print OrderItem Data -------------------"); for (Object each : getOrderItemRepository().selectAll()) { System.out.println(each); } } protected abstract OrderRepository getOrderRepository(); protected abstract OrderItemRepository getOrderItemRepository(); protected abstract Order newOrder(); protected abstract OrderItem newOrderItem(); } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/service/CommonService.java ================================================ package com.purcotton.sharding.sphere.demo.service; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:14 * @since JDK 1.8 */ public interface CommonService { void initEnvironment(); void cleanEnvironment(); void processSuccess(boolean isRangeSharding); void processFailure(); void printData(boolean isRangeSharding); } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/java/com/purcotton/sharding/sphere/demo/service/impl/SpringPojoServiceImpl.java ================================================ package com.purcotton.sharding.sphere.demo.service.impl; import com.purcotton.sharding.sphere.demo.entity.Order; import com.purcotton.sharding.sphere.demo.entity.OrderItem; import com.purcotton.sharding.sphere.demo.repository.OrderItemRepository; import com.purcotton.sharding.sphere.demo.repository.OrderRepository; import com.purcotton.sharding.sphere.demo.service.BasisCommonService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * [简要描述]: * [详细描述]: * * @author llxiao * @version 1.0, 2018/11/14 11:25 * @since JDK 1.8 */ @Service @Transactional public class SpringPojoServiceImpl extends BasisCommonService { @Resource private OrderRepository orderRepository; @Resource private OrderItemRepository orderItemRepository; @Override protected OrderRepository getOrderRepository() { return orderRepository; } @Override protected OrderItemRepository getOrderItemRepository() { return orderItemRepository; } @Override protected Order newOrder() { return new Order(); } @Override protected OrderItem newOrderItem() { return new OrderItem(); } } ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/META-INF/mappers/OrderItemMapper.xml ================================================ CREATE TABLE IF NOT EXISTS t_order_item (order_item_id BIGINT AUTO_INCREMENT, order_id BIGINT, user_id INT NOT NULL, status VARCHAR(50) , PRIMARY KEY (order_item_id)); TRUNCATE TABLE t_order_item; DROP TABLE IF EXISTS t_order_item; INSERT INTO t_order_item (order_id, user_id, status) VALUES (#{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}, #{status,jdbcType=VARCHAR}); DELETE FROM t_order_item WHERE order_id = #{orderId,jdbcType=INTEGER}; ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/META-INF/mappers/OrderMapper.xml ================================================ CREATE TABLE IF NOT EXISTS t_order (order_id BIGINT AUTO_INCREMENT, user_id INT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id)); TRUNCATE TABLE t_order; DROP TABLE IF EXISTS t_order; INSERT INTO t_order (user_id, status) VALUES (#{userId,jdbcType=INTEGER}, #{status,jdbcType=VARCHAR}); DELETE FROM t_order WHERE order_id = #{orderId,jdbcType=INTEGER}; ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/META-INF/mybatis-config.xml ================================================ ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/application-sharding-databases.properties ================================================ sharding.jdbc.datasource.names=ds_0,ds_1 sharding.jdbc.datasource.ds_0.type=com.alibaba.druid.pool.DruidDataSource sharding.jdbc.datasource.ds_0.driver-class-name=com.mysql.jdbc.Driver sharding.jdbc.datasource.ds_0.url=jdbc:mysql://192.168.206.210:3306/db_0?useSSL=false sharding.jdbc.datasource.ds_0.username=admin sharding.jdbc.datasource.ds_0.password=Admin@123 sharding.jdbc.datasource.ds_1.type=com.alibaba.druid.pool.DruidDataSource sharding.jdbc.datasource.ds_1.driver-class-name=com.mysql.jdbc.Driver sharding.jdbc.datasource.ds_1.url=jdbc:mysql://192.168.206.210:3306/db_1?useSSL=false sharding.jdbc.datasource.ds_1.username=admin sharding.jdbc.datasource.ds_1.password=Admin@123 ##ĬϷֿ ûID2ȡĪֲͬĿ sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds_$->{user_id % 2} ##ֱ sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds_$->{0..1}.t_order sharding.jdbc.config.sharding.tables.t_order.key-generator-column-name=order_id sharding.jdbc.config.sharding.tables.t_order_item.actual-data-nodes=ds_$->{0..1}.t_order_item sharding.jdbc.config.sharding.tables.t_order_item.key-generator-column-name=order_item_id ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/application.properties ================================================ spring.profiles.active=sharding-databases #spring.profiles.active=sharding-tables #spring.profiles.active=sharding-databases-tables #spring.profiles.active=master-slave #spring.profiles.active=sharding-master-slave mybatis.config-location=classpath:META-INF/mybatis-config.xml ================================================ FILE: SpringCloud-Sharding-Sphere/src/main/resources/logback.xml ================================================ ${log.context.name} ${log.pattern} ================================================ FILE: SpringCloud-ZipkinServer/README.md ================================================ **SpringCloud Sleuth Stream Zipkin Kafka Elasticsearch 实现简单链路跟踪** _注意版本号zipkin使用的是2.4.2,SpringCloud版本Dalston.SR5_ 1. **服务端主要配置**
**pom配置:**: ```$xslt org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.cloud spring-cloud-starter-zipkin org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin-stream org.springframework.cloud spring-cloud-stream-binder-kafka io.zipkin.java zipkin-autoconfigure-ui ${zipkin.version} runtime io.zipkin.java zipkin-autoconfigure-storage-elasticsearch-http ${zipkin.version} ``` **配置文件 application.properties:** ```$xslt #采样率,推荐0.1,百分之百收集的话存储可能扛不住 spring.sleuth.sampler.percentage=1 spring.sleuth.enabled=false maxHttpHeaderSize=8192 ### kafka链接和zk的链接 spring.cloud.stream.kafka.binder.brokers=192.168.206.203:9092 spring.cloud.stream.kafka.binder.zkNodes=192.168.206.203:2181 ## 使用es做存储 zipkin.storage.StorageComponent=elasticsearch zipkin.storage.type=elasticsearch zipkin.storage.elasticsearch.hosts=192.168.206.204:9200 #es集群名称 zipkin.storage.elasticsearch.cluster=zipkin-es zipkin.storage.elasticsearch.index=zipkin-db zipkin.storage.elasticsearch.index-shards=5 zipkin.storage.elasticsearch.index-replicas=1 ``` **启动注解:** ```$xslt @EnableZipkinStreamServer ``` 代码参考[SpringCloud-ZipkinServer](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-ZipkinServer) 2.**客户端配置**
**pom配置:** ```$xslt org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-stream-binder-kafka org.springframework.cloud spring-cloud-sleuth-stream ``` **参数配置application.properties:** ```$xslt ### spring 配置 spring: ## zipkin 链路跟踪配置 sleuth: enabled: true #采样率,越高会有性能影响 sampler: percentage: 1.0 cloud: ## kafka zk配置 配合zipkin stream: kafka: binder: brokers: 192.168.206.203:9092 zkNodes: 192.168.206.203:2181 ``` 代码参考[SpringCloud-Provider](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Provider)和[SpringCloud-Consumer](https://github.com/Xlinlin/SpringCloud-Demo/tree/master/SpringCloud-Consumer)
[更多参考资料](https://www.jianshu.com/p/d2a71e242ca8) ================================================ FILE: SpringCloud-ZipkinServer/pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT SpringCloud-ZipkinServer 服务链路跟踪服务端 UTF-8 UTF-8 1.8 Dalston.SR5 2.4.2 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-sleuth-zipkin org.springframework.cloud spring-cloud-starter-zipkin org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-sleuth-zipkin-stream org.springframework.cloud spring-cloud-stream-binder-kafka io.zipkin.java zipkin-autoconfigure-ui ${zipkin.version} runtime io.zipkin.java zipkin-autoconfigure-storage-elasticsearch-http ${zipkin.version} ${artifactId} ${project.build.directory}/classes src/main/resources true **/*.xml **/*.yml **/*.properties META-INF/** org.springframework.boot spring-boot-maven-plugin ================================================ FILE: SpringCloud-ZipkinServer/src/main/java/com/xiao/springcloud/zs/ZipkinServerApplication.java ================================================ /* * Winner * 文件名 :ZipkinServerApplication.java * 创建人 :llxiao * 创建时间:2018年8月9日 */ package com.xiao.springcloud.zs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer; import zipkin.server.internal.EnableZipkinServer; /** * [简要描述]:
* [详细描述]:
* * @author llxiao * @version 1.0, 2018年8月9日 * @since JDK 1.8 */ @SpringBootApplication // 关联上配置中心 //@EnableEurekaClient // 加上注解@EnableZipkinServer,开启ZipkinServer的功能 //@EnableZipkinServer @EnableZipkinStreamServer public class ZipkinServerApplication { public static void main(String[] args) { SpringApplication.run(ZipkinServerApplication.class, args); } } ================================================ FILE: SpringCloud-ZipkinServer/src/main/resources/application.properties ================================================ #ʣƼ0.1ٷ֮ռĻ洢ܿס spring.sleuth.sampler.percentage=1 spring.sleuth.enabled=false maxHttpHeaderSize=8192 ### kafkaӺzk spring.cloud.stream.kafka.binder.brokers=192.168.206.203:9092 spring.cloud.stream.kafka.binder.zkNodes=192.168.206.203:2181 ## ʹes洢 zipkin.storage.StorageComponent=elasticsearch zipkin.storage.type=elasticsearch zipkin.storage.elasticsearch.hosts=192.168.206.204:9200 #esȺ zipkin.storage.elasticsearch.cluster=zipkin-es zipkin.storage.elasticsearch.index=zipkin-db zipkin.storage.elasticsearch.index-shards=5 zipkin.storage.elasticsearch.index-replicas=1 ================================================ FILE: SpringCloud-ZipkinServer/src/main/resources/application.yml ================================================ server: port: 1115 ##配置中心 eureka: client: serviceUrl: defaultZone: http://localhost:1111/eureka/ ###服务名称 spring: application: name: zipkin-server ================================================ FILE: SpringCloud-Zookeeper/pom.xml ================================================ SpringCloud-Demo com.xiao.skywalking.demo 0.0.1-SNAPSHOT 4.0.0 SpringCloud-Zookeeper ================================================ FILE: SpringCloud-Zookeeper/readme.md ================================================ Zookeeper一些学习记录:
1. **zookeeper HA 实现主备切换**,[原博文地址](http://blog.sina.com.cn/s/blog_1312c919b0102v1a9.html)
主要实现思路:
![avatar](https://github.com/Xlinlin/SpringCloud-Demo/blob/master/SpringCloud-Zookeeper/img/HA.png) >1.1 启动server时注册一个临时的有序的子节点(注意,一定要是临时有序的),将自己注册的子节点保存在一个全局变量中
>1.2 获取父节点下所有的子节点,排序,然后将自己的节点与最小子节点比较,如果相等则成为主机,不相等则等待。
>1.3 实现Watcher接口,当父节点发生变化时,执行上两个步骤
2. **zookeeper HA 实现负载均衡**,[原博文地址](http://blog.sina.com.cn/s/blog_1312c919b0102v1aa.html)
主要实现思路:
>2.1 注册:首先你需要确定一个父节点,在这里父节点的名称暂且就叫/parentNode;
每个server端启动时首先向zk的集群的父节点/parentNode下去注册一个临时的子节点,
这样当有N台server时,注册的子节点就是/parentNode/server1、/parentNode/server2.........、/parentNode/serverN。
节点的数据就存每台server的ip 与port,这样你服务端不管是用rmi协议还是http协议,都可以向这个服务器发送请求了。
>2.2 获取服务列表:在Client端实现轮询分发的功能,实现Watcher接口,这会让你实时的监控服务端的变化。
首先去获取父节点/parentNode下所有的子节点,得到之前存的ip与port,然后将这些列表缓存到一map中,
这里就叫serverUrlCacheMap。由于实现了Watcher接口,当父节点发生变化时zk 的集群会通知Client端,
此时Client端只要重新获取父节点下所有子节点的数据,重新缓存即可
>2.3 轮询分发:定义一个全局变量index ,每次发起请求时,直接去serverUrlCacheMap中获取这个编号的URL,
然后发送给server,就可达到轮询分发的功能。
3. **zookeeper 的EPHEMERAL节点机制实现服务集群的陷阱**,[原博文地址](https://yq.aliyun.com/articles/227260)
================================================ FILE: pom.xml ================================================ 4.0.0 com.xiao.skywalking.demo SpringCloud-Demo 0.0.1-SNAPSHOT pom 1.8 UTF-8 UTF-8 6.1.1 org.springframework.boot spring-boot-starter-parent 1.5.13.RELEASE SpringCloud-Common SpringCloud-Configure SpringCloud-ConfigCenter SpringCloud-Eureka SpringCloud-ZipkinServer SpringCloud-Gateway SpringCloud-Provider SpringCloud-Consumer SpringCloud-MQTT SpringCloud-Kafka-Elk SpringCloud-Demo-Doc SpringCloud-Mybatis SpringCloud-SearchService SpringCloud-Sharding-Sphere SpringCloud-Custom-ConfigCenter SpringCloud-Quartz-JobService SpringCloud-Redisson SpringCloud-Docker SpringCloud-Canal SpringCloud-Zookeeper SpringBoot-Admin SpringCloud-Sentinel SpringCloud-Hystrix-Demo SpringBoot-Custom-Elasticsearch-Starter SpringBoot-Stock-Demo SpringCloud-Custom-RestTemplate-Stater SpringBoot-Custom-Rest-Starter org.springframework.cloud spring-cloud-dependencies Edgware.SR4 pom import