Browse Source

Squashed commit of the following:

commit 0b9848a4f5b0d23096fcf37c3eb2cae0f25692ba
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Sep 27 13:57:48 2021 +0800

    补充core-retrofit组件与core-solar组件,完善solar启动校验机制,增加在线激活模式支持

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 563da25321ca6e3e6b667f248c23eeec7ab9ceef
Author: luoshi <luoshi@qmth.com.cn>
Date:   Sat Sep 18 10:39:02 2021 +0800

    增加core-solar组件;增加空白的core-retrofit组件;修改RSA相关方法,去掉对base64字符串的默认转换

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 0f7f3a29c8e8cd7ce0e1da05e3d42718d739a4e8
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Sep 16 14:36:28 2021 +0800

    修改tools-common中的RSA工具,改成链式调用接口

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit ccb741160310534251db0219aa095c20d277ee9d
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Sep 14 19:59:46 2021 +0800

    tools-common增加AES和RSA两套加解密工具

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 9fffde4cfd49cdf60d41818c450152f75aaae907
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Aug 12 11:24:08 2021 +0800

    core-fss组件增加path自动预处理,自动去除开头的分隔符并判空

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 50f74eb9405422264bb3b55960fe7c18d85b2405
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Aug 10 09:55:05 2021 +0800

    core-fss组件区分单存储模式与多存储模块,简化单存储场景使用复杂度;tools-common组件增加IOUtils工具,并扩展ByteArray支持功能

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit f002626534b996685d7c805323fc95f7c58d5d72
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Jul 28 18:27:05 2021 +0800

    core-models增加基础异常类型,优化starter-api统一异常处理

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit ddb0aebcb63c71b2c17b49c44a338d898dd3e9f2
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Jul 28 11:42:13 2021 +0800

    修改starter-api,增加对MethodArgumentNotValidException的特殊支持

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit c6a94efb9846486a343fe49de6d09e119331212d
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Jul 28 11:22:51 2021 +0800

    tools-common组件增加ByteArray工具,简化CodecUtils的使用

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 57be883830b0ceefcc0832a17b40a700657d7f48
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jul 19 15:26:38 2021 +0800

    修改core-fss组件,写入方法输入的md5格式兼容hex与base64两种,自动根据长度判断

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 9b78ac84826b1ad7254d96f6c9268a92dfbf11d6
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jul 19 10:48:27 2021 +0800

    修改core-fss与tools-common组件,DistStore修改写入逻辑,使用临时目录文件作为中间过渡,避免MD5校验失败对正式文件的异常覆盖

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit ced723a6ae769b5fc0a3e4f6bcb46338d7b9d2a4
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Jul 15 17:09:19 2021 +0800

    core-fss组件增加OSS私服支持,自动判断使用http前缀

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 097938009f942bceee72c31bf730f84463bce70f
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jul 12 13:43:59 2021 +0800

    core-cache组件提供默认的localCacheManager和localCacheService,供纯JVM内部场景使用

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit af7924d44abc684dd848279347aa7583827998bf
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jul 12 13:20:55 2021 +0800

    增加spring.factories配置,降低应用配置复杂度与包名限制

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit b8481f177733f506427c415397ed872738e60690
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Jun 29 15:02:46 2021 +0800

    修正部分组件bean注册冲突问题

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 790595478e9318808f6f3221e6cd3e77faf44657
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Jun 29 11:32:00 2021 +0800

    优化组件自动配置代码,去掉springboot启动异常提示

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit effa96f931ce01084c45ea80fd3dd968a893d57c
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jun 28 19:25:42 2021 +0800

    优化单元测试代码,临时注释依赖本地文件的测试注解

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 69e4f4d0b41e2fd9008f010a2ca20a7312cca568
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Jun 28 18:35:39 2021 +0800

    优化data-redis配置代码

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 4deec36601cf6b6f037542b5cd2dbade9ce6d5f7
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Jun 22 18:31:31 2021 +0800

    调整data-redis部分配置代码,增加暂时未启用的任务调度托管功能

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 0ec4d0d918d26ed30766cffe1748c0865b77d2a1
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed May 19 18:04:55 2021 +0800

    修改部分组件内部依赖

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 34b34057ca6ca4af0fec922aa8b210978c57be00
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed May 19 17:47:03 2021 +0800

    增加core-fss组件,提供通用文件存储服务,并提供阿里云OSS的实现支持;修改tools-common组件,扩充CodecUtils方法

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit c2f09426245b76373e2f88a191982d1bda182d38
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue May 18 17:26:00 2021 +0800

    修改所有组件,配置bean的命名风格统一修改为XxxProperties

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit e0570d4e03f24b7e9704edfdf74e7f5c9b1fbb4d
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri May 14 18:52:27 2021 +0800

    修改core-concurrent及redis实现组件,增加判断是否已上锁方法;修改redis锁实现,普通锁和读写锁区分不同前缀

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 0dd542439ed37894acbfc87f79234bc9a6e3f359
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri May 14 16:25:02 2021 +0800

    data-mysql-mp组件更名为data-mybatis-plus; 增加core-schedule组件; 修改core-cache组件注解配置

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 1d3c9859602bbad917fa0c84d03c9a2f3698bf4d
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Apr 20 10:31:17 2021 +0800

    增加PageListIterator作为分页读取对象的辅助迭代器实现

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit fdf9ec38b4e8776df88f83f82d56732813e216f3
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri Apr 16 18:55:24 2021 +0800

    调整tools-poi代码结构,修改excel生成方法传入参数,支持流式动态获取输出数据

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 0c7c03f8e9e5f62196b562e7e57470dd5f18c448
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri Apr 16 18:55:02 2021 +0800

    调整tools-poi代码结构,修改excel生成方法传入参数,支持流式动态获取输出数据

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 3b58a53b837b2c9938437c6867fc7cd2bdf3b379
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri Apr 16 17:10:04 2021 +0800

    重构tools-poi组件,增加excel输出工具,增加单元测试代码

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit 69da643ec388b3bb79b0812aac4df18d26d866b3
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Apr 14 17:14:23 2021 +0800

    增加ExamReader入口注释说明

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit bfecd205e47e0073b5be79eebfc60140594da02a
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Apr 14 14:47:43 2021 +0800

    增加core-poi组件,提供基于EventMode的Excel读取工具

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit e7e2d6f1e30f7484933ddab06507ea7a8a4b6619
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Apr 8 16:04:05 2021 +0800

    core-security组件支持注解形式注册鉴权服务:使用AuthorizationComponent注解,按访问路径前缀设置,与编程模式相同

    Signed-off-by: luoshi <luoshi@qmth.com.cn>

commit f0973a7bbe6995126f7c9f96f3236a3c7861d919
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Apr 8 14:12:22 2021 +0800

    更新版本号至1.0.1;缓存组件提供扩展自定义设置入口,允许针对个别缓存指定特殊的失效时间;

    Signed-off-by: luoshi <luoshi@qmth.com.cn>
luoshi 3 năm trước cách đây
mục cha
commit
d627c1d9c8
100 tập tin đã thay đổi với 2467 bổ sung286 xóa
  1. 1 1
      core-cache/pom.xml
  2. 82 0
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheAutoConfiguration.java
  3. 0 23
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheConfig.java
  4. 0 35
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheManagerConfig.java
  5. 2 5
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheProperties.java
  6. 19 0
      core-cache/src/main/java/com/qmth/boot/core/cache/service/CacheConfigRegistration.java
  7. 14 0
      core-cache/src/main/java/com/qmth/boot/core/cache/service/CustomizeCacheConfiguration.java
  8. 4 4
      core-cache/src/main/java/com/qmth/boot/core/cache/service/impl/CacheServiceImpl.java
  9. 2 0
      core-cache/src/main/resources/META-INF/spring.factories
  10. 5 5
      core-concurrent/pom.xml
  11. 2 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/configuration/ConcurrentAutoConfiguration.java
  12. 24 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/ConcurrentService.java
  13. 45 12
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/impl/MemoryConcurrentService.java
  14. 2 0
      core-concurrent/src/main/resources/META-INF/spring.factories
  15. 66 0
      core-fss/pom.xml
  16. 19 0
      core-fss/src/main/java/com/qmth/boot/core/fss/condition/FssCondition.java
  17. 26 0
      core-fss/src/main/java/com/qmth/boot/core/fss/condition/FssConditionExecutor.java
  18. 31 0
      core-fss/src/main/java/com/qmth/boot/core/fss/config/FileStoreProperty.java
  19. 22 0
      core-fss/src/main/java/com/qmth/boot/core/fss/config/FileStorePropertyMap.java
  20. 51 0
      core-fss/src/main/java/com/qmth/boot/core/fss/config/FssAutoConfiguration.java
  21. 22 0
      core-fss/src/main/java/com/qmth/boot/core/fss/service/FileService.java
  22. 48 0
      core-fss/src/main/java/com/qmth/boot/core/fss/service/impl/DefaultFileService.java
  23. 104 0
      core-fss/src/main/java/com/qmth/boot/core/fss/store/FileStore.java
  24. 96 0
      core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/DiskStore.java
  25. 110 0
      core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/OssStore.java
  26. 22 0
      core-fss/src/main/java/com/qmth/boot/core/fss/utils/FileStoreBuilder.java
  27. 19 0
      core-fss/src/main/java/com/qmth/boot/core/fss/utils/FssUtils.java
  28. 2 0
      core-fss/src/main/resources/META-INF/spring.factories
  29. 24 0
      core-fss/src/test/java/com/qmth/boot/test/core/fss/DiskStoreTest.java
  30. 36 0
      core-fss/src/test/java/com/qmth/boot/test/core/fss/OssStoreTest.java
  31. 5 6
      core-logging/pom.xml
  32. 10 0
      core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerAutoConfiguration.java
  33. 0 18
      core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerConfig.java
  34. 2 9
      core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerProperties.java
  35. 10 10
      core-logging/src/main/java/com/qmth/boot/core/logger/context/LoggerContextService.java
  36. 2 0
      core-logging/src/main/resources/META-INF/spring.factories
  37. 1 1
      core-metrics/pom.xml
  38. 19 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsAutoConfiguration.java
  39. 0 16
      core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsConfig.java
  40. 10 3
      core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsProperties.java
  41. 9 22
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MemoryMetricsService.java
  42. 3 3
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MetricsRecorder.java
  43. 2 0
      core-metrics/src/main/resources/META-INF/spring.factories
  44. 4 6
      core-metrics/src/test/java/com/qmth/boot/test/core/metrics/MetricsTest.java
  45. 1 1
      core-models/pom.xml
  46. 1 0
      core-models/src/main/java/com/qmth/boot/core/exception/ReentrantException.java
  47. 59 0
      core-models/src/main/java/com/qmth/boot/core/exception/StatusException.java
  48. 3 3
      core-rate-limit/pom.xml
  49. 19 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitAutoConfiguration.java
  50. 1 1
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitRule.java
  51. 1 1
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitScope.java
  52. 1 1
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitTarget.java
  53. 1 1
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitPolicy.java
  54. 1 1
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitService.java
  55. 0 23
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/EmptyRateLimitPolicy.java
  56. 2 12
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/MemoryRateLimitService.java
  57. 2 0
      core-rate-limit/src/main/resources/META-INF/spring.factories
  58. 3 3
      core-rate-limit/src/test/java/com/qmth/boot/test/core/rateLimit/RateLimitTest.java
  59. 65 0
      core-retrofit/pom.xml
  60. 43 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/annotatioin/RetrofitClient.java
  61. 37 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/annotatioin/RetrofitScan.java
  62. 32 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/PoolProperties.java
  63. 10 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetrofitAutoConfiguration.java
  64. 80 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetrofitProperties.java
  65. 32 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetryProperties.java
  66. 72 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/ClassPathRetrofitClientScanner.java
  67. 42 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/DirectCallAdapterFactory.java
  68. 74 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitClientScannerRegistrar.java
  69. 121 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitFactoryBean.java
  70. 33 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitInvocationHandler.java
  71. 13 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/exception/RetrofitConvertError.java
  72. 37 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/exception/RetrofitResponseError.java
  73. 82 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/ErrorDecodeInterceptor.java
  74. 42 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/LoggingInterceptor.java
  75. 47 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/RetryInterceptor.java
  76. 38 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/SignatureInterceptor.java
  77. 30 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interfaces/CustomizeRetrofitConfiguration.java
  78. 16 0
      core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interfaces/SignatureProvider.java
  79. 2 0
      core-retrofit/src/main/resources/META-INF/spring.factories
  80. 33 0
      core-schedule/pom.xml
  81. 25 0
      core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ExecutorProperties.java
  82. 36 0
      core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ScheduleAutoConfiguration.java
  83. 25 0
      core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ScheduleProperties.java
  84. 2 0
      core-schedule/src/main/resources/META-INF/spring.factories
  85. 1 1
      core-security/pom.xml
  86. 30 0
      core-security/src/main/java/com/qmth/boot/core/security/annotation/AuthorizationComponent.java
  87. 10 0
      core-security/src/main/java/com/qmth/boot/core/security/config/SecurityAutoConfiguration.java
  88. 0 21
      core-security/src/main/java/com/qmth/boot/core/security/config/SecurityConfig.java
  89. 61 0
      core-security/src/main/java/com/qmth/boot/core/security/config/SecurityContextListener.java
  90. 2 5
      core-security/src/main/java/com/qmth/boot/core/security/config/SecurityProperties.java
  91. 6 2
      core-security/src/main/java/com/qmth/boot/core/security/model/AccessEntity.java
  92. 2 2
      core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationService.java
  93. 16 21
      core-security/src/main/java/com/qmth/boot/core/security/service/impl/BaseAuthorizationSupport.java
  94. 2 0
      core-security/src/main/resources/META-INF/spring.factories
  95. 7 8
      core-security/src/test/java/com/qmth/boot/test/core/security/AuthTest.java
  96. 50 0
      core-solar/pom.xml
  97. 20 0
      core-solar/src/main/java/com/qmth/boot/core/solar/api/SolarApiClient.java
  98. 38 0
      core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarApiConfiguration.java
  99. 17 0
      core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarAutoConfiguration.java
  100. 64 0
      core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarProperties.java

+ 1 - 1
core-cache/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <artifactId>qmth-boot</artifactId>
         <groupId>com.qmth.boot</groupId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-cache</artifactId>
     <name>core-uid</name>

+ 82 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheAutoConfiguration.java

@@ -0,0 +1,82 @@
+package com.qmth.boot.core.cache.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.qmth.boot.core.cache.service.CacheConfigRegistration;
+import com.qmth.boot.core.cache.service.CacheService;
+import com.qmth.boot.core.cache.service.CustomizeCacheConfiguration;
+import com.qmth.boot.core.cache.service.impl.CacheServiceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+
+/**
+ * 自定义缓存配置,缺省启用Caffeine作为单实例缓存实现
+ */
+@Configuration
+@EnableCaching
+@ComponentScan("com.qmth.boot.core.cache.*")
+public class CacheAutoConfiguration {
+
+    private static final Logger log = LoggerFactory.getLogger(CacheAutoConfiguration.class);
+
+    @Bean
+    @Primary
+    @ConditionalOnMissingBean(name = "cacheManager")
+    public CacheManager cacheManager(CacheManager localCacheManager) {
+        return localCacheManager;
+    }
+
+    @Bean
+    public CacheService localCacheService(CacheManager localCacheManager) {
+        return new CacheServiceImpl(localCacheManager);
+    }
+
+    @Bean
+    public CacheService cacheService(CacheManager cacheManager) {
+        return new CacheServiceImpl(cacheManager);
+    }
+
+    @Bean
+    public CacheManager localCacheManager(CacheProperties cacheProperties,
+            @Nullable CustomizeCacheConfiguration customizeCacheConfiguration) {
+        final CaffeineCacheManager cacheManager = new CaffeineCacheManager();
+        //默认设置
+        cacheManager.setAllowNullValues(cacheProperties.isAllowNullValue());
+        cacheManager.setCaffeine(buildCaffeineConfig(cacheProperties.getExpireAfterWrite()));
+        //自定义缓存设置
+        if (customizeCacheConfiguration != null) {
+            customizeCacheConfiguration.register(new CacheConfigRegistration() {
+
+                @Override
+                public CacheConfigRegistration setCacheConfig(String name, Duration expireAfterWrite) {
+                    log.info("Customize cache configuration: name={}, expireAfterWrite={}", name,
+                            expireAfterWrite.toString());
+                    cacheManager.registerCustomCache(name, buildCaffeineConfig(expireAfterWrite).build());
+                    return this;
+                }
+            });
+        }
+        log.info("Local cache manager inited.");
+        return cacheManager;
+    }
+
+    private Caffeine<Object, Object> buildCaffeineConfig(Duration expireAfterWrite) {
+        //默认开启softValues,避免JVM撑爆
+        Caffeine<Object, Object> caffeine = Caffeine.newBuilder().softValues();
+        //写入失效时长大于0时才启用
+        if (expireAfterWrite.toMillis() > 0) {
+            caffeine = caffeine.expireAfterWrite(expireAfterWrite);
+        }
+        return caffeine;
+    }
+}

+ 0 - 23
core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheConfig.java

@@ -1,23 +0,0 @@
-package com.qmth.boot.core.cache.config;
-
-import java.time.Duration;
-
-/**
- * 缓存通用配置参数
- */
-public interface CacheConfig {
-
-    /**
-     * 是否允许空值缓存
-     *
-     * @return
-     */
-    boolean isAllowNullValue();
-
-    /**
-     * 缓存写入后失效时长
-     *
-     * @return
-     */
-    Duration getExpireAfterWrite();
-}

+ 0 - 35
core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheManagerConfig.java

@@ -1,35 +0,0 @@
-package com.qmth.boot.core.cache.config;
-
-import com.github.benmanes.caffeine.cache.Caffeine;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.cache.CacheManager;
-import org.springframework.cache.caffeine.CaffeineCacheManager;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import javax.annotation.Resource;
-
-/**
- * 自定义缓存配置,缺省启用Caffeine作为单实例缓存实现
- */
-@Configuration
-public class CacheManagerConfig {
-
-    @Resource
-    private CacheConfig cacheConfig;
-
-    @Bean
-    @ConditionalOnMissingBean(CacheManager.class)
-    public CacheManager cacheManager() {
-        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
-        cacheManager.setAllowNullValues(cacheConfig.isAllowNullValue());
-        //默认开启softValues,避免JVM撑爆
-        Caffeine<Object, Object> caffeine = Caffeine.newBuilder().softValues();
-        //写入失效时长大于0毫秒时才启用
-        if (cacheConfig.getExpireAfterWrite().toMillis() > 0) {
-            caffeine = caffeine.expireAfterWrite(cacheConfig.getExpireAfterWrite());
-        }
-        cacheManager.setCaffeine(caffeine);
-        return cacheManager;
-    }
-}

+ 2 - 5
core-cache/src/main/java/com/qmth/boot/core/cache/config/bean/CacheConfigBean.java → core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheProperties.java

@@ -1,7 +1,5 @@
-package com.qmth.boot.core.cache.config.bean;
+package com.qmth.boot.core.cache.config;
 
-import com.qmth.boot.core.cache.config.CacheConfig;
-import com.qmth.boot.core.cache.constant.CacheConstant;
 import com.qmth.boot.core.constant.CoreConstant;
 import org.hibernate.validator.constraints.time.DurationMin;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -14,7 +12,7 @@ import java.time.Duration;
 @Component
 @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".cache")
 @Validated
-public class CacheConfigBean implements CacheConfig, CacheConstant {
+public class CacheProperties {
 
     private boolean allowNullValue = true;
 
@@ -30,7 +28,6 @@ public class CacheConfigBean implements CacheConfig, CacheConstant {
         this.allowNullValue = allowNullValue;
     }
 
-    @Override
     public Duration getExpireAfterWrite() {
         return expireAfterWrite;
     }

+ 19 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/service/CacheConfigRegistration.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.cache.service;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+/**
+ * 缓存自定义设置工具
+ */
+public interface CacheConfigRegistration {
+
+    /**
+     * 设置自定义缓存,按照缓存名设置写入失效时长
+     *
+     * @param name
+     * @param expireAfterWrite
+     * @return
+     */
+    CacheConfigRegistration setCacheConfig(@NotNull String name, @NotNull Duration expireAfterWrite);
+}

+ 14 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/service/CustomizeCacheConfiguration.java

@@ -0,0 +1,14 @@
+package com.qmth.boot.core.cache.service;
+
+/**
+ * 自定义缓存设置注册入口
+ */
+public interface CustomizeCacheConfiguration {
+
+    /**
+     * 使用传入的注册工具设置自定义缓存
+     *
+     * @param cacheConfigRegistration
+     */
+    void register(CacheConfigRegistration cacheConfigRegistration);
+}

+ 4 - 4
core-cache/src/main/java/com/qmth/boot/core/cache/service/impl/CacheServiceImpl.java

@@ -3,17 +3,17 @@ package com.qmth.boot.core.cache.service.impl;
 import com.qmth.boot.core.cache.service.CacheService;
 import org.springframework.cache.Cache;
 import org.springframework.cache.CacheManager;
-import org.springframework.stereotype.Service;
 
-import javax.annotation.Resource;
 import java.util.Collection;
 
-@Service
 public class CacheServiceImpl implements CacheService {
 
-    @Resource
     private CacheManager cacheManager;
 
+    public CacheServiceImpl(CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+    }
+
     @Override
     public Collection<String> endpoints() {
         return cacheManager.getCacheNames();

+ 2 - 0
core-cache/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.cache.config.CacheAutoConfiguration

+ 5 - 5
core-concurrent/pom.xml

@@ -6,12 +6,16 @@
     <parent>
         <artifactId>qmth-boot</artifactId>
         <groupId>com.qmth.boot</groupId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-concurrent</artifactId>
     <name>core-concurrent</name>
 
     <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
             <artifactId>core-models</artifactId>
@@ -32,10 +36,6 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

+ 2 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/configuration/ConcurrentAutoConfiguration.java

@@ -4,9 +4,11 @@ import com.qmth.boot.core.concurrent.service.ConcurrentService;
 import com.qmth.boot.core.concurrent.service.impl.MemoryConcurrentService;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
+@ComponentScan("com.qmth.boot.core.concurrent.*")
 public class ConcurrentAutoConfiguration {
 
     @Bean

+ 24 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/ConcurrentService.java

@@ -16,6 +16,14 @@ public interface ConcurrentService {
      */
     Lock getLock(String name);
 
+    /**
+     * 普通锁是否已上锁
+     *
+     * @param name
+     * @return
+     */
+    boolean isLocked(String name);
+
     /**
      * 获取读写锁
      *
@@ -24,4 +32,20 @@ public interface ConcurrentService {
      */
     ReadWriteLock getReadWriteLock(String name);
 
+    /**
+     * 读锁是否已上锁
+     *
+     * @param name
+     * @return
+     */
+    boolean isReadLocked(String name);
+
+    /**
+     * 写锁是否已上锁
+     *
+     * @param name
+     * @return
+     */
+    boolean isWriteLocked(String name);
+
 }

+ 45 - 12
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/impl/MemoryConcurrentService.java

@@ -1,8 +1,8 @@
 package com.qmth.boot.core.concurrent.service.impl;
 
 import com.qmth.boot.core.concurrent.service.ConcurrentService;
-import org.springframework.stereotype.Service;
 
+import javax.annotation.PreDestroy;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.locks.Lock;
@@ -10,16 +10,25 @@ import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
-@Service
 public class MemoryConcurrentService implements ConcurrentService {
 
-    private final Map<String, ReentrantLock> lockMap = new HashMap<>();
+    private final Map<String, ReentrantLock> lockMap;
 
-    private final Map<String, ReentrantReadWriteLock> readWrite = new HashMap<>();
+    private final Map<String, ReentrantReadWriteLock> readWriteMap;
 
-    @Override
-    public Lock getLock(String name) {
-        Lock lock = lockMap.get(name);
+    public MemoryConcurrentService() {
+        this.lockMap = new HashMap<>();
+        this.readWriteMap = new HashMap<>();
+    }
+
+    @PreDestroy
+    public void close() {
+        this.lockMap.clear();
+        this.readWriteMap.clear();
+    }
+
+    private ReentrantLock lock(String name) {
+        ReentrantLock lock = lockMap.get(name);
         if (lock == null) {
             synchronized (lockMap) {
                 lock = lockMap.computeIfAbsent(name, key -> new ReentrantLock());
@@ -28,14 +37,38 @@ public class MemoryConcurrentService implements ConcurrentService {
         return lock;
     }
 
-    @Override
-    public ReadWriteLock getReadWriteLock(String name) {
-        ReadWriteLock lock = readWrite.get(name);
+    private ReentrantReadWriteLock readWriteLock(String name) {
+        ReentrantReadWriteLock lock = readWriteMap.get(name);
         if (lock == null) {
-            synchronized (readWrite) {
-                lock = readWrite.computeIfAbsent(name, key -> new ReentrantReadWriteLock());
+            synchronized (readWriteMap) {
+                lock = readWriteMap.computeIfAbsent(name, key -> new ReentrantReadWriteLock());
             }
         }
         return lock;
     }
+
+    @Override
+    public Lock getLock(String name) {
+        return lock(name);
+    }
+
+    @Override
+    public boolean isLocked(String name) {
+        return lock(name).isLocked();
+    }
+
+    @Override
+    public ReadWriteLock getReadWriteLock(String name) {
+        return readWriteLock(name);
+    }
+
+    @Override
+    public boolean isReadLocked(String name) {
+        return readWriteLock(name).getReadLockCount() > 0;
+    }
+
+    @Override
+    public boolean isWriteLocked(String name) {
+        return readWriteLock(name).isWriteLocked();
+    }
 }

+ 2 - 0
core-concurrent/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.concurrent.configuration.ConcurrentAutoConfiguration

+ 66 - 0
core-fss/pom.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>qmth-boot</artifactId>
+        <groupId>com.qmth.boot</groupId>
+        <version>1.0.1</version>
+    </parent>
+    <artifactId>core-fss</artifactId>
+    <name>core-fss</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.xml.bind</groupId>
+            <artifactId>jaxb-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-runtime</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 19 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/condition/FssCondition.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.fss.condition;
+
+import org.springframework.context.annotation.Conditional;
+
+import java.lang.annotation.*;
+
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Conditional(FssConditionExecutor.class)
+public @interface FssCondition {
+
+    /**
+     * 是否单FileStore配置模式
+     *
+     * @return
+     */
+    boolean single();
+}

+ 26 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/condition/FssConditionExecutor.java

@@ -0,0 +1,26 @@
+package com.qmth.boot.core.fss.condition;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class FssConditionExecutor implements Condition {
+
+    private static String PROPERTY_CONFIG = CoreConstant.CONFIG_PREFIX + ".fss.config";
+
+    private static String PROPERTY_SERVER = CoreConstant.CONFIG_PREFIX + ".fss.server";
+
+    @Override
+    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
+        Boolean single = (Boolean) metadata.getAnnotationAttributes(FssCondition.class.getName()).get("single");
+        if (single) {
+            return StringUtils.isNotBlank(context.getEnvironment().getProperty(PROPERTY_CONFIG)) && StringUtils
+                    .isNotBlank(context.getEnvironment().getProperty(PROPERTY_SERVER));
+        } else {
+            return StringUtils.isBlank(context.getEnvironment().getProperty(PROPERTY_CONFIG)) && StringUtils
+                    .isBlank(context.getEnvironment().getProperty(PROPERTY_SERVER));
+        }
+    }
+}

+ 31 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/config/FileStoreProperty.java

@@ -0,0 +1,31 @@
+package com.qmth.boot.core.fss.config;
+
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+
+@Validated
+public class FileStoreProperty {
+
+    @NotNull
+    private String config;
+
+    @NotNull
+    private String server;
+
+    public String getConfig() {
+        return config;
+    }
+
+    public void setConfig(String config) {
+        this.config = config;
+    }
+
+    public String getServer() {
+        return server;
+    }
+
+    public void setServer(String server) {
+        this.server = server;
+    }
+}

+ 22 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/config/FileStorePropertyMap.java

@@ -0,0 +1,22 @@
+package com.qmth.boot.core.fss.config;
+
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.util.HashMap;
+import java.util.Map;
+
+@Validated
+public class FileStorePropertyMap {
+
+    @NotNull
+    private Map<String, FileStoreProperty> fss = new HashMap<>();
+
+    public Map<String, FileStoreProperty> getFss() {
+        return fss;
+    }
+
+    public void setFss(Map<String, FileStoreProperty> fss) {
+        this.fss = fss;
+    }
+}

+ 51 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/config/FssAutoConfiguration.java

@@ -0,0 +1,51 @@
+package com.qmth.boot.core.fss.config;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import com.qmth.boot.core.fss.condition.FssCondition;
+import com.qmth.boot.core.fss.service.FileService;
+import com.qmth.boot.core.fss.service.impl.DefaultFileService;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.boot.core.fss.utils.FileStoreBuilder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FssAutoConfiguration {
+
+    /**
+     * 配置了多个FileStore的情况
+     *
+     * @return
+     */
+    @Bean
+    @FssCondition(single = false)
+    @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX)
+    public FileStorePropertyMap fssMultiProperties() {
+        return new FileStorePropertyMap();
+    }
+
+    @Bean(destroyMethod = "close")
+    @FssCondition(single = false)
+    public FileService fileService(FileStorePropertyMap fileStorePropertyMap) {
+        return new DefaultFileService(fileStorePropertyMap.getFss());
+    }
+
+    /**
+     * 只配置了单个FileStore的情况
+     *
+     * @return
+     */
+    @Bean
+    @FssCondition(single = true)
+    @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".fss")
+    public FileStoreProperty fileStoreProperty() {
+        return new FileStoreProperty();
+    }
+
+    @Bean(destroyMethod = "close")
+    @FssCondition(single = true)
+    public FileStore fileStore(FileStoreProperty fileStoreProperty) {
+        return FileStoreBuilder.buildFileStore(fileStoreProperty);
+    }
+}

+ 22 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/service/FileService.java

@@ -0,0 +1,22 @@
+package com.qmth.boot.core.fss.service;
+
+import com.qmth.boot.core.fss.store.FileStore;
+
+/**
+ * 文件存储服务聚合入口
+ */
+public interface FileService {
+
+    /**
+     * 根据配置文件中预设置的名称,获取实际的文件存储服务
+     *
+     * @param name
+     * @return
+     */
+    FileStore getFileStore(String name);
+
+    /**
+     * 服务销毁入口
+     */
+    void close();
+}

+ 48 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/service/impl/DefaultFileService.java

@@ -0,0 +1,48 @@
+package com.qmth.boot.core.fss.service.impl;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import com.qmth.boot.core.fss.config.FileStoreProperty;
+import com.qmth.boot.core.fss.service.FileService;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.boot.core.fss.utils.FileStoreBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PreDestroy;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultFileService implements FileService {
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultFileService.class);
+
+    private Map<String, FileStore> storeMap;
+
+    public DefaultFileService(Map<String, FileStoreProperty> map) {
+        this.storeMap = new HashMap<>();
+        if (map.isEmpty()) {
+            log.warn("Property [" + CoreConstant.CONFIG_PREFIX + ".fss] is empty!");
+            return;
+        }
+        for (Map.Entry<String, FileStoreProperty> entry : map.entrySet()) {
+            storeMap.put(entry.getKey(), FileStoreBuilder.buildFileStore(entry.getValue()));
+        }
+    }
+
+    public void close() {
+        this.storeMap.clear();
+    }
+
+    @Override
+    public FileStore getFileStore(String name) {
+        return storeMap.get(name);
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        for (FileStore store : storeMap.values()) {
+            store.close();
+        }
+        storeMap.clear();
+    }
+}

+ 104 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/store/FileStore.java

@@ -0,0 +1,104 @@
+package com.qmth.boot.core.fss.store;
+
+import com.qmth.boot.core.fss.utils.FssUtils;
+import com.qmth.boot.tools.models.ByteArray;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.InputStream;
+import java.time.Duration;
+
+/**
+ * 通用文件存储服务
+ */
+public interface FileStore {
+
+    /**
+     * 关闭服务
+     */
+    default void close() {
+    }
+
+    /**
+     * 将md5内容转换为base64格式
+     *
+     * @param md5
+     * @return
+     */
+    default String toBase64(String md5) {
+        if (StringUtils.isBlank(md5)) {
+            throw new IllegalArgumentException("Invalid md5 parameter");
+        }
+        //hex格式
+        if (md5.length() == 32) {
+            return ByteArray.fromHexString(md5).toBase64();
+        }
+        //base64格式
+        else if (md5.length() == 24) {
+            return md5;
+        }
+        throw new IllegalArgumentException("Invalid md5 length");
+    }
+
+    /**
+     * 自动预处理path,去掉开头的分隔符并判空
+     *
+     * @param path
+     * @return
+     */
+    default String formatPath(String path) {
+        path = StringUtils.trimToNull(path);
+        if (path == null) {
+            throw new IllegalArgumentException("Invalid path parameter");
+        }
+        if (path.startsWith(FssUtils.FILE_PATH_SEPARATOR)) {
+            path = path.substring(1);
+        }
+        return path;
+    }
+
+    /**
+     * 获取临时授权访问URL
+     *
+     * @param path
+     * @param expireDuration
+     * @return
+     */
+    default String getTemporaryUrl(String path, Duration expireDuration) {
+        return getServer().concat(formatPath(path));
+    }
+
+    /**
+     * 获取公开访问URL前缀
+     *
+     * @return
+     */
+    String getServer();
+
+    /**
+     * 从输入流读取内容写入到文件存储的指定位置,强制进行md5校验
+     *
+     * @param path
+     * @param ins
+     * @param md5
+     * @throws Exception
+     */
+    void write(String path, InputStream ins, String md5) throws Exception;
+
+    /**
+     * 从文件存储的指定位置读取文件流,用完后需要手动关闭
+     *
+     * @param path
+     * @return
+     * @throws Exception
+     */
+    InputStream read(String path) throws Exception;
+
+    /**
+     * 判断文件存储中指定文件是否存在
+     *
+     * @param path
+     * @return
+     */
+    boolean exist(String path);
+
+}

+ 96 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/DiskStore.java

@@ -0,0 +1,96 @@
+package com.qmth.boot.core.fss.store.impl;
+
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.boot.tools.codec.CodecUtils;
+import com.qmth.boot.tools.io.IOUtils;
+import com.qmth.boot.tools.uuid.FastUUID;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+/**
+ * 本地磁盘文件管理工具
+ */
+public class DiskStore implements FileStore {
+
+    private String server;
+
+    private File rootDir;
+
+    private File tempDir;
+
+    public DiskStore(String server, String path, String temp) {
+        this.server = server;
+        this.rootDir = new File(path);
+        this.tempDir = new File(temp);
+        if (!rootDir.exists() && !rootDir.mkdirs()) {
+            //自动创建目录失败
+            throw new IllegalArgumentException("Fss disk store: " + path + ": auto mkdir faile");
+        } else if (rootDir.isFile()) {
+            //判断是否不是目录
+            throw new IllegalArgumentException("Fss disk store: " + path + ": is a file");
+        }
+        if (!tempDir.exists() && !tempDir.mkdirs()) {
+            //自动创建目录失败
+            throw new IllegalArgumentException("Fss disk store: temp auto mkdir faile");
+        } else if (tempDir.isFile()) {
+            //判断是否不是目录
+            throw new IllegalArgumentException("Fss disk store: temp dir is a file");
+        }
+    }
+
+    @Override
+    public String getServer() {
+        return server;
+    }
+
+    @Override
+    public void write(String path, InputStream ins, String md5) throws Exception {
+        path = formatPath(path);
+        //临时目录创建临时文件
+        File tempFile = new File(tempDir, FastUUID.get());
+        try {
+            final MessageDigest digest = CodecUtils.getMd5();
+            FileOutputStream ous = new FileOutputStream(tempFile);
+            IOUtils.copy(ins, ous, (buffer, length) -> digest.update(buffer, 0, length));
+            //校验写入内容的摘要信息
+            if (!CodecUtils.toBase64(digest.digest()).equalsIgnoreCase(toBase64(md5))) {
+                throw new RuntimeException("Write md5 validate faile");
+            }
+            IOUtils.closeQuietly(ins);
+            IOUtils.closeQuietly(ous);
+            //检查正式文件及目录
+            File targetFile = new File(rootDir, path);
+            if (targetFile.isDirectory()) {
+                throw new RuntimeException("Target file is directory: " + path);
+            }
+            targetFile.getParentFile().mkdirs();
+            //临时文件拷贝到正式文件
+            IOUtils.copy(tempFile, targetFile);
+        } finally {
+            tempFile.delete();
+        }
+    }
+
+    @Override
+    public InputStream read(String path) throws Exception {
+        path = formatPath(path);
+        File file = new File(rootDir, path);
+        if (file.exists() && file.isFile()) {
+            return new FileInputStream(file);
+        } else {
+            throw new RuntimeException("Read file unexist:" + path);
+        }
+    }
+
+    @Override
+    public boolean exist(String path) {
+        path = formatPath(path);
+        File file = new File(rootDir, path);
+        return file.exists() && file.isFile();
+    }
+
+}

+ 110 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/OssStore.java

@@ -0,0 +1,110 @@
+package com.qmth.boot.core.fss.store.impl;
+
+import com.aliyun.oss.ClientBuilderConfiguration;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.OSSObject;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.qmth.boot.core.fss.store.FileStore;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.Assert;
+
+import java.io.InputStream;
+import java.time.Duration;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * OSS文件管理工具
+ */
+public class OssStore implements FileStore {
+
+    private static final String ENDPOINT_PREFIX_HTTPS = "https://";
+
+    private static final String ENDPOINT_PREFIX_HTTP = "http://";
+
+    private static final Pattern CONFIG_PATTERN = Pattern.compile("^oss://([\\w]+):([\\w]+)@([\\w-]+).([\\w-.]+)$");
+
+    private String server;
+
+    private String bucket;
+
+    private OSS ossClient;
+
+    private OSS temporaryUrlClient;
+
+    public OssStore(String server, String config) {
+        String message = "fss.config: " + config + ": pattern error";
+        Matcher m = CONFIG_PATTERN.matcher(config);
+        if (m.find()) {
+            String accessKey = StringUtils.trimToNull(m.group(1));
+            String accessSecret = StringUtils.trimToNull(m.group(2));
+            this.bucket = StringUtils.trimToNull(m.group(3));
+            String endpoint = StringUtils.trimToNull(m.group(4));
+
+            Assert.notNull(accessKey, message);
+            Assert.notNull(accessSecret, message);
+            Assert.notNull(bucket, message);
+            Assert.notNull(endpoint, message);
+
+            this.server = server;
+            //判断是否阿里云OSS地址还是私服,使用不同的前缀
+            if (endpoint.contains("aliyun")) {
+                endpoint = ENDPOINT_PREFIX_HTTPS.concat(endpoint);
+            } else {
+                endpoint = ENDPOINT_PREFIX_HTTP.concat(endpoint);
+            }
+            this.ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessSecret);
+            //为支持私有读的bucket,构造专门用于获取临时访问URL的client
+            ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
+            conf.setSupportCname(true);
+            this.temporaryUrlClient = new OSSClientBuilder().build(server, accessKey, accessSecret, conf);
+        } else {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    @Override
+    public String getServer() {
+        return server;
+    }
+
+    @Override
+    public String getTemporaryUrl(String path, Duration expireDuration) {
+        return temporaryUrlClient.generatePresignedUrl(bucket, formatPath(path),
+                new Date(System.currentTimeMillis() + expireDuration.toMillis())).toString();
+    }
+
+    @Override
+    public void write(String path, InputStream ins, String md5) throws Exception {
+        ObjectMetadata metadata = new ObjectMetadata();
+        metadata.setContentMD5(toBase64(md5));
+        ossClient.putObject(bucket, formatPath(path), ins, metadata);
+    }
+
+    @Override
+    public InputStream read(String path) throws Exception {
+        OSSObject ossObject = ossClient.getObject(bucket, formatPath(path));
+        return ossObject.getObjectContent();
+    }
+
+    @Override
+    public boolean exist(String path) {
+        try {
+            return ossClient.doesObjectExist(bucket, formatPath(path));
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void close() {
+        if (ossClient != null) {
+            ossClient.shutdown();
+        }
+        if (temporaryUrlClient != null) {
+            temporaryUrlClient.shutdown();
+        }
+    }
+}

+ 22 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/utils/FileStoreBuilder.java

@@ -0,0 +1,22 @@
+package com.qmth.boot.core.fss.utils;
+
+import com.qmth.boot.core.fss.config.FileStoreProperty;
+import com.qmth.boot.core.fss.store.FileStore;
+import com.qmth.boot.core.fss.store.impl.DiskStore;
+import com.qmth.boot.core.fss.store.impl.OssStore;
+
+public class FileStoreBuilder {
+
+    public static FileStore buildFileStore(FileStoreProperty fileStoreProperty) {
+        String server = fileStoreProperty.getServer();
+        if (!server.endsWith(FssUtils.FILE_PATH_SEPARATOR)) {
+            server = server.concat(FssUtils.FILE_PATH_SEPARATOR);
+        }
+        String config = fileStoreProperty.getConfig();
+        if (config.startsWith("oss://")) {
+            return new OssStore(server, config);
+        } else {
+            return new DiskStore(server, config, System.getProperty("java.io.tmpdir"));
+        }
+    }
+}

+ 19 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/utils/FssUtils.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.fss.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class FssUtils {
+
+    public static final String FILE_PATH_SEPARATOR = "/";
+
+    /**
+     * 根据所有层级的目录/文件名称,构造标准的文件路径,用于文件存储的读取/写入
+     *
+     * @param names
+     * @return
+     */
+    public static String buildPath(String... names) {
+        return StringUtils.join(names, FILE_PATH_SEPARATOR);
+    }
+
+}

+ 2 - 0
core-fss/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.fss.config.FssAutoConfiguration

+ 24 - 0
core-fss/src/test/java/com/qmth/boot/test/core/fss/DiskStoreTest.java

@@ -0,0 +1,24 @@
+package com.qmth.boot.test.core.fss;
+
+import com.qmth.boot.core.fss.store.impl.DiskStore;
+import com.qmth.boot.tools.models.ByteArray;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+public class DiskStoreTest {
+
+    @Test
+    public void test() throws Exception {
+        DiskStore store = new DiskStore("http://server.com", System.getProperty("java.io.tmpdir"),
+                System.getProperty("java.io.tmpdir"));
+        String content = RandomStringUtils.random(128, true, true);
+        byte[] data = content.getBytes(StandardCharsets.UTF_8);
+        store.write("/1/123.txt", new ByteArrayInputStream(data), ByteArray.md5(data).toBase64());
+        Assert.assertTrue(store.exist("1/123.txt"));
+        Assert.assertEquals(content, ByteArray.fromInputStream(store.read("1/123.txt")).toString());
+    }
+}

+ 36 - 0
core-fss/src/test/java/com/qmth/boot/test/core/fss/OssStoreTest.java

@@ -0,0 +1,36 @@
+package com.qmth.boot.test.core.fss;
+
+import com.qmth.boot.core.fss.store.impl.OssStore;
+import com.qmth.boot.tools.models.ByteArray;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.time.Duration;
+
+public class OssStoreTest {
+
+    private String server_cloud = "https://qmth-test.oss-cn-shenzhen.aliyuncs.com";
+
+    private String server_local = "http://oss-file.qmth.com.cn/test";
+
+    private String config_cloud = "oss://LTAI4FnJ2pgV6aGceYcCkeEi:ktrMEVE7PfoxRPeJUPDFeygOIH4aU7@qmth-test.oss-cn-shenzhen.aliyuncs.com";
+
+    private String config_local = "oss://key:secret@test.oss-api.qmth.com.cn";
+
+    @Test
+    public void test() throws Exception {
+        OssStore store = new OssStore(server_local, config_local);
+        String content = RandomStringUtils.random(128, true, true);
+        ByteArray data = ByteArray.fromString(content);
+        store.write("/test/1.txt", new ByteArrayInputStream(data.value()), ByteArray.md5(data.value()).toHexString());
+        //Assert.assertTrue(store.exist("test/1.txt"));
+        Assert.assertEquals(content, ByteArray.fromInputStream(store.read("test/1.txt")).toString());
+        String url = store.getTemporaryUrl("/test/1.txt", Duration.ofMinutes(5));
+        //System.out.println(url);
+        Assert.assertTrue(url.startsWith(store.getServer()));
+        store.close();
+    }
+
+}

+ 5 - 6
core-logging/pom.xml

@@ -2,17 +2,20 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>qmth-boot</artifactId>
         <groupId>com.qmth.boot</groupId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-logging</artifactId>
     <name>core-logging</name>
 
     <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
             <artifactId>core-models</artifactId>
@@ -29,10 +32,6 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

+ 10 - 0
core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerAutoConfiguration.java

@@ -0,0 +1,10 @@
+package com.qmth.boot.core.logger.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.logger.*")
+public class LoggerAutoConfiguration {
+
+}

+ 0 - 18
core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerConfig.java

@@ -1,18 +0,0 @@
-package com.qmth.boot.core.logger.config;
-
-import org.springframework.util.unit.DataSize;
-
-public interface LoggerConfig {
-
-    String getPattern();
-
-    String getRootLevel();
-
-    String getFilePath();
-
-    DataSize getFileMaxSize();
-
-    DataSize getTotalMaxSize();
-
-    int getFileMaxHistory();
-}

+ 2 - 9
core-logging/src/main/java/com/qmth/boot/core/logger/config/bean/LoggerConfigBean.java → core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerProperties.java

@@ -1,7 +1,6 @@
-package com.qmth.boot.core.logger.config.bean;
+package com.qmth.boot.core.logger.config;
 
 import com.qmth.boot.core.constant.CoreConstant;
-import com.qmth.boot.core.logger.config.LoggerConfig;
 import com.qmth.boot.core.logger.constant.LoggerConstant;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.lang.Nullable;
@@ -16,7 +15,7 @@ import javax.validation.constraints.NotNull;
 @Component
 @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".logging")
 @Validated
-public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
+public class LoggerProperties implements LoggerConstant {
 
     @NotBlank
     private String pattern = DEFAULT_PATTERN;
@@ -36,7 +35,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
     @Min(1)
     private int fileMaxHistory = 7;
 
-    @Override
     public String getPattern() {
         return pattern;
     }
@@ -45,7 +43,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
         this.pattern = pattern;
     }
 
-    @Override
     public String getRootLevel() {
         return rootLevel;
     }
@@ -54,7 +51,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
         this.rootLevel = rootLevel;
     }
 
-    @Override
     public String getFilePath() {
         return filePath;
     }
@@ -63,7 +59,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
         this.filePath = filePath;
     }
 
-    @Override
     public DataSize getFileMaxSize() {
         return fileMaxSize;
     }
@@ -72,7 +67,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
         this.fileMaxSize = fileMaxSize;
     }
 
-    @Override
     public DataSize getTotalMaxSize() {
         return totalMaxSize;
     }
@@ -81,7 +75,6 @@ public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
         this.totalMaxSize = totalMaxSize;
     }
 
-    @Override
     public int getFileMaxHistory() {
         return fileMaxHistory;
     }

+ 10 - 10
core-logging/src/main/java/com/qmth/boot/core/logger/context/LoggerContextService.java

@@ -10,7 +10,7 @@ import ch.qos.logback.core.ConsoleAppender;
 import ch.qos.logback.core.rolling.RollingFileAppender;
 import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
 import ch.qos.logback.core.util.FileSize;
-import com.qmth.boot.core.logger.config.LoggerConfig;
+import com.qmth.boot.core.logger.config.LoggerProperties;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
@@ -28,30 +28,30 @@ public class LoggerContextService {
     private static final String ROLLING_FILE_APPENDER = "RollingFileAppender";
 
     @Resource
-    private LoggerConfig loggerConfig;
+    private LoggerProperties loggerProperties;
 
     @PostConstruct
     public void init() {
         LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
         Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
-        rootLogger.setLevel(Level.toLevel(loggerConfig.getRootLevel(), Level.INFO));
+        rootLogger.setLevel(Level.toLevel(loggerProperties.getRootLevel(), Level.INFO));
 
         PatternLayoutEncoder encoder = new PatternLayoutEncoder();
         encoder.setContext(context);
         encoder.setCharset(StandardCharsets.UTF_8);
-        encoder.setPattern(loggerConfig.getPattern());
+        encoder.setPattern(loggerProperties.getPattern());
         encoder.start();
 
-        if (StringUtils.isNotBlank(loggerConfig.getFilePath())) {
+        if (StringUtils.isNotBlank(loggerProperties.getFilePath())) {
             RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
             appender.setContext(context);
             appender.setEncoder(encoder);
-            appender.setFile(loggerConfig.getFilePath());
+            appender.setFile(loggerProperties.getFilePath());
             appender.setName(ROLLING_FILE_APPENDER);
             appender.setAppend(true);
 
             String filePattern;
-            String basePath = loggerConfig.getFilePath();
+            String basePath = loggerProperties.getFilePath();
             if (basePath.endsWith(".log")) {
                 filePattern = basePath.substring(0, basePath.length() - 4) + TIME_ROLLING_PATTERN;
             } else {
@@ -59,9 +59,9 @@ public class LoggerContextService {
             }
             SizeAndTimeBasedRollingPolicy<ILoggingEvent> policy = new SizeAndTimeBasedRollingPolicy<>();
             policy.setFileNamePattern(filePattern);
-            policy.setMaxHistory(loggerConfig.getFileMaxHistory());
-            policy.setMaxFileSize(new FileSize(loggerConfig.getFileMaxSize().toBytes()));
-            policy.setTotalSizeCap(new FileSize(loggerConfig.getTotalMaxSize().toBytes()));
+            policy.setMaxHistory(loggerProperties.getFileMaxHistory());
+            policy.setMaxFileSize(new FileSize(loggerProperties.getFileMaxSize().toBytes()));
+            policy.setTotalSizeCap(new FileSize(loggerProperties.getTotalMaxSize().toBytes()));
             policy.setParent(appender);
             policy.setContext(context);
             policy.start();

+ 2 - 0
core-logging/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.logger.config.LoggerAutoConfiguration

+ 1 - 1
core-metrics/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>com.qmth.boot</groupId>
         <artifactId>qmth-boot</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-metrics</artifactId>
     <name>core-metrics</name>

+ 19 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsAutoConfiguration.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.metrics.config;
+
+import com.qmth.boot.core.metrics.service.MetricsService;
+import com.qmth.boot.core.metrics.service.impl.MemoryMetricsService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.metrics.*")
+public class MetricsAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(MetricsService.class)
+    public MetricsService metricsService(MetricsProperties metricsProperties) {
+        return new MemoryMetricsService(metricsProperties);
+    }
+}

+ 0 - 16
core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsConfig.java

@@ -1,16 +0,0 @@
-package com.qmth.boot.core.metrics.config;
-
-import java.time.Duration;
-
-/**
- * 统计工具参数设置
- */
-public interface MetricsConfig {
-
-    /**
-     * 获取响应时间分组间隔
-     *
-     * @return
-     */
-    Duration[] getTimeGroup();
-}

+ 10 - 3
core-metrics/src/main/java/com/qmth/boot/core/metrics/config/bean/MetricsConfigBean.java → core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsProperties.java

@@ -1,7 +1,6 @@
-package com.qmth.boot.core.metrics.config.bean;
+package com.qmth.boot.core.metrics.config;
 
 import com.qmth.boot.core.constant.CoreConstant;
-import com.qmth.boot.core.metrics.config.MetricsConfig;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 import org.springframework.validation.annotation.Validated;
@@ -9,10 +8,13 @@ import org.springframework.validation.annotation.Validated;
 import javax.validation.constraints.NotEmpty;
 import java.time.Duration;
 
+/**
+ * 统计工具参数设置
+ */
 @Component
 @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".metrics")
 @Validated
-public class MetricsConfigBean implements MetricsConfig {
+public class MetricsProperties {
 
     /**
      * 默认时间分组,按0~10ms,10ms~100ms,100ms~500ms,500ms~1s,1s~10s,>10s分为6个档次
@@ -21,6 +23,11 @@ public class MetricsConfigBean implements MetricsConfig {
     private Duration[] timeGroup = new Duration[] { Duration.ofMillis(10), Duration.ofMillis(100),
             Duration.ofMillis(500), Duration.ofSeconds(1), Duration.ofSeconds(10) };
 
+    /**
+     * 获取响应时间分组间隔
+     *
+     * @return
+     */
     public Duration[] getTimeGroup() {
         return timeGroup;
     }

+ 9 - 22
core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MemoryMetricsService.java

@@ -1,47 +1,34 @@
 package com.qmth.boot.core.metrics.service.impl;
 
-import com.qmth.boot.core.metrics.config.MetricsConfig;
+import com.qmth.boot.core.metrics.config.MetricsProperties;
 import com.qmth.boot.core.metrics.model.MetricsResult;
 import com.qmth.boot.core.metrics.service.MetricsService;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 
-import javax.annotation.Resource;
+import javax.annotation.PreDestroy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-@Configuration
 public class MemoryMetricsService implements MetricsService {
 
     private Map<String, MetricsRecorder> recorderMap;
 
-    @Resource
-    private MetricsConfig metricsConfig;
+    private MetricsProperties metricsProperties;
 
-    @Bean
-    @ConditionalOnMissingBean(MetricsService.class)
-    public MetricsService metricsService() {
-        return new MemoryMetricsService();
-    }
-
-    public MemoryMetricsService() {
+    public MemoryMetricsService(MetricsProperties metricsProperties) {
+        this.metricsProperties = metricsProperties;
         this.recorderMap = new HashMap<>();
     }
 
-    public MetricsConfig getMetricsConfig() {
-        return metricsConfig;
-    }
-
-    public void setMetricsConfig(MetricsConfig metricsConfig) {
-        this.metricsConfig = metricsConfig;
+    @PreDestroy
+    public void close() {
+        this.recorderMap.clear();
     }
 
     @Override
     public void record(String endpoint, int status, long timecost) {
-        recorderMap.computeIfAbsent(endpoint, key -> new MetricsRecorder(metricsConfig)).record(status, timecost);
+        recorderMap.computeIfAbsent(endpoint, key -> new MetricsRecorder(metricsProperties)).record(status, timecost);
     }
 
     @Override

+ 3 - 3
core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MetricsRecorder.java

@@ -1,6 +1,6 @@
 package com.qmth.boot.core.metrics.service.impl;
 
-import com.qmth.boot.core.metrics.config.MetricsConfig;
+import com.qmth.boot.core.metrics.config.MetricsProperties;
 import com.qmth.boot.core.metrics.model.MetricsResult;
 import com.qmth.boot.core.metrics.model.TimeRange;
 
@@ -17,11 +17,11 @@ public class MetricsRecorder {
 
     private List<TimeCounter> timeList;
 
-    public MetricsRecorder(MetricsConfig config) {
+    public MetricsRecorder(MetricsProperties properties) {
         this.statusMap = new HashMap<>();
         this.timeList = new ArrayList<>();
         long ge = 0;
-        for (Duration time : config.getTimeGroup()) {
+        for (Duration time : properties.getTimeGroup()) {
             long lt = time.toMillis();
             this.timeList.add(new TimeCounter(ge, lt));
             ge = lt;

+ 2 - 0
core-metrics/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.metrics.config.MetricsAutoConfiguration

+ 4 - 6
core-metrics/src/test/java/com/qmth/boot/test/core/metrics/MetricsTest.java

@@ -1,7 +1,6 @@
 package com.qmth.boot.test.core.metrics;
 
-import com.qmth.boot.core.metrics.config.MetricsConfig;
-import com.qmth.boot.core.metrics.config.bean.MetricsConfigBean;
+import com.qmth.boot.core.metrics.config.MetricsProperties;
 import com.qmth.boot.core.metrics.model.MetricsResult;
 import com.qmth.boot.core.metrics.model.TimeRange;
 import com.qmth.boot.core.metrics.service.impl.MemoryMetricsService;
@@ -16,13 +15,12 @@ public class MetricsTest {
 
     private static MemoryMetricsService service;
 
-    private static MetricsConfig config;
+    private static MetricsProperties config;
 
     @BeforeClass
     public static void prepare() {
-        config = new MetricsConfigBean();
-        service = new MemoryMetricsService();
-        service.setMetricsConfig(config);
+        config = new MetricsProperties();
+        service = new MemoryMetricsService(config);
     }
 
     @AfterClass

+ 1 - 1
core-models/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <artifactId>qmth-boot</artifactId>
         <groupId>com.qmth.boot</groupId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-models</artifactId>
     <name>core-models</name>

+ 1 - 0
core-models/src/main/java/com/qmth/boot/core/exception/ReentrantException.java

@@ -14,4 +14,5 @@ public class ReentrantException extends RuntimeException {
     public ReentrantException(String message, Throwable t) {
         super(message, t);
     }
+
 }

+ 59 - 0
core-models/src/main/java/com/qmth/boot/core/exception/StatusException.java

@@ -0,0 +1,59 @@
+package com.qmth.boot.core.exception;
+
+/**
+ * 有状态标识的运行时异常,在API层按500状态码处理
+ */
+public class StatusException extends RuntimeException {
+
+    private static final long serialVersionUID = -2411329525159341065L;
+
+    private Integer code;
+
+    private String name;
+
+    /**
+     * 有明确的文字提示
+     *
+     * @param message
+     */
+    public StatusException(String message) {
+        super(message);
+    }
+
+    /**
+     * 有明确的文字提示与触发来源
+     *
+     * @param message
+     * @param cause
+     */
+    public StatusException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * 带所有参数的构造方法
+     *
+     * @param code    - 数字标识,范围在100~999
+     * @param name    - 异常详细类型
+     * @param message - 文字提示
+     * @param cause   - 触发来源
+     */
+    public StatusException(Integer code, String name, String message, Throwable cause) {
+        super(message, cause);
+        if (code != null) {
+            if (code < 100 || code > 999) {
+                throw new IllegalArgumentException("StatusException.code should between 100~999");
+            }
+        }
+        this.code = code;
+        this.name = name;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getName() {
+        return name;
+    }
+}

+ 3 - 3
core-rate-limit/pom.xml

@@ -6,15 +6,15 @@
     <parent>
         <groupId>com.qmth.boot</groupId>
         <artifactId>qmth-boot</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-rate-limit</artifactId>
     <name>core-rate-limit</name>
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 19 - 0
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitAutoConfiguration.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.rateLimit.config;
+
+import com.qmth.boot.core.rateLimit.service.RateLimitService;
+import com.qmth.boot.core.rateLimit.service.impl.MemoryRateLimitService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.rateLimit.*")
+public class RateLimitAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(RateLimitService.class)
+    public RateLimitService rateLimitService() {
+        return new MemoryRateLimitService();
+    }
+}

+ 1 - 1
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitRule.java → core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitRule.java

@@ -1,4 +1,4 @@
-package com.qmth.boot.core.rateLimit.config;
+package com.qmth.boot.core.rateLimit.entity;
 
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.boot.convert.DurationStyle;

+ 1 - 1
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitScope.java → core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitScope.java

@@ -1,4 +1,4 @@
-package com.qmth.boot.core.rateLimit.config;
+package com.qmth.boot.core.rateLimit.entity;
 
 public enum RateLimitScope {
 

+ 1 - 1
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitTarget.java → core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/entity/RateLimitTarget.java

@@ -1,4 +1,4 @@
-package com.qmth.boot.core.rateLimit.config;
+package com.qmth.boot.core.rateLimit.entity;
 
 public enum RateLimitTarget {
 

+ 1 - 1
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitPolicy.java

@@ -1,6 +1,6 @@
 package com.qmth.boot.core.rateLimit.service;
 
-import com.qmth.boot.core.rateLimit.config.RateLimitRule;
+import com.qmth.boot.core.rateLimit.entity.RateLimitRule;
 
 /**
  * 动态获取限流规则扩展接口

+ 1 - 1
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitService.java

@@ -1,6 +1,6 @@
 package com.qmth.boot.core.rateLimit.service;
 
-import com.qmth.boot.core.rateLimit.config.RateLimitRule;
+import com.qmth.boot.core.rateLimit.entity.RateLimitRule;
 
 /**
  * 限流执行服务

+ 0 - 23
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/EmptyRateLimitPolicy.java

@@ -1,23 +0,0 @@
-package com.qmth.boot.core.rateLimit.service.impl;
-
-import com.qmth.boot.core.rateLimit.config.RateLimitRule;
-import com.qmth.boot.core.rateLimit.service.RateLimitPolicy;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class EmptyRateLimitPolicy implements RateLimitPolicy {
-
-    @Bean
-    @ConditionalOnMissingBean(RateLimitPolicy.class)
-    public RateLimitPolicy rateLimitPolicy() {
-        return new EmptyRateLimitPolicy();
-    }
-
-    @Override
-    public RateLimitRule[] getRules(String path) {
-        return null;
-    }
-
-}

+ 2 - 12
core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/MemoryRateLimitService.java

@@ -1,18 +1,14 @@
 package com.qmth.boot.core.rateLimit.service.impl;
 
-import com.qmth.boot.core.rateLimit.config.RateLimitRule;
-import com.qmth.boot.core.rateLimit.config.RateLimitTarget;
+import com.qmth.boot.core.rateLimit.entity.RateLimitRule;
+import com.qmth.boot.core.rateLimit.entity.RateLimitTarget;
 import com.qmth.boot.core.rateLimit.service.RateLimitService;
 import org.apache.commons.lang3.ArrayUtils;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 
 import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
 
-@Configuration
 public class MemoryRateLimitService implements RateLimitService {
 
     protected static final String KEY_JOINER = "_";
@@ -21,12 +17,6 @@ public class MemoryRateLimitService implements RateLimitService {
 
     private final Map<String, RateLimitor[]> limitorMap = new HashMap<>();
 
-    @Bean
-    @ConditionalOnMissingBean(RateLimitService.class)
-    public RateLimitService rateLimitService() {
-        return new MemoryRateLimitService();
-    }
-
     @Override
     public boolean accept(String endpoint, String device, RateLimitRule... rules) {
         if (ArrayUtils.isEmpty(rules)) {

+ 2 - 0
core-rate-limit/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.rateLimit.config.RateLimitAutoConfiguration

+ 3 - 3
core-rate-limit/src/test/java/com/qmth/boot/test/core/rateLimit/RateLimitTest.java

@@ -1,8 +1,8 @@
 package com.qmth.boot.test.core.rateLimit;
 
-import com.qmth.boot.core.rateLimit.config.RateLimitRule;
-import com.qmth.boot.core.rateLimit.config.RateLimitScope;
-import com.qmth.boot.core.rateLimit.config.RateLimitTarget;
+import com.qmth.boot.core.rateLimit.entity.RateLimitRule;
+import com.qmth.boot.core.rateLimit.entity.RateLimitScope;
+import com.qmth.boot.core.rateLimit.entity.RateLimitTarget;
 import com.qmth.boot.core.rateLimit.service.impl.MemoryRateLimitService;
 import org.apache.commons.lang3.RandomUtils;
 import org.junit.Assert;

+ 65 - 0
core-retrofit/pom.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>qmth-boot</artifactId>
+        <groupId>com.qmth.boot</groupId>
+        <version>1.0.1</version>
+    </parent>
+    <artifactId>core-retrofit</artifactId>
+    <name>core-retrofit</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-signature</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>retrofit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>converter-jackson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>logging-interceptor</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 43 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/annotatioin/RetrofitClient.java

@@ -0,0 +1,43 @@
+package com.qmth.boot.core.retrofit.annotatioin;
+
+import com.qmth.boot.core.retrofit.interfaces.CustomizeRetrofitConfiguration;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.stereotype.Service;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Service
+public @interface RetrofitClient {
+
+    /**
+     * 访问地址前缀,默认空白
+     *
+     * @return
+     */
+    String baseUrl() default "";
+
+    /**
+     * 日志级别,默认关闭
+     *
+     * @return
+     */
+    LogLevel logLevel() default LogLevel.OFF;
+
+    /**
+     * 是否直接返回数据对象而不使用Call包装,默认为false
+     *
+     * @return
+     */
+    boolean directReturn() default false;
+
+    /**
+     * 自定义客户端配置
+     *
+     * @return
+     */
+    Class<? extends CustomizeRetrofitConfiguration> configuration() default CustomizeRetrofitConfiguration.VoidRetrofitConfiguration.class;
+
+}

+ 37 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/annotatioin/RetrofitScan.java

@@ -0,0 +1,37 @@
+package com.qmth.boot.core.retrofit.annotatioin;
+
+import com.qmth.boot.core.retrofit.core.RetrofitClientScannerRegistrar;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Import(RetrofitClientScannerRegistrar.class)
+public @interface RetrofitScan {
+
+    /**
+     * 扫描包路径
+     * 等同于basePackages
+     *
+     * @return basePackages
+     */
+    @AliasFor("basePackages") String[] value() default {};
+
+    /**
+     * 扫描包路径
+     *
+     * @return basePackages
+     */
+    @AliasFor("value") String[] basePackages() default {};
+
+    /**
+     * 扫描类集合
+     *
+     * @return Scan package classes
+     */
+    Class<?>[] basePackageClasses() default {};
+
+}

+ 32 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/PoolProperties.java

@@ -0,0 +1,32 @@
+package com.qmth.boot.core.retrofit.config;
+
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Validated
+public class PoolProperties {
+
+    @NotNull
+    private Integer maxIdle = 1;
+
+    @NotNull
+    private Duration keepAlive = Duration.ofMinutes(10);
+
+    public Integer getMaxIdle() {
+        return maxIdle;
+    }
+
+    public void setMaxIdle(Integer maxIdle) {
+        this.maxIdle = maxIdle;
+    }
+
+    public Duration getKeepAlive() {
+        return keepAlive;
+    }
+
+    public void setKeepAlive(Duration keepAlive) {
+        this.keepAlive = keepAlive;
+    }
+}

+ 10 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetrofitAutoConfiguration.java

@@ -0,0 +1,10 @@
+package com.qmth.boot.core.retrofit.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.retrofit.*")
+public class RetrofitAutoConfiguration {
+
+}

+ 80 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetrofitProperties.java

@@ -0,0 +1,80 @@
+package com.qmth.boot.core.retrofit.config;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".retrofit")
+public class RetrofitProperties {
+
+    @NotNull
+    private Duration connectTimeout = Duration.ofSeconds(10);
+
+    @NotNull
+    private Duration readTimeout = Duration.ofSeconds(10);
+
+    @NotNull
+    private Duration writeTimeout = Duration.ofSeconds(10);
+
+    @NotNull
+    private PoolProperties pool = new PoolProperties();
+
+    @NotNull
+    private RetryProperties retry = new RetryProperties();
+
+    @NotNull
+    private LogLevel logLevel = LogLevel.OFF;
+
+    public Duration getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    public void setConnectTimeout(Duration connectTimeout) {
+        this.connectTimeout = connectTimeout;
+    }
+
+    public Duration getReadTimeout() {
+        return readTimeout;
+    }
+
+    public void setReadTimeout(Duration readTimeout) {
+        this.readTimeout = readTimeout;
+    }
+
+    public Duration getWriteTimeout() {
+        return writeTimeout;
+    }
+
+    public void setWriteTimeout(Duration writeTimeout) {
+        this.writeTimeout = writeTimeout;
+    }
+
+    public PoolProperties getPool() {
+        return pool;
+    }
+
+    public void setPool(PoolProperties pool) {
+        this.pool = pool;
+    }
+
+    public RetryProperties getRetry() {
+        return retry;
+    }
+
+    public void setRetry(RetryProperties retry) {
+        this.retry = retry;
+    }
+
+    public LogLevel getLogLevel() {
+        return logLevel;
+    }
+
+    public void setLogLevel(LogLevel logLevel) {
+        this.logLevel = logLevel;
+    }
+}

+ 32 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/config/RetryProperties.java

@@ -0,0 +1,32 @@
+package com.qmth.boot.core.retrofit.config;
+
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Validated
+public class RetryProperties {
+
+    @NotNull
+    private Integer count = 3;
+
+    @NotNull
+    private Duration interval = Duration.ofMillis(300);
+
+    public Integer getCount() {
+        return count;
+    }
+
+    public void setCount(Integer count) {
+        this.count = count;
+    }
+
+    public Duration getInterval() {
+        return interval;
+    }
+
+    public void setInterval(Duration interval) {
+        this.interval = interval;
+    }
+}

+ 72 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/ClassPathRetrofitClientScanner.java

@@ -0,0 +1,72 @@
+package com.qmth.boot.core.retrofit.core;
+
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.ClassUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner {
+
+    private final static Logger logger = LoggerFactory.getLogger(ClassPathRetrofitClientScanner.class);
+
+    private ClassLoader classLoader;
+
+    public ClassPathRetrofitClientScanner(BeanDefinitionRegistry registry, ClassLoader classLoader) {
+        super(registry, false);
+        this.classLoader = classLoader;
+    }
+
+    public void registerFilters() {
+        this.addIncludeFilter(new AnnotationTypeFilter(RetrofitClient.class));
+    }
+
+    @Override
+    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
+        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
+        if (beanDefinitions.isEmpty()) {
+            logger.warn("No RetrofitClient was found in '" + Arrays.toString(basePackages)
+                    + "' package. Please check your configuration.");
+        } else {
+            processBeanDefinitions(beanDefinitions);
+        }
+        return beanDefinitions;
+    }
+
+    @Override
+    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
+        if (beanDefinition.getMetadata().isInterface()) {
+            try {
+                Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(), classLoader);
+                return !target.isAnnotation();
+            } catch (Exception ex) {
+                logger.error("load class exception:", ex);
+            }
+        }
+        return false;
+    }
+
+    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
+        GenericBeanDefinition definition;
+        for (BeanDefinitionHolder holder : beanDefinitions) {
+            definition = (GenericBeanDefinition) holder.getBeanDefinition();
+            if (logger.isDebugEnabled()) {
+                logger.debug("Creating RetrofitClientBean with name '" + holder.getBeanName() + "' and '" + definition
+                        .getBeanClassName() + "' Interface");
+            }
+            definition.getConstructorArgumentValues()
+                    .addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName()));
+            // beanClass全部设置为RetrofitFactoryBean
+            definition.setBeanClass(RetrofitFactoryBean.class);
+        }
+    }
+}

+ 42 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/DirectCallAdapterFactory.java

@@ -0,0 +1,42 @@
+package com.qmth.boot.core.retrofit.core;
+
+import com.qmth.boot.core.retrofit.exception.RetrofitConvertError;
+import retrofit2.Call;
+import retrofit2.CallAdapter;
+import retrofit2.Retrofit;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+public class DirectCallAdapterFactory extends CallAdapter.Factory {
+
+    @Override
+    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
+        final Type responseType = getResponseType(returnType);
+
+        return new CallAdapter<Object, Object>() {
+
+            public Type responseType() {
+                return responseType;
+            }
+
+            public Object adapt(Call<Object> call) {
+                // todo 可以在这里判断接口数据格式
+                try {
+                    return call.execute().body();
+                } catch (IOException e) {
+                    throw new RetrofitConvertError("Retrofit call execute error", e);
+                }
+            }
+        };
+    }
+
+    private Type getResponseType(Type type) {
+        if (type instanceof WildcardType) {
+            return ((WildcardType) type).getUpperBounds()[0];
+        }
+        return type;
+    }
+}

+ 74 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitClientScannerRegistrar.java

@@ -0,0 +1,74 @@
+package com.qmth.boot.core.retrofit.core;
+
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitScan;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.util.ClassUtils;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class RetrofitClientScannerRegistrar
+        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
+
+    private ResourceLoader resourceLoader;
+
+    private ClassLoader classLoader;
+
+    @Override
+    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
+        AnnotationAttributes attributes = AnnotationAttributes
+                .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName()));
+        if (attributes == null) {
+            return;
+        }
+        // Scan the @RetrofitClient annotated interface under the specified path and register it to the BeanDefinitionRegistry
+        ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
+        if (resourceLoader != null) {
+            scanner.setResourceLoader(resourceLoader);
+        }
+        // Specify the base package for scanning
+        String[] basePackages = getPackagesToScan(attributes);
+        scanner.registerFilters();
+        // Scan and register to BeanDefinition
+        scanner.doScan(basePackages);
+    }
+
+    /**
+     * 获取扫描的基础包路径
+     *
+     * @return 基础包路径
+     */
+    private String[] getPackagesToScan(AnnotationAttributes attributes) {
+        //String[] value = attributes.getStringArray("value");
+        String[] basePackages = attributes.getStringArray("basePackages");
+        Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
+        //if (!ArrayUtils.isEmpty(value)) {
+        //    Assert.state(ArrayUtils.isEmpty(basePackages),
+        //            "@RetrofitScan basePackages and value attributes are mutually exclusive");
+        //}
+        Set<String> packagesToScan = new LinkedHashSet<>();
+        //packagesToScan.addAll(Arrays.asList(value));
+        packagesToScan.addAll(Arrays.asList(basePackages));
+        for (Class<?> basePackageClass : basePackageClasses) {
+            packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
+        }
+        return packagesToScan.toArray(new String[packagesToScan.size()]);
+    }
+
+    @Override
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
+    }
+
+    @Override
+    public void setBeanClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+}

+ 121 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitFactoryBean.java

@@ -0,0 +1,121 @@
+package com.qmth.boot.core.retrofit.core;
+
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitClient;
+import com.qmth.boot.core.retrofit.config.RetrofitProperties;
+import com.qmth.boot.core.retrofit.interceptor.ErrorDecodeInterceptor;
+import com.qmth.boot.core.retrofit.interceptor.LoggingInterceptor;
+import com.qmth.boot.core.retrofit.interceptor.RetryInterceptor;
+import com.qmth.boot.core.retrofit.interceptor.SignatureInterceptor;
+import com.qmth.boot.core.retrofit.interfaces.CustomizeRetrofitConfiguration;
+import com.qmth.boot.core.retrofit.interfaces.SignatureProvider;
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import retrofit2.Retrofit;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+
+import java.lang.reflect.Proxy;
+
+public class RetrofitFactoryBean<T> implements FactoryBean<T>, ApplicationContextAware {
+
+    private ApplicationContext applicationContext;
+
+    private Class<T> retrofitInterface;
+
+    private RetrofitProperties retrofitProperties;
+
+    public RetrofitFactoryBean(Class<T> retrofitInterface) {
+        this.retrofitInterface = retrofitInterface;
+    }
+
+    @Override
+    public T getObject() {
+        Retrofit retrofit = getRetrofit(retrofitInterface);
+        // source
+        T source = retrofit.create(retrofitInterface);
+        // proxy
+        return (T) Proxy.newProxyInstance(retrofitInterface.getClassLoader(), new Class<?>[] { retrofitInterface },
+                new RetrofitInvocationHandler(source, retrofitProperties));
+    }
+
+    @Override
+    public Class<T> getObjectType() {
+        return this.retrofitInterface;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) {
+        RetrofitClient retrofitClient = retrofitClientInterfaceClass.getAnnotation(RetrofitClient.class);
+        CustomizeRetrofitConfiguration customize = applicationContext.getBean(retrofitClient.configuration());
+
+        OkHttpClient client = getOkHttpClient(retrofitClient, customize);
+        Retrofit.Builder retrofitBuilder = new Retrofit.Builder().baseUrl(getBaseUrl(retrofitClient, customize))
+                .addConverterFactory(JacksonConverterFactory.create()).validateEagerly(true).client(client);
+        if (retrofitClient.directReturn()) {
+            retrofitBuilder.addCallAdapterFactory(new DirectCallAdapterFactory());
+        }
+        return retrofitBuilder.build();
+    }
+
+    /**
+     * Get OkHttpClient instance, one interface corresponds to one OkHttpClient
+     *
+     * @param retrofitClient @RetrofitClient
+     * @return OkHttpClient instance
+     */
+    private OkHttpClient getOkHttpClient(RetrofitClient retrofitClient, CustomizeRetrofitConfiguration customize) {
+        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder().connectionPool(new ConnectionPool());
+
+        // add signature interceptor
+        SignatureProvider provider = customize.getSignature();
+        if (provider != null) {
+            okHttpClientBuilder.addInterceptor(new SignatureInterceptor(provider));
+        }
+
+        // add ErrorDecodeInterceptor
+        okHttpClientBuilder.addInterceptor(new ErrorDecodeInterceptor());
+
+        // add retry interceptor
+        okHttpClientBuilder.addInterceptor(new RetryInterceptor(retrofitProperties.getRetry()));
+
+        // add logging interceptor
+        LogLevel logLevel = getLogLevel(retrofitClient);
+        if (!logLevel.equals(LogLevel.OFF)) {
+            okHttpClientBuilder.addNetworkInterceptor(new LoggingInterceptor(logLevel));
+        }
+
+        return okHttpClientBuilder.build();
+    }
+
+    private String getBaseUrl(RetrofitClient retrofitClient, CustomizeRetrofitConfiguration customize) {
+        String url = customize.getBaseUrl();
+        if (StringUtils.isBlank(url)) {
+            url = retrofitClient.baseUrl();
+        }
+        return url;
+    }
+
+    private LogLevel getLogLevel(RetrofitClient retrofitClient) {
+        LogLevel level = retrofitClient.logLevel();
+        if (level.equals(LogLevel.OFF)) {
+            level = retrofitProperties.getLogLevel();
+        }
+        return level;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+        this.retrofitProperties = applicationContext.getBean(RetrofitProperties.class);
+    }
+
+}

+ 33 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/core/RetrofitInvocationHandler.java

@@ -0,0 +1,33 @@
+package com.qmth.boot.core.retrofit.core;
+
+import com.qmth.boot.core.retrofit.config.RetrofitProperties;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+public class RetrofitInvocationHandler implements InvocationHandler {
+
+    private Object source;
+
+    private RetrofitProperties retrofitProperties;
+
+    public RetrofitInvocationHandler(Object source, RetrofitProperties retrofitProperties) {
+        this.source = source;
+        this.retrofitProperties = retrofitProperties;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        try {
+            return method.invoke(source, args);
+        } catch (Throwable e) {
+            Throwable cause = e.getCause();
+            // Object fallbackObject = getFallbackObject(cause);
+            // 熔断逻辑
+            // if (cause instanceof RetrofitBlockException && degradeProperty.isEnable() && fallbackObject != null) {
+            //     return method.invoke(fallbackObject, args);
+            //}
+            throw cause;
+        }
+    }
+}

+ 13 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/exception/RetrofitConvertError.java

@@ -0,0 +1,13 @@
+package com.qmth.boot.core.retrofit.exception;
+
+/**
+ * Retrofit数据转换错误
+ */
+public class RetrofitConvertError extends RuntimeException {
+
+    private static final long serialVersionUID = 4909517717522216000L;
+
+    public RetrofitConvertError(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 37 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/exception/RetrofitResponseError.java

@@ -0,0 +1,37 @@
+package com.qmth.boot.core.retrofit.exception;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Retrofit调用响应错误
+ */
+public class RetrofitResponseError extends RuntimeException {
+
+    private static final long serialVersionUID = 2146534139282835176L;
+
+    private static final String MESSSAGE_FORMAT = "code=%s, body=%s";
+
+    private int code;
+
+    private String body;
+
+    public RetrofitResponseError(int code, String body) {
+        super(String.format(MESSSAGE_FORMAT, code, StringUtils.trimToEmpty(body)));
+        this.code = code;
+        this.body = body;
+    }
+
+    public RetrofitResponseError(int code, String body, Throwable cause) {
+        super(String.format(MESSSAGE_FORMAT, code, StringUtils.trimToEmpty(body)), cause);
+        this.code = code;
+        this.body = body;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getBody() {
+        return body;
+    }
+}

+ 82 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/ErrorDecodeInterceptor.java

@@ -0,0 +1,82 @@
+package com.qmth.boot.core.retrofit.interceptor;
+
+import com.qmth.boot.core.retrofit.exception.RetrofitResponseError;
+import okhttp3.*;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.GzipSource;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class ErrorDecodeInterceptor implements Interceptor {
+
+    public static final String GZIP = "gzip";
+
+    public static final String CONTENT_ENCODING = "Content-Encoding";
+
+    public static final String IDENTITY = "identity";
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        Response response = chain.proceed(chain.request());
+        if (response.isSuccessful()) {
+            return response;
+        }
+        throw new RetrofitResponseError(response.code(), readResponseBody(response));
+    }
+
+    /**
+     * read ResponseBody as String
+     *
+     * @param response response
+     * @return ResponseBody String
+     * @throws IOException
+     */
+    public static String readResponseBody(Response response) {
+        try {
+            Headers headers = response.headers();
+            if (bodyHasUnknownEncoding(headers)) {
+                return null;
+            }
+            ResponseBody responseBody = response.body();
+            if (responseBody == null) {
+                return null;
+            }
+            long contentLength = responseBody.contentLength();
+
+            BufferedSource source = responseBody.source();
+            // Buffer the entire body.
+            source.request(Long.MAX_VALUE);
+            Buffer buffer = source.getBuffer();
+
+            if (GZIP.equalsIgnoreCase(headers.get(CONTENT_ENCODING))) {
+                try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
+                    buffer = new Buffer();
+                    buffer.writeAll(gzippedResponseBody);
+                }
+            }
+            Charset charset = StandardCharsets.UTF_8;
+            MediaType contentType = responseBody.contentType();
+            if (contentType != null) {
+                charset = contentType.charset(StandardCharsets.UTF_8);
+            }
+
+            if (contentLength > 0) {
+                return buffer.clone().readString(charset);
+            } else {
+                return null;
+            }
+        } catch (Exception e) {
+            //throw new RuntimeException(e);
+            return null;
+        }
+    }
+
+    private static boolean bodyHasUnknownEncoding(Headers headers) {
+        String contentEncoding = headers.get(CONTENT_ENCODING);
+        return contentEncoding != null && !IDENTITY.equalsIgnoreCase(contentEncoding) && !GZIP
+                .equalsIgnoreCase(contentEncoding);
+    }
+}

+ 42 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/LoggingInterceptor.java

@@ -0,0 +1,42 @@
+package com.qmth.boot.core.retrofit.interceptor;
+
+import okhttp3.Interceptor;
+import okhttp3.Response;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.logging.LogLevel;
+
+import java.io.IOException;
+
+public class LoggingInterceptor implements Interceptor {
+
+    private final static Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
+
+    private HttpLoggingInterceptor httpLoggingInterceptor;
+
+    public LoggingInterceptor(LogLevel logLevel) {
+        this.httpLoggingInterceptor = new HttpLoggingInterceptor(getLogger(logLevel));
+    }
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        return httpLoggingInterceptor.intercept(chain);
+    }
+
+    public HttpLoggingInterceptor.Logger getLogger(LogLevel level) {
+        switch (level) {
+        case TRACE:
+            return log::trace;
+        case DEBUG:
+            return log::debug;
+        case INFO:
+            return log::info;
+        case WARN:
+            return log::warn;
+        case ERROR:
+            return log::error;
+        }
+        throw new UnsupportedOperationException("Don't support this log level currently.");
+    }
+}

+ 47 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/RetryInterceptor.java

@@ -0,0 +1,47 @@
+package com.qmth.boot.core.retrofit.interceptor;
+
+import com.qmth.boot.core.retrofit.config.RetryProperties;
+import okhttp3.Interceptor;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+public class RetryInterceptor implements Interceptor {
+
+    private RetryProperties retryProperties;
+
+    public RetryInterceptor(RetryProperties retryProperties) {
+        this.retryProperties = retryProperties;
+    }
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        int count = 0;
+        Response response = chain.proceed(chain.request());
+        while (needRetry(response, count)) {
+            response.close();
+            sleep();
+            response = chain.proceed(chain.request());
+            count++;
+        }
+        return response;
+    }
+
+    private boolean needRetry(Response response, int count) {
+        if (response.isSuccessful() || response.code() != 503) {
+            return false;
+        }
+        return count < retryProperties.getCount();
+    }
+
+    private void sleep() {
+        if (retryProperties.getInterval().toMillis() > 0) {
+            try {
+                Thread.sleep(retryProperties.getInterval().toMillis());
+            } catch (Exception e) {
+                throw new RuntimeException("Retrofit retry sleep interupted", e);
+            }
+        }
+    }
+
+}

+ 38 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interceptor/SignatureInterceptor.java

@@ -0,0 +1,38 @@
+package com.qmth.boot.core.retrofit.interceptor;
+
+import com.qmth.boot.core.retrofit.interfaces.SignatureProvider;
+import com.qmth.boot.tools.signature.SignatureEntity;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+/**
+ * 签名构造器,符合开发框架通用鉴权标准
+ */
+public class SignatureInterceptor implements Interceptor {
+
+    private static final String HEADER_KEY_AUTHORIZATION = "authorization";
+
+    private static final String HEADER_KEY_TIME = "time";
+
+    private SignatureProvider provider;
+
+    public SignatureInterceptor(SignatureProvider provider) {
+        this.provider = provider;
+    }
+
+    @Override
+    public Response intercept(Chain chain) throws IOException {
+        Request request = chain.request();
+        long time = System.currentTimeMillis();
+        String signature = SignatureEntity
+                .build(provider.getType(), request.method(), request.url().uri().getPath(), time,
+                        provider.getIdentity(), provider.getSecret());
+        return chain.proceed(request.newBuilder().removeHeader(HEADER_KEY_TIME).removeHeader(HEADER_KEY_AUTHORIZATION)
+                .addHeader(HEADER_KEY_TIME, String.valueOf(time)).addHeader(HEADER_KEY_AUTHORIZATION, signature)
+                .build());
+    }
+
+}

+ 30 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interfaces/CustomizeRetrofitConfiguration.java

@@ -0,0 +1,30 @@
+package com.qmth.boot.core.retrofit.interfaces;
+
+/**
+ * Retrofit客户端配置自定义扩展
+ */
+public interface CustomizeRetrofitConfiguration {
+
+    class VoidRetrofitConfiguration implements CustomizeRetrofitConfiguration {
+
+    }
+
+    /**
+     * 访问地址前缀
+     *
+     * @return
+     */
+    default String getBaseUrl() {
+        return null;
+    }
+
+    /**
+     * 启用签名鉴权
+     *
+     * @return
+     */
+    default SignatureProvider getSignature() {
+        return null;
+    }
+
+}

+ 16 - 0
core-retrofit/src/main/java/com/qmth/boot/core/retrofit/interfaces/SignatureProvider.java

@@ -0,0 +1,16 @@
+package com.qmth.boot.core.retrofit.interfaces;
+
+import com.qmth.boot.tools.signature.SignatureType;
+
+/**
+ * 签名信息提供接口,符合通用鉴权标准
+ */
+public interface SignatureProvider {
+
+    SignatureType getType();
+
+    String getIdentity();
+
+    String getSecret();
+
+}

+ 2 - 0
core-retrofit/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.retrofit.config.RetrofitAutoConfiguration

+ 33 - 0
core-schedule/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>qmth-boot</artifactId>
+        <groupId>com.qmth.boot</groupId>
+        <version>1.0.1</version>
+    </parent>
+    <artifactId>core-schedule</artifactId>
+    <name>core-schedule</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 25 - 0
core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ExecutorProperties.java

@@ -0,0 +1,25 @@
+package com.qmth.boot.core.schedule.config;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".executor")
+@Validated
+public class ExecutorProperties {
+
+    @Min(1)
+    private int poolSize = Runtime.getRuntime().availableProcessors() * 2;
+
+    public int getPoolSize() {
+        return poolSize;
+    }
+
+    public void setPoolSize(int poolSize) {
+        this.poolSize = poolSize;
+    }
+}

+ 36 - 0
core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ScheduleAutoConfiguration.java

@@ -0,0 +1,36 @@
+package com.qmth.boot.core.schedule.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+@Configuration
+@EnableScheduling
+@EnableAsync
+@ComponentScan("com.qmth.boot.core.schedule.*")
+public class ScheduleAutoConfiguration {
+
+    @Bean(destroyMethod = "shutdown")
+    @ConditionalOnMissingBean(TaskExecutor.class)
+    public ThreadPoolTaskExecutor taskExecutor(ExecutorProperties executorProperties) {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(executorProperties.getPoolSize());
+        executor.setMaxPoolSize(executorProperties.getPoolSize());
+        return executor;
+    }
+
+    @Bean(destroyMethod = "shutdown")
+    @ConditionalOnMissingBean(TaskScheduler.class)
+    public ThreadPoolTaskScheduler taskScheduler(ScheduleProperties scheduleProperties) {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+        scheduler.setPoolSize(scheduleProperties.getPoolSize());
+        return scheduler;
+    }
+}

+ 25 - 0
core-schedule/src/main/java/com/qmth/boot/core/schedule/config/ScheduleProperties.java

@@ -0,0 +1,25 @@
+package com.qmth.boot.core.schedule.config;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".schedule")
+@Validated
+public class ScheduleProperties {
+
+    @Min(1)
+    private int poolSize = Runtime.getRuntime().availableProcessors() * 2;
+
+    public int getPoolSize() {
+        return poolSize;
+    }
+
+    public void setPoolSize(int poolSize) {
+        this.poolSize = poolSize;
+    }
+}

+ 2 - 0
core-schedule/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.schedule.config.ScheduleAutoConfiguration

+ 1 - 1
core-security/pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>com.qmth.boot</groupId>
         <artifactId>qmth-boot</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>core-security</artifactId>
     <name>core-security</name>

+ 30 - 0
core-security/src/main/java/com/qmth/boot/core/security/annotation/AuthorizationComponent.java

@@ -0,0 +1,30 @@
+package com.qmth.boot.core.security.annotation;
+
+import com.qmth.boot.tools.signature.SignatureType;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义鉴权服务,可以省略Component注解,bean需要实现AuthorizationService接口
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE })
+@Documented
+@Component
+public @interface AuthorizationComponent {
+
+    /**
+     * 匹配路径前缀,空白表示默认鉴权服务
+     *
+     * @return
+     */
+    String prefix() default "";
+
+    /**
+     * 匹配签名类型,可以为空
+     *
+     * @return
+     */
+    SignatureType[] type() default {};
+}

+ 10 - 0
core-security/src/main/java/com/qmth/boot/core/security/config/SecurityAutoConfiguration.java

@@ -0,0 +1,10 @@
+package com.qmth.boot.core.security.config;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.security.*")
+public class SecurityAutoConfiguration {
+
+}

+ 0 - 21
core-security/src/main/java/com/qmth/boot/core/security/config/SecurityConfig.java

@@ -1,21 +0,0 @@
-package com.qmth.boot.core.security.config;
-
-import java.time.Duration;
-
-/**
- * 安全组件配置参数
- */
-public interface SecurityConfig {
-
-    /**
-     * 允许服务器时间晚于请求时间戳最大时长
-     */
-    Duration getTimeMaxDelay();
-
-    /**
-     * 允许服务器时间早于请求时间戳最大时长
-     *
-     * @return
-     */
-    Duration getTimeMaxAhead();
-}

+ 61 - 0
core-security/src/main/java/com/qmth/boot/core/security/config/SecurityContextListener.java

@@ -0,0 +1,61 @@
+package com.qmth.boot.core.security.config;
+
+import com.qmth.boot.core.security.annotation.AuthorizationComponent;
+import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.core.security.service.impl.BaseAuthorizationSupport;
+import com.qmth.boot.tools.signature.SignatureType;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+@Component
+public class SecurityContextListener implements ApplicationListener<ContextRefreshedEvent> {
+
+    @Resource
+    private BaseAuthorizationSupport baseAuthorizationSupport;
+
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        ApplicationContext applicationContext = event.getApplicationContext();
+        //注意 需要时根容器才能通过注解获取到bean,比如event直接获取的容器中只有一些公共注册bean
+        if (applicationContext.getParent() != null) {
+            applicationContext = applicationContext.getParent();
+        }
+        Map<String, Object> beansWithAnnotation = applicationContext
+                .getBeansWithAnnotation(AuthorizationComponent.class);
+        for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
+            AuthorizationComponent annotation = applicationContext
+                    .findAnnotationOnBean(entry.getKey(), AuthorizationComponent.class);
+            if (annotation != null && entry.getValue() instanceof AuthorizationService) {
+                if (annotation.type().length > 0) {
+                    for (SignatureType type : annotation.type()) {
+                        register((AuthorizationService) entry.getValue(), annotation.prefix(), type);
+                    }
+                } else {
+                    register((AuthorizationService) entry.getValue(), annotation.prefix(), null);
+                }
+            }
+        }
+    }
+
+    private void register(AuthorizationService bean, String prefix, SignatureType type) {
+        if (StringUtils.isNotBlank(prefix)) {
+            if (type != null) {
+                baseAuthorizationSupport.getServiceContainer().setPrefix(prefix, type, bean);
+            } else {
+                baseAuthorizationSupport.getServiceContainer().setPrefix(prefix, bean);
+            }
+        } else {
+            if (type != null) {
+                baseAuthorizationSupport.getServiceContainer().setDefault(type, bean);
+            } else {
+                baseAuthorizationSupport.getServiceContainer().setDefault(bean);
+            }
+        }
+    }
+}

+ 2 - 5
core-security/src/main/java/com/qmth/boot/core/security/config/bean/SecurityConfigBean.java → core-security/src/main/java/com/qmth/boot/core/security/config/SecurityProperties.java

@@ -1,7 +1,6 @@
-package com.qmth.boot.core.security.config.bean;
+package com.qmth.boot.core.security.config;
 
 import com.qmth.boot.core.constant.CoreConstant;
-import com.qmth.boot.core.security.config.SecurityConfig;
 import org.hibernate.validator.constraints.time.DurationMin;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
@@ -16,7 +15,7 @@ import java.time.Duration;
 @Component
 @ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".auth")
 @Validated
-public class SecurityConfigBean implements SecurityConfig {
+public class SecurityProperties {
 
     /**
      * 允许服务器时间晚于请求时间戳最大时长,默认15秒
@@ -32,7 +31,6 @@ public class SecurityConfigBean implements SecurityConfig {
     @DurationMin(seconds = 0)
     private Duration timeMaxAhead = Duration.ofSeconds(5);
 
-    @Override
     public Duration getTimeMaxDelay() {
         return timeMaxDelay;
     }
@@ -41,7 +39,6 @@ public class SecurityConfigBean implements SecurityConfig {
         this.timeMaxDelay = timeMaxDelay;
     }
 
-    @Override
     public Duration getTimeMaxAhead() {
         return timeMaxAhead;
     }

+ 6 - 2
core-security/src/main/java/com/qmth/boot/core/security/model/AccessEntity.java

@@ -26,13 +26,17 @@ public interface AccessEntity {
      *
      * @return
      */
-    Collection<String> getAllowIP();
+    default Collection<String> getAllowIP() {
+        return null;
+    }
 
     /**
      * 禁止访问的IP
      *
      * @return
      */
-    Collection<String> getDenyIP();
+    default Collection<String> getDenyIP() {
+        return null;
+    }
 
 }

+ 2 - 2
core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationService.java

@@ -13,7 +13,7 @@ public interface AuthorizationService<T extends AccessEntity> {
      *
      * @param identity
      * @param type
-     * @param uri
+     * @param path
      * @return
      */
     T findByIdentity(String identity, SignatureType type, String path);
@@ -22,7 +22,7 @@ public interface AuthorizationService<T extends AccessEntity> {
      * 根据鉴权对象判断是否有路径访问权限
      *
      * @param accessEntity
-     * @param uri
+     * @param path
      * @return
      */
     boolean hasPermission(T accessEntity, String path);

+ 16 - 21
core-security/src/main/java/com/qmth/boot/core/security/service/impl/BaseAuthorizationSupport.java

@@ -1,19 +1,20 @@
 package com.qmth.boot.core.security.service.impl;
 
-import com.qmth.boot.core.security.config.SecurityConfig;
+import com.qmth.boot.core.security.config.SecurityProperties;
 import com.qmth.boot.core.security.exception.AuthorizationException;
 import com.qmth.boot.core.security.model.AccessEntity;
 import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.core.security.service.AuthorizationServiceRegistration;
 import com.qmth.boot.core.security.service.AuthorizationSupport;
 import com.qmth.boot.core.security.service.CustomizeAuthorizationService;
 import com.qmth.boot.tools.signature.SignatureEntity;
 import com.qmth.boot.tools.signature.SignatureType;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.math.NumberUtils;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotNull;
 
 /**
  * 内置默认鉴权服务
@@ -21,30 +22,23 @@ import javax.annotation.PostConstruct;
 @Component
 public class BaseAuthorizationSupport implements AuthorizationSupport {
 
-    private SecurityConfig securityConfig;
+    private SecurityProperties securityProperties;
 
     private AuthorizationServiceContainer serviceContainer;
 
-    private CustomizeAuthorizationService customizeAuthorizationService;
-
-    @Autowired
-    public void setSecurityConfig(SecurityConfig securityConfig) {
-        this.securityConfig = securityConfig;
-    }
-
-    @Autowired(required = false)
-    public void setCustomizeAuthorizationService(CustomizeAuthorizationService customizeAuthorizationService) {
-        this.customizeAuthorizationService = customizeAuthorizationService;
-    }
-
-    @PostConstruct
-    public void init() {
+    public BaseAuthorizationSupport(@NotNull SecurityProperties securityProperties,
+            @Nullable CustomizeAuthorizationService customizeAuthorizationService) {
+        this.securityProperties = securityProperties;
         this.serviceContainer = new AuthorizationServiceContainer();
-        if (this.customizeAuthorizationService != null) {
-            this.customizeAuthorizationService.register(serviceContainer);
+        if (customizeAuthorizationService != null) {
+            customizeAuthorizationService.register(serviceContainer);
         }
     }
 
+    public AuthorizationServiceRegistration getServiceContainer() {
+        return serviceContainer;
+    }
+
     @Override
     public AccessEntity validateSignature(String signature, String method, String uri, String timestamp,
             SignatureType... signTypes) {
@@ -101,10 +95,11 @@ public class BaseAuthorizationSupport implements AuthorizationSupport {
             throw AuthorizationException.TIME_INVALID;
         }
         long diff = System.currentTimeMillis() - time;
-        if (diff < (-1 * securityConfig.getTimeMaxAhead().toMillis()) || diff > securityConfig.getTimeMaxDelay()
+        if (diff < (-1 * securityProperties.getTimeMaxAhead().toMillis()) || diff > securityProperties.getTimeMaxDelay()
                 .toMillis()) {
             throw AuthorizationException.TIME_EXPIRED;
         }
         return time;
     }
+
 }

+ 2 - 0
core-security/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  com.qmth.boot.core.security.config.SecurityAutoConfiguration

+ 7 - 8
core-security/src/test/java/com/qmth/boot/test/core/security/AuthTest.java

@@ -1,6 +1,6 @@
 package com.qmth.boot.test.core.security;
 
-import com.qmth.boot.core.security.config.bean.SecurityConfigBean;
+import com.qmth.boot.core.security.config.SecurityProperties;
 import com.qmth.boot.core.security.exception.AuthorizationException;
 import com.qmth.boot.core.security.model.AccessEntity;
 import com.qmth.boot.core.security.service.impl.BaseAuthorizationSupport;
@@ -13,9 +13,9 @@ import org.junit.Test;
 
 public class AuthTest {
 
-    public static SecurityConfigBean config = new SecurityConfigBean();
+    public static SecurityProperties properties;
 
-    public static BaseAuthorizationSupport support = new BaseAuthorizationSupport();
+    public static BaseAuthorizationSupport support;
 
     public static TestAuthorizationService authorization = new TestAuthorizationService();
 
@@ -27,12 +27,11 @@ public class AuthTest {
 
     @BeforeClass
     public static void prepare() {
-        support.setSecurityConfig(config);
-        support.setCustomizeAuthorizationService(registration -> {
+        properties = new SecurityProperties();
+        support = new BaseAuthorizationSupport(properties, registration -> {
             registration.setPrefix("/api/token", SignatureType.TOKEN, authorization);
             registration.setPrefix("/api/secret", SignatureType.SECRET, authorization);
         });
-        support.init();
     }
 
     @Test
@@ -96,13 +95,13 @@ public class AuthTest {
         }
         try {
             support.validateSignature(signature, method, tokenPath,
-                    String.valueOf(System.currentTimeMillis() + config.getTimeMaxAhead().toMillis() + 1000));
+                    String.valueOf(System.currentTimeMillis() + properties.getTimeMaxAhead().toMillis() + 1000));
         } catch (AuthorizationException ee) {
             Assert.assertEquals(ee, AuthorizationException.TIME_EXPIRED);
         }
         try {
             support.validateSignature(signature, method, tokenPath,
-                    String.valueOf(System.currentTimeMillis() - config.getTimeMaxDelay().toMillis() - 1000));
+                    String.valueOf(System.currentTimeMillis() - properties.getTimeMaxDelay().toMillis() - 1000));
         } catch (AuthorizationException ee) {
             Assert.assertEquals(ee, AuthorizationException.TIME_EXPIRED);
         }

+ 50 - 0
core-solar/pom.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>qmth-boot</artifactId>
+        <groupId>com.qmth.boot</groupId>
+        <version>1.0.1</version>
+    </parent>
+    <artifactId>core-solar</artifactId>
+    <name>core-solar</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-retrofit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 20 - 0
core-solar/src/main/java/com/qmth/boot/core/solar/api/SolarApiClient.java

@@ -0,0 +1,20 @@
+package com.qmth.boot.core.solar.api;
+
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitClient;
+import com.qmth.boot.core.solar.config.SolarApiConfiguration;
+import com.qmth.boot.core.solar.model.AppInfo;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import retrofit2.http.POST;
+import retrofit2.http.Query;
+
+import java.util.List;
+
+@RetrofitClient(directReturn = true, configuration = SolarApiConfiguration.class)
+public interface SolarApiClient {
+
+    @POST("app/info")
+    AppInfo getAppInfo();
+
+    @POST("org/query")
+    List<OrgInfo> listOrgInfo(@Query("pageNumber") int pageNumber, @Query("pageSize") int pageSize);
+}

+ 38 - 0
core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarApiConfiguration.java

@@ -0,0 +1,38 @@
+package com.qmth.boot.core.solar.config;
+
+import com.qmth.boot.core.retrofit.interfaces.CustomizeRetrofitConfiguration;
+import com.qmth.boot.core.retrofit.interfaces.SignatureProvider;
+import com.qmth.boot.tools.signature.SignatureType;
+
+public class SolarApiConfiguration implements CustomizeRetrofitConfiguration {
+
+    private SolarProperties solarProperties;
+
+    public SolarApiConfiguration(SolarProperties solarProperties) {
+        this.solarProperties = solarProperties;
+    }
+
+    public String getBaseUrl() {
+        return solarProperties.getServer() + "/api/open/";
+    }
+
+    public SignatureProvider getSignature() {
+        return new SignatureProvider() {
+
+            @Override
+            public SignatureType getType() {
+                return SignatureType.SECRET;
+            }
+
+            @Override
+            public String getIdentity() {
+                return solarProperties.getAccessKey();
+            }
+
+            @Override
+            public String getSecret() {
+                return solarProperties.getAccessSecret();
+            }
+        };
+    }
+}

+ 17 - 0
core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarAutoConfiguration.java

@@ -0,0 +1,17 @@
+package com.qmth.boot.core.solar.config;
+
+import com.qmth.boot.core.retrofit.annotatioin.RetrofitScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("com.qmth.boot.core.solar.*")
+@RetrofitScan("com.qmth.boot.core.solar.*")
+public class SolarAutoConfiguration {
+
+    @Bean
+    public SolarApiConfiguration solarApiConfiguration(SolarProperties solarProperties) {
+        return new SolarApiConfiguration(solarProperties);
+    }
+}

+ 64 - 0
core-solar/src/main/java/com/qmth/boot/core/solar/config/SolarProperties.java

@@ -0,0 +1,64 @@
+package com.qmth.boot.core.solar.config;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".solar")
+public class SolarProperties {
+
+    @NotNull
+    private String server = "https://solar.qmth.com.cn";
+
+    private boolean checkup = true;
+
+    private String accessKey;
+
+    private String accessSecret;
+
+    private String license;
+
+    public String getServer() {
+        return server;
+    }
+
+    public void setServer(String server) {
+        this.server = server;
+    }
+
+    public boolean isCheckup() {
+        return checkup;
+    }
+
+    public void setCheckup(boolean checkup) {
+        this.checkup = checkup;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public void setAccessSecret(String accessSecret) {
+        this.accessSecret = accessSecret;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public void setLicense(String license) {
+        this.license = license;
+    }
+
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác