Просмотр исходного кода

Squashed commit of the following:

commit 8a51f459f8cf1ad3ac2fa049a275194be5580ddd
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Mar 22 09:31:15 2021 +0800

    ValveInterceptor修复bug

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

commit 74f9af7f452906c01fa91336a111d5c57b4497d7
Author: luoshi <luoshi@qmth.com.cn>
Date:   Sun Mar 21 16:34:37 2021 +0800

    修改core-security组件,修改找不到匹配鉴权服务时的异常类型;增加服务匹配查找单元测试代码;

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

commit bdd2f027b74340bd6b183ddd1d0186adaf35d271
Author: luoshi <luoshi@qmth.com.cn>
Date:   Sun Mar 21 16:20:24 2021 +0800

    修改core-security组件,增加自定义鉴权服务注册入口,支持按路径前缀与签名类型组合,支持默认鉴权服务设置,扩展AccessEntity对象支持动态指定IP过滤规则;
    修改starter-api鉴权拦截器逻辑,增加鉴权对象的二次IP过滤支持,去掉缺省的动态IP过滤规则注入,抽象公共的IP过滤服务;

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

commit 0ea3024c6a044caa5f5cb9d55fab918a8bdbad6a
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri Mar 19 18:00:28 2021 +0800

    增加data-mysql-mp组件,以MybatisPlus提供MySQL访问支持;
    迁移FastUUID工具到tools-common组件;
    core-logging组件扩展动态修改日志级别方法;

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

commit 818f706205b9fa80166202dc1a693cddbad04534
Author: luoshi <luoshi@qmth.com.cn>
Date:   Tue Mar 16 09:44:28 2021 +0800

    签名鉴权部分代码优化

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

commit 952cfb31dad4cd488a944fa6b6db73268e10f1e2
Author: luoshi <luoshi@qmth.com.cn>
Date:   Mon Mar 8 18:13:09 2021 +0800

    core-lock更名为core-concurrent;
    增加data-redis组件;
    修改redis底层工具为redisson;

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

commit 08680a22ddc21081510e1c05bc6ee8903c4cf2ad
Author: luoshi <luoshi@qmth.com.cn>
Date:   Sat Feb 20 18:14:13 2021 +0800

    tools-matcher模块修改为tools-common模块,增加编解码工具

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

commit f5fe395b25fe6f6608f44a963260bb635a20826c
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Jan 28 18:00:13 2021 +0800

    修改core-cache组件expire-after-write参数生效规则

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

commit 480655ac31ec8c20143c93015125b6abe7c8cceb
Author: luoshi <luoshi@qmth.com.cn>
Date:   Wed Jan 27 18:09:14 2021 +0800

    增加core-cache组件

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

commit f500f1e38efe93a6a64bee2e148937c1a10a6354
Author: luoshi <luoshi@qmth.com.cn>
Date:   Fri Jan 22 14:01:05 2021 +0800

    增加core-lock组件骨架;修改配置验证依赖;删除重复maven定义

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

commit 4d43b79f8796a73f380b6a3d24047940f91497b9
Author: luoshi <luoshi@qmth.com.cn>
Date:   Thu Jan 21 10:40:51 2021 +0800

    首批组件代码提交

    Signed-off-by: luoshi <luoshi@qmth.com.cn>
luoshi 4 лет назад
Родитель
Сommit
3c2b8f22b2
100 измененных файлов с 4591 добавлено и 0 удалено
  1. 46 0
      core-cache/pom.xml
  2. 23 0
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheConfig.java
  3. 35 0
      core-cache/src/main/java/com/qmth/boot/core/cache/config/CacheManagerConfig.java
  4. 41 0
      core-cache/src/main/java/com/qmth/boot/core/cache/config/bean/CacheConfigBean.java
  5. 5 0
      core-cache/src/main/java/com/qmth/boot/core/cache/constant/CacheConstant.java
  6. 50 0
      core-cache/src/main/java/com/qmth/boot/core/cache/service/CacheService.java
  7. 42 0
      core-cache/src/main/java/com/qmth/boot/core/cache/service/impl/CacheServiceImpl.java
  8. 46 0
      core-concurrent/pom.xml
  9. 43 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/annotation/Lockable.java
  10. 15 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/annotation/Locks.java
  11. 18 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/configuration/ConcurrentAutoConfiguration.java
  12. 149 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/configuration/LockAspectConfiguration.java
  13. 19 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/exception/TryLockFaileException.java
  14. 6 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/model/LockType.java
  15. 52 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/model/McsLock.java
  16. 27 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/ConcurrentService.java
  17. 41 0
      core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/impl/MemoryConcurrentService.java
  18. 42 0
      core-logging/pom.xml
  19. 18 0
      core-logging/src/main/java/com/qmth/boot/core/logger/config/LoggerConfig.java
  20. 92 0
      core-logging/src/main/java/com/qmth/boot/core/logger/config/bean/LoggerConfigBean.java
  21. 14 0
      core-logging/src/main/java/com/qmth/boot/core/logger/constant/LoggerConstant.java
  22. 108 0
      core-logging/src/main/java/com/qmth/boot/core/logger/context/LoggerContextService.java
  23. 38 0
      core-metrics/pom.xml
  24. 16 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/config/MetricsConfig.java
  25. 31 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/config/bean/MetricsConfigBean.java
  26. 39 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/model/MetricsResult.java
  27. 54 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/model/TimeRange.java
  28. 47 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/MetricsService.java
  29. 78 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MemoryMetricsService.java
  30. 60 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MetricsRecorder.java
  31. 35 0
      core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/TimeCounter.java
  32. 51 0
      core-metrics/src/test/java/com/qmth/boot/test/core/metrics/MetricsTest.java
  33. 21 0
      core-models/pom.xml
  34. 6 0
      core-models/src/main/java/com/qmth/boot/core/constant/CoreConstant.java
  35. 21 0
      core-models/src/main/java/com/qmth/boot/core/enums/Platform.java
  36. 17 0
      core-models/src/main/java/com/qmth/boot/core/exception/ReentrantException.java
  37. 34 0
      core-rate-limit/pom.xml
  38. 96 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitRule.java
  39. 21 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitScope.java
  40. 21 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/config/RateLimitTarget.java
  41. 18 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitPolicy.java
  42. 21 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/RateLimitService.java
  43. 23 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/EmptyRateLimitPolicy.java
  44. 78 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/MemoryRateLimitService.java
  45. 38 0
      core-rate-limit/src/main/java/com/qmth/boot/core/rateLimit/service/impl/RateLimitor.java
  46. 63 0
      core-rate-limit/src/test/java/com/qmth/boot/test/core/rateLimit/RateLimitTest.java
  47. 42 0
      core-security/pom.xml
  48. 21 0
      core-security/src/main/java/com/qmth/boot/core/security/config/SecurityConfig.java
  49. 52 0
      core-security/src/main/java/com/qmth/boot/core/security/config/bean/SecurityConfigBean.java
  50. 37 0
      core-security/src/main/java/com/qmth/boot/core/security/exception/AuthorizationException.java
  51. 38 0
      core-security/src/main/java/com/qmth/boot/core/security/model/AccessEntity.java
  52. 30 0
      core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationService.java
  53. 50 0
      core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationServiceRegistration.java
  54. 25 0
      core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationSupport.java
  55. 14 0
      core-security/src/main/java/com/qmth/boot/core/security/service/CustomizeAuthorizationService.java
  56. 81 0
      core-security/src/main/java/com/qmth/boot/core/security/service/impl/AuthorizationServiceContainer.java
  57. 110 0
      core-security/src/main/java/com/qmth/boot/core/security/service/impl/BaseAuthorizationSupport.java
  58. 48 0
      core-security/src/test/java/com/qmth/boot/test/core/security/AuthContainerTest.java
  59. 146 0
      core-security/src/test/java/com/qmth/boot/test/core/security/AuthTest.java
  60. 32 0
      core-security/src/test/java/com/qmth/boot/test/core/security/model/TestAccessEntity.java
  61. 29 0
      core-security/src/test/java/com/qmth/boot/test/core/security/model/TestAuthorizationService.java
  62. 34 0
      core-uid/pom.xml
  63. 104 0
      core-uid/src/main/java/com/qmth/boot/core/uid/UidGenerator.java
  64. 7 0
      core-uid/src/main/java/com/qmth/boot/core/uid/config/UidConfig.java
  65. 34 0
      core-uid/src/main/java/com/qmth/boot/core/uid/config/bean/UidConfigBean.java
  66. 7 0
      core-uid/src/main/java/com/qmth/boot/core/uid/service/UidMachineService.java
  67. 7 0
      core-uid/src/main/java/com/qmth/boot/core/uid/service/UidService.java
  68. 21 0
      core-uid/src/main/java/com/qmth/boot/core/uid/service/impl/LocalMachineService.java
  69. 56 0
      core-uid/src/main/java/com/qmth/boot/core/uid/service/impl/LocalUidService.java
  70. 33 0
      core-uid/src/test/java/com/qmth/boot/test/core/uid/UidTest.java
  71. 45 0
      data-mysql-mp/pom.xml
  72. 33 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/DataSourceAutoConfiguration.java
  73. 49 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/MybatisPlusAutoConfiguration.java
  74. 24 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/MysqlConfig.java
  75. 125 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/bean/MysqlConfigBean.java
  76. 48 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/logger/MybatisPlusLogger.java
  77. 37 0
      data-mysql-mp/src/main/java/com/qmth/boot/mysql/query/BaseQuery.java
  78. 69 0
      data-redis/pom.xml
  79. 49 0
      data-redis/src/main/java/com/qmth/boot/redis/cache/RedisCacheAutoConfiguration.java
  80. 15 0
      data-redis/src/main/java/com/qmth/boot/redis/concurrent/RedisConcurrentAutoConfiguration.java
  81. 27 0
      data-redis/src/main/java/com/qmth/boot/redis/concurrent/RedisConcurrentService.java
  82. 69 0
      data-redis/src/main/java/com/qmth/boot/redis/config/RedisAutoConfiguration.java
  83. 19 0
      data-redis/src/main/java/com/qmth/boot/redis/config/RedisConfig.java
  84. 83 0
      data-redis/src/main/java/com/qmth/boot/redis/config/bean/RedisConfigBean.java
  85. 15 0
      data-redis/src/main/java/com/qmth/boot/redis/constant/RedisConstant.java
  86. 15 0
      data-redis/src/main/java/com/qmth/boot/redis/rateLimit/RedisRateLimitAutoConfiguration.java
  87. 50 0
      data-redis/src/main/java/com/qmth/boot/redis/rateLimit/RedisRateLimitService.java
  88. 39 0
      data-redis/src/main/java/com/qmth/boot/redis/uid/RedisMachineService.java
  89. 15 0
      data-redis/src/main/java/com/qmth/boot/redis/uid/RedisUidAutoConfiguration.java
  90. 254 0
      pom.xml
  91. 99 0
      starter-api/pom.xml
  92. 72 0
      starter-api/src/main/java/com/qmth/boot/api/annotation/Aac.java
  93. 7 0
      starter-api/src/main/java/com/qmth/boot/api/annotation/BOOL.java
  94. 153 0
      starter-api/src/main/java/com/qmth/boot/api/config/AacConfig.java
  95. 15 0
      starter-api/src/main/java/com/qmth/boot/api/config/ApiConfig.java
  96. 56 0
      starter-api/src/main/java/com/qmth/boot/api/config/ApiInterceptorConfig.java
  97. 74 0
      starter-api/src/main/java/com/qmth/boot/api/config/bean/ApiConfigBean.java
  98. 41 0
      starter-api/src/main/java/com/qmth/boot/api/constant/ApiConstant.java
  99. 47 0
      starter-api/src/main/java/com/qmth/boot/api/exception/ApiException.java
  100. 30 0
      starter-api/src/main/java/com/qmth/boot/api/exception/DefaultErrorController.java

+ 46 - 0
core-cache/pom.xml

@@ -0,0 +1,46 @@
+<?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.0</version>
+    </parent>
+    <artifactId>core-cache</artifactId>
+    <name>core-uid</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>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

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

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

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

@@ -0,0 +1,35 @@
+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;
+    }
+}

+ 41 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/config/bean/CacheConfigBean.java

@@ -0,0 +1,41 @@
+package com.qmth.boot.core.cache.config.bean;
+
+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;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".cache")
+@Validated
+public class CacheConfigBean implements CacheConfig, CacheConstant {
+
+    private boolean allowNullValue = true;
+
+    @NotNull
+    @DurationMin(seconds = 0)
+    private Duration expireAfterWrite = Duration.ofSeconds(0);
+
+    public boolean isAllowNullValue() {
+        return allowNullValue;
+    }
+
+    public void setAllowNullValue(boolean allowNullValue) {
+        this.allowNullValue = allowNullValue;
+    }
+
+    @Override
+    public Duration getExpireAfterWrite() {
+        return expireAfterWrite;
+    }
+
+    public void setExpireAfterWrite(Duration expireAfterWrite) {
+        this.expireAfterWrite = expireAfterWrite;
+    }
+}

+ 5 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/constant/CacheConstant.java

@@ -0,0 +1,5 @@
+package com.qmth.boot.core.cache.constant;
+
+public interface CacheConstant {
+
+}

+ 50 - 0
core-cache/src/main/java/com/qmth/boot/core/cache/service/CacheService.java

@@ -0,0 +1,50 @@
+package com.qmth.boot.core.cache.service;
+
+import javax.validation.constraints.NotNull;
+import java.util.Collection;
+
+/**
+ * 编程式缓存操作服务接口
+ */
+public interface CacheService {
+
+    /**
+     * 获取所有缓存标识
+     *
+     * @return
+     */
+    Collection<String> endpoints();
+
+    /**
+     * 按缓存标识和key读取缓存值
+     *
+     * @param endpoint
+     * @param key
+     * @return
+     */
+    Object get(@NotNull String endpoint, @NotNull String key);
+
+    /**
+     * 按标识写入缓存
+     *
+     * @param endpoint
+     * @param key
+     * @param value
+     */
+    void put(@NotNull String endpoint, @NotNull String key, Object value);
+
+    /**
+     * 按标识和key强制失效
+     *
+     * @param endpoint
+     * @param key
+     */
+    void evict(@NotNull String endpoint, @NotNull String key);
+
+    /**
+     * 按标识清空所有缓存值
+     *
+     * @param endpoint
+     */
+    void clear(@NotNull String endpoint);
+}

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

@@ -0,0 +1,42 @@
+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;
+
+    @Override
+    public Collection<String> endpoints() {
+        return cacheManager.getCacheNames();
+    }
+
+    @Override
+    public Object get(String endpoint, String key) {
+        Cache.ValueWrapper wrapper = cacheManager.getCache(endpoint).get(key);
+        return wrapper != null ? wrapper.get() : null;
+    }
+
+    @Override
+    public void put(String endpoint, String key, Object value) {
+        cacheManager.getCache(endpoint).put(key, value);
+    }
+
+    @Override
+    public void evict(String endpoint, String key) {
+        cacheManager.getCache(endpoint).evict(key);
+    }
+
+    @Override
+    public void clear(String endpoint) {
+        cacheManager.getCache(endpoint).clear();
+    }
+}

+ 46 - 0
core-concurrent/pom.xml

@@ -0,0 +1,46 @@
+<?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.0</version>
+    </parent>
+    <artifactId>core-concurrent</artifactId>
+    <name>core-concurrent</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>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-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 43 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/annotation/Lockable.java

@@ -0,0 +1,43 @@
+package com.qmth.boot.core.concurrent.annotation;
+
+import com.qmth.boot.core.concurrent.model.LockType;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义锁注解,方便在方法执行过程自动上锁
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD })
+@Documented
+public @interface Lockable {
+
+    /**
+     * 锁名称,不能为空
+     *
+     * @return
+     */
+    String name();
+
+    /**
+     * 锁标识,支持SPEL表达式,可以为空
+     *
+     * @return
+     */
+    String key() default "";
+
+    /**
+     * 锁类型,默认为写锁
+     *
+     * @return
+     */
+    LockType type() default LockType.WRITE;
+
+    /**
+     * 上锁超时秒数,默认-1强制阻塞,大于等于0则尝试失败后抛出异常
+     *
+     * @return
+     */
+    int timeout() default -1;
+
+}

+ 15 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/annotation/Locks.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.core.concurrent.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 多个自定义锁注解的组合模式
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD })
+@Documented
+public @interface Locks {
+
+    Lockable[] value() default {};
+
+}

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

@@ -0,0 +1,18 @@
+package com.qmth.boot.core.concurrent.configuration;
+
+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.Configuration;
+
+@Configuration
+public class ConcurrentAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(ConcurrentService.class)
+    public ConcurrentService concurrentService() {
+        return new MemoryConcurrentService();
+    }
+
+}

+ 149 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/configuration/LockAspectConfiguration.java

@@ -0,0 +1,149 @@
+package com.qmth.boot.core.concurrent.configuration;
+
+import com.qmth.boot.core.concurrent.annotation.Lockable;
+import com.qmth.boot.core.concurrent.annotation.Locks;
+import com.qmth.boot.core.concurrent.exception.TryLockFaileException;
+import com.qmth.boot.core.concurrent.model.LockType;
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+/**
+ * 自定义锁注解的定义执行过程
+ */
+@Aspect
+@Component
+public class LockAspectConfiguration {
+
+    protected static final Logger log = LoggerFactory.getLogger(LockAspectConfiguration.class);
+
+    private static final String LOCK_KEY_SEPARATOR = "_";
+
+    @Resource
+    private ConcurrentService concurrentService;
+
+    /***
+     * 定义切入点拦截规则,拦截Lockable注解的业务方法
+     */
+    @Pointcut("@annotation(com.qmth.boot.core.concurrent.annotation.Lockable)")
+    public void lockPointCut() {
+    }
+
+    /***
+     * 定义切入点拦截规则,拦截Locks注解的业务方法
+     */
+    @Pointcut("@annotation(com.qmth.boot.core.concurrent.annotation.Locks)")
+    public void locksPointCut() {
+    }
+
+    /**
+     * AOP锁拦截
+     *
+     * @param joinPoint
+     * @return
+     * @throws Exception
+     */
+    @Around("lockPointCut()")
+    public Object lockPointProcess(ProceedingJoinPoint joinPoint) throws Throwable {
+        return process(joinPoint);
+    }
+
+    @Around("locksPointCut()")
+    public Object locksPointProcess(ProceedingJoinPoint joinPoint) throws Throwable {
+        return process(joinPoint);
+    }
+
+    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
+        // 获取切面相关信息
+        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+        Method targetMethod = methodSignature.getMethod();
+        List<Lockable> list = new LinkedList<>();
+        int count = 1;
+        Locks annotationArray = targetMethod.getAnnotation(Locks.class);
+        Lockable annotation = targetMethod.getAnnotation(Lockable.class);
+        if (annotationArray != null && annotationArray.value().length > 0) {
+            list.addAll(Arrays.asList(annotationArray.value()));
+            count = annotationArray.value().length;
+        } else if (annotation != null) {
+            list.add(annotation);
+        }
+        // 已上锁堆栈
+        Deque<Lock> stack = new ArrayDeque<>(count);
+        try {
+            // 按顺序循环尝试上锁
+            for (Lockable item : list) {
+                // 支持SPEL生成key
+                String key = StringUtils.trimToNull(item.name());
+                Assert.notNull(key, "@Lock.name should not be blank");
+                if (StringUtils.isNotEmpty(item.key())) {
+                    key = key + LOCK_KEY_SEPARATOR + parseKey(item.key(), targetMethod, joinPoint.getArgs());
+                }
+                ReadWriteLock readWriteLock = concurrentService.getReadWriteLock(key);
+                Lock lock = item.type() == LockType.READ ? readWriteLock.readLock() : readWriteLock.writeLock();
+                log.info("Start lock: name={}, type={}, timeout={}", key, item.type(), item.timeout());
+                if (item.timeout() >= 0) {
+                    // 定义了超时时间为尝试上锁模式,失败则抛出异常
+                    if (!lock.tryLock(item.timeout(), TimeUnit.SECONDS)) {
+                        throw new TryLockFaileException("Try lock faile: " + key);
+                    }
+                } else {
+                    lock.lockInterruptibly();
+                }
+                stack.push(lock);
+            }
+            return joinPoint.proceed();
+        } catch (TryLockFaileException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("Lock aspect error", e);
+            throw new RuntimeException(e);
+        } finally {
+            while (!stack.isEmpty()) {
+                stack.pop().unlock();
+            }
+        }
+    }
+
+    /**
+     * 获取缓存的key
+     * key 定义在注解上,支持SPEL表达式
+     *
+     * @return
+     */
+    private String parseKey(String key, Method method, Object[] args) {
+        //获取被拦截方法参数名列表(使用Spring支持类库)
+        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
+        String[] paraNameArr = u.getParameterNames(method);
+        //使用SPEL进行key的解析
+        ExpressionParser parser = new SpelExpressionParser();
+        //SPEL上下文
+        StandardEvaluationContext context = new StandardEvaluationContext();
+        if (paraNameArr != null) {
+            //把方法参数放入SPEL上下文中
+            for (int i = 0; i < paraNameArr.length; i++) {
+                context.setVariable(paraNameArr[i], args[i]);
+            }
+        }
+        return parser.parseExpression(key).getValue(context, String.class);
+    }
+
+}

+ 19 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/exception/TryLockFaileException.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.core.concurrent.exception;
+
+import com.qmth.boot.core.exception.ReentrantException;
+
+/**
+ * 使用自定义锁注解上锁失败抛出的异常
+ */
+public class TryLockFaileException extends ReentrantException {
+
+    private static final long serialVersionUID = -7763869249567752648L;
+
+    public TryLockFaileException(String message) {
+        super(message);
+    }
+
+    public TryLockFaileException(String message, Throwable t) {
+        super(message, t);
+    }
+}

+ 6 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/model/LockType.java

@@ -0,0 +1,6 @@
+package com.qmth.boot.core.concurrent.model;
+
+public enum LockType {
+
+    READ, WRITE;
+}

+ 52 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/model/McsLock.java

@@ -0,0 +1,52 @@
+package com.qmth.boot.core.concurrent.model;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * MCS算法实现的公平自旋锁
+ */
+public class McsLock {
+
+    private AtomicReference<McsNode> tail;
+
+    private ThreadLocal<McsNode> threadLocal;
+
+    public McsLock() {
+        this.tail = new AtomicReference<>();
+        this.threadLocal = ThreadLocal.withInitial(McsNode::new);
+    }
+
+    public void lock() {
+        McsNode current = threadLocal.get();
+        McsNode previous = tail.getAndSet(current);
+        if (previous != null) {
+            current.locked = true;
+            previous.next = current;
+            while (current.locked) {
+                //等待前置节点结束,更新当前节点状态
+            }
+        }
+    }
+
+    public void unlock() {
+        McsNode current = threadLocal.get();
+        if (current.next == null) {
+            if (tail.compareAndSet(current, null)) {
+                return;
+            }
+            while (current.next == null) {
+                //等待后置节点完成加入,更新当前节点后置引用
+            }
+        }
+        current.next.locked = false;
+        current.next = null;
+    }
+
+    private static class McsNode {
+
+        private volatile boolean locked = false;
+
+        private volatile McsNode next = null;
+
+    }
+}

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

@@ -0,0 +1,27 @@
+package com.qmth.boot.core.concurrent.service;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+/**
+ * 内置并发服务接口
+ */
+public interface ConcurrentService {
+
+    /**
+     * 获取普通锁
+     *
+     * @param name
+     * @return
+     */
+    Lock getLock(String name);
+
+    /**
+     * 获取读写锁
+     *
+     * @param name
+     * @return
+     */
+    ReadWriteLock getReadWriteLock(String name);
+
+}

+ 41 - 0
core-concurrent/src/main/java/com/qmth/boot/core/concurrent/service/impl/MemoryConcurrentService.java

@@ -0,0 +1,41 @@
+package com.qmth.boot.core.concurrent.service.impl;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+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, ReentrantReadWriteLock> readWrite = new HashMap<>();
+
+    @Override
+    public Lock getLock(String name) {
+        Lock lock = lockMap.get(name);
+        if (lock == null) {
+            synchronized (lockMap) {
+                lock = lockMap.computeIfAbsent(name, key -> new ReentrantLock());
+            }
+        }
+        return lock;
+    }
+
+    @Override
+    public ReadWriteLock getReadWriteLock(String name) {
+        ReadWriteLock lock = readWrite.get(name);
+        if (lock == null) {
+            synchronized (readWrite) {
+                lock = readWrite.computeIfAbsent(name, key -> new ReentrantReadWriteLock());
+            }
+        }
+        return lock;
+    }
+}

+ 42 - 0
core-logging/pom.xml

@@ -0,0 +1,42 @@
+<?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.0</version>
+    </parent>
+    <artifactId>core-logging</artifactId>
+    <name>core-logging</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+        </dependency>
+        <dependency>
+            <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>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

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

@@ -0,0 +1,18 @@
+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();
+}

+ 92 - 0
core-logging/src/main/java/com/qmth/boot/core/logger/config/bean/LoggerConfigBean.java

@@ -0,0 +1,92 @@
+package com.qmth.boot.core.logger.config.bean;
+
+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;
+import org.springframework.stereotype.Component;
+import org.springframework.util.unit.DataSize;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".logging")
+@Validated
+public class LoggerConfigBean implements LoggerConfig, LoggerConstant {
+
+    @NotBlank
+    private String pattern = DEFAULT_PATTERN;
+
+    @NotBlank
+    private String rootLevel = DEFAULT_LEVEL;
+
+    @Nullable
+    private String filePath;
+
+    @NotNull
+    private DataSize fileMaxSize = DataSize.ofMegabytes(100);
+
+    @NotNull
+    private DataSize totalMaxSize = DataSize.ofGigabytes(20);
+
+    @Min(1)
+    private int fileMaxHistory = 7;
+
+    @Override
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+
+    @Override
+    public String getRootLevel() {
+        return rootLevel;
+    }
+
+    public void setRootLevel(String rootLevel) {
+        this.rootLevel = rootLevel;
+    }
+
+    @Override
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    @Override
+    public DataSize getFileMaxSize() {
+        return fileMaxSize;
+    }
+
+    public void setFileMaxSize(DataSize fileMaxSize) {
+        this.fileMaxSize = fileMaxSize;
+    }
+
+    @Override
+    public DataSize getTotalMaxSize() {
+        return totalMaxSize;
+    }
+
+    public void setTotalMaxSize(DataSize totalMaxSize) {
+        this.totalMaxSize = totalMaxSize;
+    }
+
+    @Override
+    public int getFileMaxHistory() {
+        return fileMaxHistory;
+    }
+
+    public void setFileMaxHistory(int fileMaxHistory) {
+        this.fileMaxHistory = fileMaxHistory;
+    }
+}

+ 14 - 0
core-logging/src/main/java/com/qmth/boot/core/logger/constant/LoggerConstant.java

@@ -0,0 +1,14 @@
+package com.qmth.boot.core.logger.constant;
+
+public interface LoggerConstant {
+
+    String MDC_TRACE_ID = "TRACE_ID";
+
+    String MDC_CALLER = "CALLER";
+
+    String DEFAULT_LEVEL = "info";
+
+    String DEFAULT_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} | %-5level | %X{" + MDC_TRACE_ID + "} | %X{" + MDC_CALLER
+            + "} | %logger{39}:%L | %msg%n";
+
+}

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

@@ -0,0 +1,108 @@
+package com.qmth.boot.core.logger.context;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+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 org.apache.commons.lang3.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.nio.charset.StandardCharsets;
+import java.util.Iterator;
+
+@Component
+public class LoggerContextService {
+
+    private static final String TIME_ROLLING_PATTERN = ".%d{yyyy-MM-dd}-%i.log";
+
+    private static final String ROLLING_FILE_APPENDER = "RollingFileAppender";
+
+    @Resource
+    private LoggerConfig loggerConfig;
+
+    @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));
+
+        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+        encoder.setContext(context);
+        encoder.setCharset(StandardCharsets.UTF_8);
+        encoder.setPattern(loggerConfig.getPattern());
+        encoder.start();
+
+        if (StringUtils.isNotBlank(loggerConfig.getFilePath())) {
+            RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
+            appender.setContext(context);
+            appender.setEncoder(encoder);
+            appender.setFile(loggerConfig.getFilePath());
+            appender.setName(ROLLING_FILE_APPENDER);
+            appender.setAppend(true);
+
+            String filePattern;
+            String basePath = loggerConfig.getFilePath();
+            if (basePath.endsWith(".log")) {
+                filePattern = basePath.substring(0, basePath.length() - 4) + TIME_ROLLING_PATTERN;
+            } else {
+                filePattern = basePath + TIME_ROLLING_PATTERN;
+            }
+            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.setParent(appender);
+            policy.setContext(context);
+            policy.start();
+
+            appender.setRollingPolicy(policy);
+            appender.start();
+            rootLogger.addAppender(appender);
+        }
+
+        boolean hasConsole = false;
+        Iterator<Appender<ILoggingEvent>> iterator = rootLogger.iteratorForAppenders();
+        while (iterator.hasNext()) {
+            Appender<ILoggingEvent> appender = iterator.next();
+            if (appender instanceof ConsoleAppender) {
+                ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
+                consoleAppender.setEncoder(encoder);
+                consoleAppender.start();
+                hasConsole = true;
+            }
+        }
+        if (!hasConsole) {
+            ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
+            appender.setContext(context);
+            appender.setEncoder(encoder);
+            appender.setName("Console");
+            appender.setTarget("System.out");
+            appender.start();
+            rootLogger.addAppender(appender);
+        }
+    }
+
+    public void setLevel(String name, Level level) {
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        ch.qos.logback.classic.Logger logger = context.getLogger(name);
+        logger.setLevel(level);
+    }
+
+    public void setLevel(Class<?> clazz, Level level) {
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        ch.qos.logback.classic.Logger logger = context.getLogger(clazz);
+        logger.setLevel(level);
+    }
+
+}

+ 38 - 0
core-metrics/pom.xml

@@ -0,0 +1,38 @@
+<?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>
+        <groupId>com.qmth.boot</groupId>
+        <artifactId>qmth-boot</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>core-metrics</artifactId>
+    <name>core-metrics</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>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

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

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

+ 31 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/config/bean/MetricsConfigBean.java

@@ -0,0 +1,31 @@
+package com.qmth.boot.core.metrics.config.bean;
+
+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;
+
+import javax.validation.constraints.NotEmpty;
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".metrics")
+@Validated
+public class MetricsConfigBean implements MetricsConfig {
+
+    /**
+     * 默认时间分组,按0~10ms,10ms~100ms,100ms~500ms,500ms~1s,1s~10s,>10s分为6个档次
+     */
+    @NotEmpty
+    private Duration[] timeGroup = new Duration[] { Duration.ofMillis(10), Duration.ofMillis(100),
+            Duration.ofMillis(500), Duration.ofSeconds(1), Duration.ofSeconds(10) };
+
+    public Duration[] getTimeGroup() {
+        return timeGroup;
+    }
+
+    public void setTimeGroup(Duration[] timeGroup) {
+        this.timeGroup = timeGroup;
+    }
+}

+ 39 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/model/MetricsResult.java

@@ -0,0 +1,39 @@
+package com.qmth.boot.core.metrics.model;
+
+import java.util.Map;
+
+/**
+ * 单个统计点的统计结果
+ */
+public class MetricsResult {
+
+    private String endpoint;
+
+    private Map<Integer, Long> status;
+
+    private Map<TimeRange, Long> time;
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public Map<Integer, Long> getStatus() {
+        return status;
+    }
+
+    public void setStatus(Map<Integer, Long> status) {
+        this.status = status;
+    }
+
+    public Map<TimeRange, Long> getTime() {
+        return time;
+    }
+
+    public void setTime(Map<TimeRange, Long> time) {
+        this.time = time;
+    }
+}

+ 54 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/model/TimeRange.java

@@ -0,0 +1,54 @@
+package com.qmth.boot.core.metrics.model;
+
+/**
+ * 以毫秒为单位的统计时间范围
+ */
+public class TimeRange {
+
+    private long ge;
+
+    private long lt;
+
+    public TimeRange(long ge, long lt) {
+        this.ge = ge;
+        this.lt = lt;
+    }
+
+    public long getGe() {
+        return ge;
+    }
+
+    public long getLt() {
+        return lt;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (ge >= 0) {
+            sb.append(ge).append("-");
+        }
+        if (lt >= 0) {
+            sb.append(lt);
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof TimeRange)) {
+            return false;
+        }
+        TimeRange other = (TimeRange) obj;
+        return other.ge == this.ge && other.lt == this.lt;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + Long.hashCode(ge);
+        result = PRIME * result + Long.hashCode(lt);
+        return result;
+    }
+}

+ 47 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/service/MetricsService.java

@@ -0,0 +1,47 @@
+package com.qmth.boot.core.metrics.service;
+
+import com.qmth.boot.core.metrics.model.MetricsResult;
+
+import java.util.List;
+
+/**
+ * 统计服务接口
+ */
+public interface MetricsService {
+
+    /**
+     * 记录单次统计结果
+     *
+     * @param endpoint
+     * @param status
+     * @param timecost
+     */
+    void record(String endpoint, int status, long timecost);
+
+    /**
+     * 获取某个统计点的统计结果
+     *
+     * @param endpoint
+     * @return
+     */
+    MetricsResult result(String endpoint);
+
+    /**
+     * 获取所有统计点的统计结果
+     *
+     * @return
+     */
+    List<MetricsResult> result();
+
+    /**
+     * 重置某个统计点的统计结果
+     *
+     * @param endpoint
+     */
+    void reset(String endpoint);
+
+    /**
+     * 重置所有统计结果
+     */
+    void reset();
+}

+ 78 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/MemoryMetricsService.java

@@ -0,0 +1,78 @@
+package com.qmth.boot.core.metrics.service.impl;
+
+import com.qmth.boot.core.metrics.config.MetricsConfig;
+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 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;
+
+    @Bean
+    @ConditionalOnMissingBean(MetricsService.class)
+    public MetricsService metricsService() {
+        return new MemoryMetricsService();
+    }
+
+    public MemoryMetricsService() {
+        this.recorderMap = new HashMap<>();
+    }
+
+    public MetricsConfig getMetricsConfig() {
+        return metricsConfig;
+    }
+
+    public void setMetricsConfig(MetricsConfig metricsConfig) {
+        this.metricsConfig = metricsConfig;
+    }
+
+    @Override
+    public void record(String endpoint, int status, long timecost) {
+        recorderMap.computeIfAbsent(endpoint, key -> new MetricsRecorder(metricsConfig)).record(status, timecost);
+    }
+
+    @Override
+    public MetricsResult result(String endpoint) {
+        MetricsResult result = new MetricsResult();
+        MetricsRecorder recorder = recorderMap.get(endpoint);
+        if (recorder != null) {
+            result = recorder.getResult();
+        }
+        result.setEndpoint(endpoint);
+        return result;
+    }
+
+    @Override
+    public List<MetricsResult> result() {
+        List<MetricsResult> list = new ArrayList<>();
+        for (Map.Entry<String, MetricsRecorder> entry : recorderMap.entrySet()) {
+            MetricsResult result = entry.getValue().getResult();
+            result.setEndpoint(entry.getKey());
+            list.add(result);
+        }
+        return list;
+    }
+
+    @Override
+    public void reset(String endpoint) {
+        recorderMap.remove(endpoint);
+    }
+
+    @Override
+    public void reset() {
+        recorderMap.clear();
+    }
+}

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

@@ -0,0 +1,60 @@
+package com.qmth.boot.core.metrics.service.impl;
+
+import com.qmth.boot.core.metrics.config.MetricsConfig;
+import com.qmth.boot.core.metrics.model.MetricsResult;
+import com.qmth.boot.core.metrics.model.TimeRange;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MetricsRecorder {
+
+    private Map<Integer, AtomicLong> statusMap;
+
+    private List<TimeCounter> timeList;
+
+    public MetricsRecorder(MetricsConfig config) {
+        this.statusMap = new HashMap<>();
+        this.timeList = new ArrayList<>();
+        long ge = 0;
+        for (Duration time : config.getTimeGroup()) {
+            long lt = time.toMillis();
+            this.timeList.add(new TimeCounter(ge, lt));
+            ge = lt;
+        }
+        this.timeList.add(new TimeCounter(ge, -1));
+    }
+
+    public void record(int status, long timecost) {
+        statusMap.computeIfAbsent(status, key -> new AtomicLong(0)).incrementAndGet();
+        for (TimeCounter counter : timeList) {
+            if (counter.accept(timecost)) {
+                break;
+            }
+        }
+    }
+
+    public MetricsResult getResult() {
+        MetricsResult result = new MetricsResult();
+
+        Map<Integer, Long> statusResult = new HashMap<>();
+        for (Map.Entry<Integer, AtomicLong> entry : statusMap.entrySet()) {
+            statusResult.put(entry.getKey(), entry.getValue().longValue());
+        }
+        result.setStatus(statusResult);
+
+        Map<TimeRange, Long> timeResult = new HashMap<>();
+        for (TimeCounter tc : timeList) {
+            timeResult.put(tc.getTimeRage(), tc.count());
+        }
+        result.setTime(timeResult);
+
+        return result;
+    }
+
+}
+

+ 35 - 0
core-metrics/src/main/java/com/qmth/boot/core/metrics/service/impl/TimeCounter.java

@@ -0,0 +1,35 @@
+package com.qmth.boot.core.metrics.service.impl;
+
+import com.qmth.boot.core.metrics.model.TimeRange;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class TimeCounter {
+
+    private TimeRange range;
+
+    private AtomicLong count;
+
+    public TimeCounter(long ge, long lt) {
+        this.range = new TimeRange(ge, lt);
+        this.count = new AtomicLong(0);
+    }
+
+    public TimeRange getTimeRage() {
+        return this.range;
+    }
+
+    public boolean accept(long timecost) {
+        if (timecost >= range.getGe() && (range.getLt() < 0 || timecost < range.getLt())) {
+            count.incrementAndGet();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public long count() {
+        return count.get();
+    }
+
+}

+ 51 - 0
core-metrics/src/test/java/com/qmth/boot/test/core/metrics/MetricsTest.java

@@ -0,0 +1,51 @@
+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.model.MetricsResult;
+import com.qmth.boot.core.metrics.model.TimeRange;
+import com.qmth.boot.core.metrics.service.impl.MemoryMetricsService;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+public class MetricsTest {
+
+    private static MemoryMetricsService service;
+
+    private static MetricsConfig config;
+
+    @BeforeClass
+    public static void prepare() {
+        config = new MetricsConfigBean();
+        service = new MemoryMetricsService();
+        service.setMetricsConfig(config);
+    }
+
+    @AfterClass
+    public static void clear() {
+        service.reset();
+    }
+
+    @Test
+    public void testSingleEndpoint() {
+        service.record("/a1", 200, 8);
+        service.record("/a1", 200, 22);
+        service.record("/a1", 200, 130);
+        service.record("/a1", 400, 12);
+        service.record("/a1", 500, 33);
+        service.record("/a1", 401, 330);
+        service.record("/a1", 404, 1209);
+        List<MetricsResult> list = service.result();
+        Assert.assertEquals(1, list.size());
+        MetricsResult result = list.get(0);
+        Assert.assertEquals(5, result.getStatus().size());
+        Assert.assertEquals(3, result.getStatus().get(200).longValue());
+        Assert.assertEquals(3, result.getTime().get(new TimeRange(10, 100)).longValue());
+        Assert.assertEquals(0, result.getTime().get(new TimeRange(10000, -1)).longValue());
+    }
+
+}

+ 21 - 0
core-models/pom.xml

@@ -0,0 +1,21 @@
+<?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.0</version>
+    </parent>
+    <artifactId>core-models</artifactId>
+    <name>core-models</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 6 - 0
core-models/src/main/java/com/qmth/boot/core/constant/CoreConstant.java

@@ -0,0 +1,6 @@
+package com.qmth.boot.core.constant;
+
+public interface CoreConstant {
+
+    String CONFIG_PREFIX = "com.qmth";
+}

+ 21 - 0
core-models/src/main/java/com/qmth/boot/core/enums/Platform.java

@@ -0,0 +1,21 @@
+package com.qmth.boot.core.enums;
+
+public enum Platform {
+
+    IOS, ANDROID, WIN, MAC, WXAPP, WEB;
+
+    public String getName() {
+        return toString().toLowerCase();
+    }
+
+    public static Platform findByName(String name) {
+        if (name != null) {
+            for (Platform platform : values()) {
+                if (platform.toString().equalsIgnoreCase(name)) {
+                    return platform;
+                }
+            }
+        }
+        return null;
+    }
+}

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

@@ -0,0 +1,17 @@
+package com.qmth.boot.core.exception;
+
+/**
+ * 可重试的运行时异常,在API层会进行特殊处理
+ */
+public class ReentrantException extends RuntimeException {
+
+    private static final long serialVersionUID = 4759388758158289454L;
+
+    public ReentrantException(String message) {
+        super(message);
+    }
+
+    public ReentrantException(String message, Throwable t) {
+        super(message, t);
+    }
+}

+ 34 - 0
core-rate-limit/pom.xml

@@ -0,0 +1,34 @@
+<?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>
+        <groupId>com.qmth.boot</groupId>
+        <artifactId>qmth-boot</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>core-rate-limit</artifactId>
+    <name>core-rate-limit</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</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>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

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

@@ -0,0 +1,96 @@
+package com.qmth.boot.core.rateLimit.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.convert.DurationStyle;
+
+import java.time.Duration;
+
+/**
+ * 限流规则对象,可以基于标准表达式构建
+ * 表达式格式为:100/2s,斜杠左边为数量,右边为时间间隔
+ * 可选参数格式:100/id/2s,i表示单实例,d表示单设备
+ * 未配置情况下,默认参数为集群环境下的所有设备
+ */
+public class RateLimitRule {
+
+    private static final String EXPRESSION_SPLIT = "/";
+
+    private int count;
+
+    private RateLimitScope scope;
+
+    private RateLimitTarget target;
+
+    private Duration period;
+
+    public static RateLimitRule parse(String expression) {
+        String[] values = StringUtils.split(StringUtils.trimToNull(expression), EXPRESSION_SPLIT);
+        if (values != null) {
+            String number = null;
+            String duration = null;
+            RateLimitScope scope = RateLimitScope.CLUSTER;
+            RateLimitTarget target = RateLimitTarget.ALL;
+            if (values.length == 2) {
+                number = values[0];
+                duration = values[1];
+            } else if (values.length == 3) {
+                number = values[0];
+                duration = values[2];
+                char[] custom = values[1].toCharArray();
+                for (char c : custom) {
+                    RateLimitScope s = RateLimitScope.findByCode(c);
+                    if (s != null) {
+                        scope = s;
+                    }
+                    RateLimitTarget t = RateLimitTarget.findByCode(c);
+                    if (t != null) {
+                        target = t;
+                    }
+                }
+            }
+            try {
+                int count = Integer.parseInt(number);
+                Duration period = DurationStyle.detectAndParse(duration);
+                if (count > 0 && period != null) {
+                    return new RateLimitRule(count, scope, target, period);
+                }
+            } catch (Exception e) {
+            }
+        }
+        return null;
+    }
+
+    public RateLimitRule(int count, RateLimitScope scope, RateLimitTarget target, Duration period) {
+        this.count = count;
+        this.scope = scope;
+        this.target = target;
+        this.period = period;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public Duration getPeriod() {
+        return period;
+    }
+
+    public RateLimitScope getScope() {
+        return scope;
+    }
+
+    public RateLimitTarget getTarget() {
+        return target;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(count).append(EXPRESSION_SPLIT);
+        if (scope != RateLimitScope.CLUSTER && target != RateLimitTarget.ALL) {
+            sb.append(scope.getCode()).append(target.getCode()).append(EXPRESSION_SPLIT);
+        }
+        sb.append(DurationStyle.SIMPLE.print(period));
+        return sb.toString();
+    }
+}

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

@@ -0,0 +1,21 @@
+package com.qmth.boot.core.rateLimit.config;
+
+public enum RateLimitScope {
+
+    CLUSTER(""), INSTANCE("i");
+
+    private String code;
+
+    private RateLimitScope(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public static RateLimitScope findByCode(char code) {
+        return INSTANCE.getCode().equalsIgnoreCase(String.valueOf(code)) ? INSTANCE : null;
+    }
+}
+

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

@@ -0,0 +1,21 @@
+package com.qmth.boot.core.rateLimit.config;
+
+public enum RateLimitTarget {
+
+    ALL(""), DEVICE("d");
+
+    private String code;
+
+    private RateLimitTarget(String code) {
+        this.code = code;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public static RateLimitTarget findByCode(char code) {
+        return DEVICE.getCode().equalsIgnoreCase(String.valueOf(code)) ? DEVICE : null;
+    }
+}
+

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

@@ -0,0 +1,18 @@
+package com.qmth.boot.core.rateLimit.service;
+
+import com.qmth.boot.core.rateLimit.config.RateLimitRule;
+
+/**
+ * 动态获取限流规则扩展接口
+ */
+public interface RateLimitPolicy {
+
+    /**
+     * 根据API地址动态获取限流规则
+     *
+     * @param endpoint
+     * @return
+     */
+    RateLimitRule[] getRules(String endpoint);
+
+}

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

@@ -0,0 +1,21 @@
+package com.qmth.boot.core.rateLimit.service;
+
+import com.qmth.boot.core.rateLimit.config.RateLimitRule;
+
+/**
+ * 限流执行服务
+ */
+public interface RateLimitService {
+
+    /**
+     * 根据控制点、访问设备、规则集合执行限流判定
+     * 多个规则必须全满足才判定通过
+     *
+     * @param endpoint
+     * @param device
+     * @param rules
+     * @return
+     */
+    boolean accept(String endpoint, String device, RateLimitRule... rules);
+
+}

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

@@ -0,0 +1,23 @@
+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;
+    }
+
+}

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

@@ -0,0 +1,78 @@
+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.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 = "_";
+
+    protected static final int LIMITOR_ARRAY_COUNT = 10;
+
+    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)) {
+            return true;
+        }
+        for (RateLimitRule rule : rules) {
+            if (!accept(endpoint, device, rule)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    protected boolean accept(String endpoint, String device, RateLimitRule rule) {
+        String key = endpoint;
+        if (rule.getTarget() == RateLimitTarget.DEVICE) {
+            // 若限流对象是单个设备,则KEY需要包含设备标识
+            key = key.concat(KEY_JOINER).concat(device);
+        }
+        RateLimitor limitor = getLimitor(key, rule.getPeriod());
+        return limitor.count() <= rule.getCount() && limitor.incr() <= rule.getCount();
+    }
+
+    private RateLimitor getLimitor(String key, Duration period) {
+        RateLimitor[] array = limitorMap.get(key);
+        if (array == null) {
+            synchronized (limitorMap) {
+                array = limitorMap.computeIfAbsent(key, k -> initArray());
+            }
+        }
+        long time = System.currentTimeMillis() / period.toMillis();
+        int index = (int) (time % LIMITOR_ARRAY_COUNT);
+        RateLimitor limitor = array[index];
+        // 判断当前限流器归属的时间窗口,不是当前则重置
+        if (limitor.time() != time) {
+            limitor.reset(time);
+        }
+        return limitor;
+    }
+
+    private RateLimitor[] initArray() {
+        RateLimitor[] array = new RateLimitor[LIMITOR_ARRAY_COUNT];
+        for (int i = 0; i < LIMITOR_ARRAY_COUNT; i++) {
+            array[i] = new RateLimitor(0);
+        }
+        return array;
+    }
+
+}

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

@@ -0,0 +1,38 @@
+package com.qmth.boot.core.rateLimit.service.impl;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 内存限流控制单元
+ */
+public class RateLimitor {
+
+    private long time;
+
+    private AtomicInteger count;
+
+    public RateLimitor(long time) {
+        this.time = time;
+        this.count = new AtomicInteger(0);
+    }
+
+    public synchronized void reset(long time) {
+        if (time != this.time) {
+            this.time = time;
+            this.count.set(0);
+        }
+    }
+
+    public int incr() {
+        return this.count.incrementAndGet();
+    }
+
+    public long time() {
+        return time;
+    }
+
+    public int count() {
+        return count.get();
+    }
+
+}

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

@@ -0,0 +1,63 @@
+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.service.impl.MemoryRateLimitService;
+import org.apache.commons.lang3.RandomUtils;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.time.Duration;
+
+public class RateLimitTest {
+
+    private static MemoryRateLimitService service;
+
+    @BeforeClass
+    public static void prepare() {
+        service = new MemoryRateLimitService();
+    }
+
+    @Test
+    public void testRuleExpression() {
+        RateLimitRule rule = RateLimitRule.parse("1/1s");
+        Assert.assertNotNull(rule);
+        Assert.assertEquals(rule.getCount(), 1);
+        Assert.assertEquals(rule.getPeriod(), Duration.ofSeconds(1));
+        Assert.assertEquals(rule.getScope(), RateLimitScope.CLUSTER);
+        Assert.assertEquals(rule.getTarget(), RateLimitTarget.ALL);
+
+        rule = RateLimitRule.parse("10/id/500ms");
+        Assert.assertNotNull(rule);
+        Assert.assertEquals(rule.getCount(), 10);
+        Assert.assertEquals(rule.getPeriod(), Duration.ofMillis(500));
+        Assert.assertEquals(rule.getScope(), RateLimitScope.INSTANCE);
+        Assert.assertEquals(rule.getTarget(), RateLimitTarget.DEVICE);
+    }
+
+    @Test
+    public void testSimpleRule() throws InterruptedException {
+        RateLimitRule rule = RateLimitRule.parse("1/1s");
+        String endpoint = "/api/test";
+        String device = "test";
+
+        int loop = 0;
+        long time = 0;
+        while (loop < 50) {
+            long current = System.currentTimeMillis() / rule.getPeriod().toMillis();
+            boolean result = service.accept(endpoint, device, rule);
+            if (current != time) {
+                Assert.assertTrue(result);
+            } else {
+                Assert.assertFalse(result);
+            }
+            time = current;
+            loop++;
+            Thread.sleep(RandomUtils.nextInt(100, 200));
+        }
+
+    }
+
+}

+ 42 - 0
core-security/pom.xml

@@ -0,0 +1,42 @@
+<?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>
+        <groupId>com.qmth.boot</groupId>
+        <artifactId>qmth-boot</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>core-security</artifactId>
+    <name>core-security</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>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>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

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

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

+ 52 - 0
core-security/src/main/java/com/qmth/boot/core/security/config/bean/SecurityConfigBean.java

@@ -0,0 +1,52 @@
+package com.qmth.boot.core.security.config.bean;
+
+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;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+/**
+ * 鉴权模块设置参数,支持配置文件自定义修改
+ */
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".auth")
+@Validated
+public class SecurityConfigBean implements SecurityConfig {
+
+    /**
+     * 允许服务器时间晚于请求时间戳最大时长,默认15秒
+     */
+    @NotNull
+    @DurationMin(seconds = 1)
+    private Duration timeMaxDelay = Duration.ofSeconds(15);
+
+    /**
+     * 允许服务器时间早于请求时间戳最大时长,默认5秒
+     */
+    @NotNull
+    @DurationMin(seconds = 0)
+    private Duration timeMaxAhead = Duration.ofSeconds(5);
+
+    @Override
+    public Duration getTimeMaxDelay() {
+        return timeMaxDelay;
+    }
+
+    public void setTimeMaxDelay(Duration timeMaxDelay) {
+        this.timeMaxDelay = timeMaxDelay;
+    }
+
+    @Override
+    public Duration getTimeMaxAhead() {
+        return timeMaxAhead;
+    }
+
+    public void setTimeMaxAhead(Duration timeMaxAhead) {
+        this.timeMaxAhead = timeMaxAhead;
+    }
+}

+ 37 - 0
core-security/src/main/java/com/qmth/boot/core/security/exception/AuthorizationException.java

@@ -0,0 +1,37 @@
+package com.qmth.boot.core.security.exception;
+
+public class AuthorizationException extends RuntimeException {
+
+    private static final long serialVersionUID = -2876981104626449737L;
+
+    private int code;
+
+    public static AuthorizationException TIME_INVALID = new AuthorizationException(401001, "time is invalid");
+
+    public static AuthorizationException TIME_EXPIRED = new AuthorizationException(401002, "time has expired");
+
+    public static AuthorizationException SIGNATURE_INVALID = new AuthorizationException(401003, "signature is invalid");
+
+    public static AuthorizationException SIGNATURE_TYPE_INVALID = new AuthorizationException(401004,
+            "signature type invalid");
+
+    public static AuthorizationException IDENTITY_NOT_FOUND = new AuthorizationException(401005, "identity not found");
+
+    public static AuthorizationException SECRET_ERROR = new AuthorizationException(401006, "secret error");
+
+    public static AuthorizationException TOKEN_ERROR = new AuthorizationException(401007, "token error");
+
+    public static AuthorizationException NO_PERMISSION = new AuthorizationException(401008, "no permission");
+
+    public static AuthorizationException SERVICE_NOT_FOUND = new AuthorizationException(401010,
+            "authorization service not found");
+
+    public AuthorizationException(int code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    public int getCode() {
+        return code;
+    }
+}

+ 38 - 0
core-security/src/main/java/com/qmth/boot/core/security/model/AccessEntity.java

@@ -0,0 +1,38 @@
+package com.qmth.boot.core.security.model;
+
+import java.util.Collection;
+
+/**
+ * 鉴权模块对请求访问的抽象描述
+ */
+public interface AccessEntity {
+
+    /**
+     * 请求携带的身份标识
+     *
+     * @return
+     */
+    String getIdentity();
+
+    /**
+     * 鉴权所需的保密信息
+     *
+     * @return
+     */
+    String getSecret();
+
+    /**
+     * 允许访问的IP
+     *
+     * @return
+     */
+    Collection<String> getAllowIP();
+
+    /**
+     * 禁止访问的IP
+     *
+     * @return
+     */
+    Collection<String> getDenyIP();
+
+}

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

@@ -0,0 +1,30 @@
+package com.qmth.boot.core.security.service;
+
+import com.qmth.boot.core.security.model.AccessEntity;
+import com.qmth.boot.tools.signature.SignatureType;
+
+/**
+ * 应用鉴权服务接口
+ */
+public interface AuthorizationService<T extends AccessEntity> {
+
+    /**
+     * 根据签名标识、签名类型、访问路径查询鉴权对象
+     *
+     * @param identity
+     * @param type
+     * @param uri
+     * @return
+     */
+    T findByIdentity(String identity, SignatureType type, String path);
+
+    /**
+     * 根据鉴权对象判断是否有路径访问权限
+     *
+     * @param accessEntity
+     * @param uri
+     * @return
+     */
+    boolean hasPermission(T accessEntity, String path);
+
+}

+ 50 - 0
core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationServiceRegistration.java

@@ -0,0 +1,50 @@
+package com.qmth.boot.core.security.service;
+
+import com.qmth.boot.tools.signature.SignatureType;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 鉴权服务注册服务
+ */
+public interface AuthorizationServiceRegistration {
+
+    /**
+     * 按访问路径前缀与签名类型设置鉴权服务
+     *
+     * @param prefix
+     * @param service
+     * @return
+     */
+    AuthorizationServiceRegistration setPrefix(@NotBlank String prefix, @NotNull AuthorizationService service);
+
+    /**
+     * 按访问路径前缀设置鉴权服务,不限制签名类型
+     *
+     * @param prefix
+     * @param type
+     * @param service
+     * @return
+     */
+    AuthorizationServiceRegistration setPrefix(@NotBlank String prefix, @NotNull SignatureType type,
+            @NotNull AuthorizationService service);
+
+    /**
+     * 按签名类型设置默认鉴权服务
+     *
+     * @param type
+     * @param service
+     * @return
+     */
+    AuthorizationServiceRegistration setDefault(@NotNull SignatureType type, @NotNull AuthorizationService service);
+
+    /**
+     * 设置默认鉴权服务,不限制签名类型
+     *
+     * @param service
+     * @return
+     */
+    AuthorizationServiceRegistration setDefault(@NotNull AuthorizationService service);
+
+}

+ 25 - 0
core-security/src/main/java/com/qmth/boot/core/security/service/AuthorizationSupport.java

@@ -0,0 +1,25 @@
+package com.qmth.boot.core.security.service;
+
+import com.qmth.boot.core.security.exception.AuthorizationException;
+import com.qmth.boot.core.security.model.AccessEntity;
+import com.qmth.boot.tools.signature.SignatureType;
+
+/**
+ * 鉴权处理接口
+ */
+public interface AuthorizationSupport {
+
+    /**
+     * 签名鉴权方法
+     *
+     * @param signature
+     * @param method
+     * @param uri
+     * @param timestamp
+     * @param signTypes
+     * @return
+     * @throws AuthorizationException
+     */
+    AccessEntity validateSignature(String signature, String method, String uri, String timestamp,
+            SignatureType... signTypes) throws AuthorizationException;
+}

+ 14 - 0
core-security/src/main/java/com/qmth/boot/core/security/service/CustomizeAuthorizationService.java

@@ -0,0 +1,14 @@
+package com.qmth.boot.core.security.service;
+
+/**
+ * 自定义鉴权服务注册扩展接口
+ */
+public interface CustomizeAuthorizationService {
+
+    /**
+     * 注册自定义鉴权服务
+     *
+     * @param registration
+     */
+    void register(AuthorizationServiceRegistration registration);
+}

+ 81 - 0
core-security/src/main/java/com/qmth/boot/core/security/service/impl/AuthorizationServiceContainer.java

@@ -0,0 +1,81 @@
+package com.qmth.boot.core.security.service.impl;
+
+import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.core.security.service.AuthorizationServiceRegistration;
+import com.qmth.boot.tools.signature.SignatureType;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AuthorizationServiceContainer implements AuthorizationServiceRegistration {
+
+    private static final String NULL_SIGNATURE_TYPE_PLACEHOLDER = "#";
+
+    private Map<String, AuthorizationService> defaultMap;
+
+    private Map<String, Map<String, AuthorizationService>> pathMap;
+
+    public AuthorizationServiceContainer() {
+        this.defaultMap = new HashMap<>();
+        this.pathMap = new HashMap<>();
+    }
+
+    @Override
+    public AuthorizationServiceRegistration setPrefix(@NotBlank String prefix, @NotNull AuthorizationService service) {
+        pathMap.computeIfAbsent(prefix, key -> new HashMap<>()).put(NULL_SIGNATURE_TYPE_PLACEHOLDER, service);
+        return this;
+    }
+
+    @Override
+    public AuthorizationServiceRegistration setPrefix(@NotBlank String prefix, @NotNull SignatureType type,
+            @NotNull AuthorizationService service) {
+        pathMap.computeIfAbsent(prefix, key -> new HashMap<>()).put(type.getName(), service);
+        return this;
+    }
+
+    @Override
+    public AuthorizationServiceRegistration setDefault(@NotNull SignatureType type,
+            @NotNull AuthorizationService service) {
+        defaultMap.put(type.getName(), service);
+        return this;
+    }
+
+    @Override
+    public AuthorizationServiceRegistration setDefault(@NotNull AuthorizationService service) {
+        defaultMap.put(NULL_SIGNATURE_TYPE_PLACEHOLDER, service);
+        return this;
+    }
+
+    public AuthorizationService find(String path, SignatureType type) {
+        AuthorizationService service = null;
+        // 按路径前缀规则查找
+        int length = 0;
+        for (Map.Entry<String, Map<String, AuthorizationService>> entry : pathMap.entrySet()) {
+            if (path.startsWith(entry.getKey()) && entry.getKey().length() > length) {
+                AuthorizationService item = find(entry.getValue(), type);
+                if (item != null) {
+                    service = item;
+                    length = entry.getKey().length();
+                }
+            }
+        }
+        // 最后查找默认服务
+        if (service == null) {
+            service = find(defaultMap, type);
+        }
+        return service;
+    }
+
+    private AuthorizationService find(Map<String, AuthorizationService> map, SignatureType type) {
+        AuthorizationService service = null;
+        if (map != null) {
+            service = map.get(type.getName());
+            if (service == null) {
+                service = map.get(NULL_SIGNATURE_TYPE_PLACEHOLDER);
+            }
+        }
+        return service;
+    }
+}

+ 110 - 0
core-security/src/main/java/com/qmth/boot/core/security/service/impl/BaseAuthorizationSupport.java

@@ -0,0 +1,110 @@
+package com.qmth.boot.core.security.service.impl;
+
+import com.qmth.boot.core.security.config.SecurityConfig;
+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.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.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 内置默认鉴权服务
+ */
+@Component
+public class BaseAuthorizationSupport implements AuthorizationSupport {
+
+    private SecurityConfig securityConfig;
+
+    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() {
+        this.serviceContainer = new AuthorizationServiceContainer();
+        if (this.customizeAuthorizationService != null) {
+            this.customizeAuthorizationService.register(serviceContainer);
+        }
+    }
+
+    @Override
+    public AccessEntity validateSignature(String signature, String method, String uri, String timestamp,
+            SignatureType... signTypes) {
+        // 验证时间戳字段是否过期
+        long time = validateTime(timestamp);
+        // 解析签名鉴权信息
+        SignatureEntity entity = SignatureEntity.parse(signature, method, uri, time);
+        if (entity == null) {
+            throw AuthorizationException.SIGNATURE_INVALID;
+        }
+        // 过滤签名类型
+        validateSignatureType(entity, signTypes);
+        // 获取合适的鉴权服务
+        AuthorizationService service = serviceContainer.find(uri, entity.getType());
+        if (service == null) {
+            throw AuthorizationException.SERVICE_NOT_FOUND;
+        }
+        // 获取身份信息并验证摘要内容
+        AccessEntity ae = service.findByIdentity(entity.getInvoker(), entity.getType(), uri);
+        if (ae == null) {
+            throw AuthorizationException.IDENTITY_NOT_FOUND;
+        }
+        // 验证签名的密文部分
+        if (!entity.validate(ae.getSecret())) {
+            throw entity.getType() == SignatureType.SECRET ?
+                    AuthorizationException.SECRET_ERROR :
+                    AuthorizationException.TOKEN_ERROR;
+        }
+        // 提交请求对象和接口地址进行权限验证
+        if (!service.hasPermission(ae, uri)) {
+            throw AuthorizationException.NO_PERMISSION;
+        }
+        return ae;
+    }
+
+    private void validateSignatureType(SignatureEntity entity, SignatureType[] types) {
+        if (ArrayUtils.isNotEmpty(types)) {
+            boolean match = false;
+            for (SignatureType type : types) {
+                if (entity.getType() == type) {
+                    match = true;
+                    break;
+                }
+            }
+            if (!match) {
+                throw AuthorizationException.SIGNATURE_TYPE_INVALID;
+            }
+        }
+    }
+
+    private long validateTime(String timestamp) {
+        long time = NumberUtils.toLong(timestamp, 0);
+        if (time <= 0) {
+            throw AuthorizationException.TIME_INVALID;
+        }
+        long diff = System.currentTimeMillis() - time;
+        if (diff < (-1 * securityConfig.getTimeMaxAhead().toMillis()) || diff > securityConfig.getTimeMaxDelay()
+                .toMillis()) {
+            throw AuthorizationException.TIME_EXPIRED;
+        }
+        return time;
+    }
+}

+ 48 - 0
core-security/src/test/java/com/qmth/boot/test/core/security/AuthContainerTest.java

@@ -0,0 +1,48 @@
+package com.qmth.boot.test.core.security;
+
+import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.core.security.service.impl.AuthorizationServiceContainer;
+import com.qmth.boot.test.core.security.model.TestAuthorizationService;
+import com.qmth.boot.tools.signature.SignatureType;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AuthContainerTest {
+
+    private static AuthorizationServiceContainer container;
+
+    private static AuthorizationService s1, s2;
+
+    @BeforeClass
+    public static void init() {
+        s1 = new TestAuthorizationService();
+        s2 = new TestAuthorizationService();
+        container = new AuthorizationServiceContainer();
+    }
+
+    @Test
+    public void testPrefix() {
+        container.setPrefix("/a", s1);
+        container.setPrefix("/ab", s2);
+        container.setPrefix("/abc", s1);
+        container.setPrefix("/a/b", s1);
+        container.setDefault(s2);
+
+        Assert.assertEquals(container.find("/a/b/c", SignatureType.TOKEN), s1);
+        Assert.assertEquals(container.find("/ab/c", SignatureType.TOKEN), s2);
+        Assert.assertEquals(container.find("/abcd", SignatureType.TOKEN), s1);
+        Assert.assertEquals(container.find("/ac", SignatureType.TOKEN), s1);
+        Assert.assertEquals(container.find("/b", SignatureType.TOKEN), s2);
+    }
+
+    @Test
+    public void testType() {
+        container.setPrefix("/a/b", SignatureType.TOKEN, s1);
+        container.setPrefix("/ab", SignatureType.SECRET, s2);
+
+        Assert.assertNull(container.find("/a/b/c", SignatureType.SECRET));
+        Assert.assertNull(container.find("/ab/c", SignatureType.TOKEN));
+    }
+
+}

+ 146 - 0
core-security/src/test/java/com/qmth/boot/test/core/security/AuthTest.java

@@ -0,0 +1,146 @@
+package com.qmth.boot.test.core.security;
+
+import com.qmth.boot.core.security.config.bean.SecurityConfigBean;
+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;
+import com.qmth.boot.test.core.security.model.TestAuthorizationService;
+import com.qmth.boot.tools.signature.SignatureEntity;
+import com.qmth.boot.tools.signature.SignatureType;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class AuthTest {
+
+    public static SecurityConfigBean config = new SecurityConfigBean();
+
+    public static BaseAuthorizationSupport support = new BaseAuthorizationSupport();
+
+    public static TestAuthorizationService authorization = new TestAuthorizationService();
+
+    public static String method = "post";
+
+    public static String tokenPath = "/api/token";
+
+    public static String secretPath = "/api/secret";
+
+    @BeforeClass
+    public static void prepare() {
+        support.setSecurityConfig(config);
+        support.setCustomizeAuthorizationService(registration -> {
+            registration.setPrefix("/api/token", SignatureType.TOKEN, authorization);
+            registration.setPrefix("/api/secret", SignatureType.SECRET, authorization);
+        });
+        support.init();
+    }
+
+    @Test
+    public void testAuthSecret() {
+        long time = System.currentTimeMillis();
+        String accessKey = "123456";
+        String accessSecret = "secret";
+        authorization.secret = accessSecret;
+        String signature = SignatureEntity
+                .build(SignatureType.SECRET, method, secretPath, time, accessKey, accessSecret);
+
+        try {
+            support.validateSignature(signature, method, "/api/token", String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.SERVICE_NOT_FOUND);
+        }
+        authorization.disturb = true;
+        try {
+            support.validateSignature(signature, method, secretPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.IDENTITY_NOT_FOUND);
+        }
+        authorization.disturb = false;
+        authorization.secret = "error";
+        try {
+            support.validateSignature(signature, method, secretPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.SECRET_ERROR);
+        }
+        authorization.secret = accessSecret;
+        authorization.permission = false;
+        try {
+            support.validateSignature(signature, method, secretPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.NO_PERMISSION);
+        }
+        authorization.permission = true;
+        AccessEntity entity = support.validateSignature(signature, method, secretPath, String.valueOf(time));
+        Assert.assertNotNull(entity);
+        Assert.assertEquals(entity.getIdentity(), accessKey);
+        Assert.assertEquals(entity.getSecret(), accessSecret);
+    }
+
+    @Test
+    public void testAuthToken() {
+        long time = System.currentTimeMillis();
+        String sessionId = "123456";
+        String token = "token";
+        authorization.secret = token;
+        String signature = SignatureEntity.build(SignatureType.TOKEN, method, tokenPath, time, sessionId, token);
+
+        try {
+            support.validateSignature(signature, method, tokenPath, "");
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.TIME_INVALID);
+        }
+        try {
+            support.validateSignature(signature, method, tokenPath, "0");
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.TIME_INVALID);
+        }
+        try {
+            support.validateSignature(signature, method, tokenPath,
+                    String.valueOf(System.currentTimeMillis() + config.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));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.TIME_EXPIRED);
+        }
+        try {
+            support.validateSignature("", method, tokenPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.SIGNATURE_INVALID);
+        }
+        try {
+            support.validateSignature(signature, method, "/api/wasd", String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.SERVICE_NOT_FOUND);
+        }
+        authorization.disturb = true;
+        try {
+            support.validateSignature(signature, method, tokenPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.IDENTITY_NOT_FOUND);
+        }
+        authorization.disturb = false;
+        authorization.secret = "error";
+        try {
+            support.validateSignature(signature, method, tokenPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.TOKEN_ERROR);
+        }
+        authorization.secret = token;
+        authorization.permission = false;
+        try {
+            support.validateSignature(signature, method, tokenPath, String.valueOf(time));
+        } catch (AuthorizationException ee) {
+            Assert.assertEquals(ee, AuthorizationException.NO_PERMISSION);
+        }
+        authorization.permission = true;
+        AccessEntity entity = support.validateSignature(signature, method, tokenPath, String.valueOf(time));
+        Assert.assertNotNull(entity);
+        Assert.assertEquals(entity.getIdentity(), sessionId);
+        Assert.assertEquals(entity.getSecret(), token);
+    }
+
+}

+ 32 - 0
core-security/src/test/java/com/qmth/boot/test/core/security/model/TestAccessEntity.java

@@ -0,0 +1,32 @@
+package com.qmth.boot.test.core.security.model;
+
+import com.qmth.boot.core.security.model.AccessEntity;
+
+import java.util.Collection;
+
+public class TestAccessEntity implements AccessEntity {
+
+    public String identity;
+
+    public String secret;
+
+    @Override
+    public String getIdentity() {
+        return identity;
+    }
+
+    @Override
+    public String getSecret() {
+        return secret;
+    }
+
+    @Override
+    public Collection<String> getAllowIP() {
+        return null;
+    }
+
+    @Override
+    public Collection<String> getDenyIP() {
+        return null;
+    }
+}

+ 29 - 0
core-security/src/test/java/com/qmth/boot/test/core/security/model/TestAuthorizationService.java

@@ -0,0 +1,29 @@
+package com.qmth.boot.test.core.security.model;
+
+import com.qmth.boot.core.security.service.AuthorizationService;
+import com.qmth.boot.tools.signature.SignatureType;
+
+public class TestAuthorizationService implements AuthorizationService<TestAccessEntity> {
+
+    public String secret;
+
+    public boolean permission = true;
+
+    public boolean disturb = false;
+
+    @Override
+    public TestAccessEntity findByIdentity(String identity, SignatureType type, String path) {
+        if (disturb) {
+            return null;
+        }
+        TestAccessEntity entity = new TestAccessEntity();
+        entity.identity = identity;
+        entity.secret = secret;
+        return entity;
+    }
+
+    @Override
+    public boolean hasPermission(TestAccessEntity accessEntity, String path) {
+        return permission;
+    }
+}

+ 34 - 0
core-uid/pom.xml

@@ -0,0 +1,34 @@
+<?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>
+        <groupId>com.qmth.boot</groupId>
+        <artifactId>qmth-boot</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>core-uid</artifactId>
+    <name>core-uid</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>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 104 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/UidGenerator.java

@@ -0,0 +1,104 @@
+package com.qmth.boot.core.uid;
+
+/**
+ * SnowFlake算法修改版生成递增分布式ID<br/>
+ *
+ * @author luoshi
+ */
+public class UidGenerator {
+
+    // 起始时间戳,2020-06-01 00:00:00
+    private final static long START_TIMESTAMP = 1590940800000L;
+
+    // 序列号占用的位数
+    private final static long SEQUENCE_BIT = 12;
+
+    // 数据中心编号占用的位数
+    private final static long DATACENTER_BIT = 3;
+
+    // 机器编号占用的位数
+    private final static long MACHINE_BIT = 7;
+
+    // 最大数据中心编号7
+    public final static long MAX_DATACENTER_ID = ~(-1L << DATACENTER_BIT);
+
+    // 最大机器编号127
+    public final static long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
+
+    // 最大序列号
+    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
+
+    /**
+     * 每一部分向左的位移
+     */
+    private final static long MACHINE_LEFT = SEQUENCE_BIT;
+
+    private final static long DATACENTER_LEFT = MACHINE_LEFT + MACHINE_BIT;
+
+    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+
+    // 数据中心编号
+    private long datacenterId;
+
+    // 机器编号
+    private long machineId;
+
+    // 序列号
+    private long sequence = 0L;
+
+    // 上一次时间戳
+    private long lastTimestamp = -1L;
+
+    public UidGenerator(long datacenterId, long machineId) {
+        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
+            throw new IllegalArgumentException(
+                    "datacenterId can't be greater than " + MAX_DATACENTER_ID + " or less than 0");
+        }
+        if (machineId > MAX_MACHINE_ID || machineId < 0) {
+            throw new IllegalArgumentException("machineId can't be greater than " + MAX_MACHINE_ID + " or less than 0");
+        }
+        this.datacenterId = datacenterId;
+        this.machineId = machineId;
+    }
+
+    /**
+     * 产生下一个UID
+     *
+     * @return
+     */
+    public synchronized long next() {
+        long current = System.currentTimeMillis();
+        // 不能在程序运行过程中回拨时钟,否则报异常需要等待时钟赶上后重试
+        if (current < lastTimestamp) {
+            throw new RuntimeException("Clock moved backwards.  Refusing to generate uid");
+        }
+
+        if (current == lastTimestamp) {
+            // 相同毫秒内,序列号自增
+            sequence = (sequence + 1) & MAX_SEQUENCE;
+            // 同一毫秒的序列号已经用完,等待延迟到下一毫秒
+            if (sequence == 0L) {
+                current = waitNextMillisecond();
+            }
+        } else {
+            // 不同毫秒内,序列号置为0
+            sequence = 0L;
+        }
+
+        lastTimestamp = current;
+
+        return (current - START_TIMESTAMP) << TIMESTMP_LEFT // 时间戳部分
+                | (datacenterId << DATACENTER_LEFT) //数据中心部分
+                | (machineId << MACHINE_LEFT) // 机器部分
+                | sequence; // 序列号部分
+    }
+
+    private long waitNextMillisecond() {
+        long time = System.currentTimeMillis();
+        while (time <= lastTimestamp) {
+            time = System.currentTimeMillis();
+        }
+        return time;
+    }
+
+}

+ 7 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/config/UidConfig.java

@@ -0,0 +1,7 @@
+package com.qmth.boot.core.uid.config;
+
+public interface UidConfig {
+
+    int getDatacenterId();
+
+}

+ 34 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/config/bean/UidConfigBean.java

@@ -0,0 +1,34 @@
+package com.qmth.boot.core.uid.config.bean;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import com.qmth.boot.core.uid.UidGenerator;
+import com.qmth.boot.core.uid.config.UidConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".uid")
+public class UidConfigBean implements UidConfig {
+
+    private int datacenterId = 0;
+
+    @PostConstruct
+    public void init() {
+        if (datacenterId < 0 || datacenterId > UidGenerator.MAX_DATACENTER_ID) {
+            throw new RuntimeException(
+                    "[com.qmth.uid.datacenter-id] invalid, should between 0 and " + UidGenerator.MAX_DATACENTER_ID);
+        }
+    }
+
+    @Override
+    public int getDatacenterId() {
+        return datacenterId;
+    }
+
+    public void setDatacenterId(int datacenterId) {
+        this.datacenterId = datacenterId;
+    }
+
+}

+ 7 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/service/UidMachineService.java

@@ -0,0 +1,7 @@
+package com.qmth.boot.core.uid.service;
+
+public interface UidMachineService {
+
+    int getMachineId();
+
+}

+ 7 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/service/UidService.java

@@ -0,0 +1,7 @@
+package com.qmth.boot.core.uid.service;
+
+public interface UidService {
+
+    long getId();
+
+}

+ 21 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/service/impl/LocalMachineService.java

@@ -0,0 +1,21 @@
+package com.qmth.boot.core.uid.service.impl;
+
+import com.qmth.boot.core.uid.service.UidMachineService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LocalMachineService implements UidMachineService {
+
+    @Bean
+    @ConditionalOnMissingBean(UidMachineService.class)
+    public UidMachineService uidMachineService() {
+        return new LocalMachineService();
+    }
+
+    @Override
+    public int getMachineId() {
+        return 0;
+    }
+}

+ 56 - 0
core-uid/src/main/java/com/qmth/boot/core/uid/service/impl/LocalUidService.java

@@ -0,0 +1,56 @@
+package com.qmth.boot.core.uid.service.impl;
+
+import com.qmth.boot.core.uid.UidGenerator;
+import com.qmth.boot.core.uid.config.UidConfig;
+import com.qmth.boot.core.uid.service.UidMachineService;
+import com.qmth.boot.core.uid.service.UidService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+
+@Configuration
+public class LocalUidService implements UidService {
+
+    @Resource
+    private UidConfig uidConfig;
+
+    @Resource
+    private UidMachineService uidMachineService;
+
+    private UidGenerator generator;
+
+    @Bean
+    @ConditionalOnMissingBean(UidService.class)
+    public UidService uidService() {
+        return new LocalUidService();
+    }
+
+    @PostConstruct
+    public void init() {
+        this.generator = new UidGenerator(uidConfig.getDatacenterId(), uidMachineService.getMachineId());
+    }
+
+    @Override
+    public long getId() {
+        return generator.next();
+    }
+
+    public UidConfig getUidConfig() {
+        return uidConfig;
+    }
+
+    public void setUidConfig(UidConfig uidConfig) {
+        this.uidConfig = uidConfig;
+    }
+
+    public UidMachineService getUidMachineService() {
+        return uidMachineService;
+    }
+
+    public void setUidMachineService(UidMachineService uidMachineService) {
+        this.uidMachineService = uidMachineService;
+    }
+}

+ 33 - 0
core-uid/src/test/java/com/qmth/boot/test/core/uid/UidTest.java

@@ -0,0 +1,33 @@
+package com.qmth.boot.test.core.uid;
+
+import com.qmth.boot.core.uid.config.bean.UidConfigBean;
+import com.qmth.boot.core.uid.service.impl.LocalMachineService;
+import com.qmth.boot.core.uid.service.impl.LocalUidService;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class UidTest {
+
+    private static UidConfigBean uidConfig;
+
+    private static LocalUidService uidService;
+
+    private static LocalMachineService machineService;
+
+    @BeforeClass
+    public static void prepare() {
+        uidConfig = new UidConfigBean();
+        machineService = new LocalMachineService();
+
+        uidService = new LocalUidService();
+        uidService.setUidConfig(uidConfig);
+        uidService.setUidMachineService(machineService);
+        uidService.init();
+    }
+
+    @Test
+    public void test() {
+        Assert.assertTrue(uidService.getId() > 0);
+    }
+}

+ 45 - 0
data-mysql-mp/pom.xml

@@ -0,0 +1,45 @@
+<?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.0</version>
+    </parent>
+    <artifactId>data-mysql-mp</artifactId>
+    <name>data-mysql-mp</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>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>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 33 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/DataSourceAutoConfiguration.java

@@ -0,0 +1,33 @@
+package com.qmth.boot.mysql.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+/**
+ * MySQL数据源配置
+ */
+@Configuration
+public class DataSourceAutoConfiguration {
+
+    @Resource
+    private MysqlConfig mysqlConfig;
+
+    @Bean
+    public DataSource dataSource() {
+        HikariDataSource dataSource = new HikariDataSource();
+        dataSource.setJdbcUrl(mysqlConfig.getUrl());
+        dataSource.setUsername(mysqlConfig.getUsername());
+        dataSource.setPassword(mysqlConfig.getPassword());
+        dataSource.setMinimumIdle(mysqlConfig.getMinIdle());
+        dataSource.setMaximumPoolSize(mysqlConfig.getMaxPoolSize());
+        dataSource.setMaxLifetime(mysqlConfig.getMaxLifetime().toMillis());
+        dataSource.setIdleTimeout(mysqlConfig.getIdleTimeout().toMillis());
+        dataSource.setConnectionTimeout(mysqlConfig.getConnectionTimeout().toMillis());
+        return dataSource;
+    }
+
+}

+ 49 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/MybatisPlusAutoConfiguration.java

@@ -0,0 +1,49 @@
+package com.qmth.boot.mysql.config;
+
+import ch.qos.logback.classic.Level;
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.qmth.boot.core.logger.context.LoggerContextService;
+import com.qmth.boot.mysql.logger.MybatisPlusLogger;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+/**
+ * MyBatisPlus插件配置
+ */
+@Configuration
+public class MybatisPlusAutoConfiguration {
+
+    @Resource
+    private MysqlConfig mysqlConfig;
+
+    @Resource
+    private LoggerContextService loggerContextService;
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
+        return interceptor;
+    }
+
+    @Bean
+    public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
+        return plusProperties -> plusProperties.getGlobalConfig().setBanner(false);
+    }
+
+    @Bean
+    public ConfigurationCustomizer configurationCustomizer() {
+        return configuration -> {
+            loggerContextService.setLevel(MybatisPlusLogger.class, Level.toLevel(mysqlConfig.getLogLevel()));
+            configuration.setLogImpl(MybatisPlusLogger.class);
+        };
+    }
+}

+ 24 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/MysqlConfig.java

@@ -0,0 +1,24 @@
+package com.qmth.boot.mysql.config;
+
+import java.time.Duration;
+
+public interface MysqlConfig {
+
+    String getUrl();
+
+    String getUsername();
+
+    String getPassword();
+
+    int getMinIdle();
+
+    int getMaxPoolSize();
+
+    Duration getIdleTimeout();
+
+    Duration getConnectionTimeout();
+
+    Duration getMaxLifetime();
+
+    String getLogLevel();
+}

+ 125 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/config/bean/MysqlConfigBean.java

@@ -0,0 +1,125 @@
+package com.qmth.boot.mysql.config.bean;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import com.qmth.boot.core.logger.constant.LoggerConstant;
+import com.qmth.boot.mysql.config.MysqlConfig;
+import org.hibernate.validator.constraints.time.DurationMin;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".mysql")
+@Validated
+public class MysqlConfigBean implements MysqlConfig {
+
+    @NotBlank
+    private String url;
+
+    @NotBlank
+    private String username;
+
+    @NotBlank
+    private String password;
+
+    @Min(0)
+    private int minIdle = 5;
+
+    @Min(1)
+    private int maxPoolSize = 10;
+
+    @NotNull
+    @DurationMin(seconds = 60)
+    private Duration idleTimeout = Duration.ofMinutes(10);
+
+    @NotNull
+    @DurationMin(seconds = 1)
+    private Duration connectionTimeout = Duration.ofSeconds(30);
+
+    @NotNull
+    @DurationMin(millis = 0)
+    private Duration maxLifetime = Duration.ofMillis(0);
+
+    @NotNull
+    private String logLevel = LoggerConstant.DEFAULT_LEVEL;
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Override
+    public int getMinIdle() {
+        return minIdle;
+    }
+
+    public void setMinIdle(int minIdle) {
+        this.minIdle = minIdle;
+    }
+
+    @Override
+    public int getMaxPoolSize() {
+        return maxPoolSize;
+    }
+
+    public void setMaxPoolSize(int maxPoolSize) {
+        this.maxPoolSize = maxPoolSize;
+    }
+
+    public Duration getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    public void setIdleTimeout(Duration idleTimeout) {
+        this.idleTimeout = idleTimeout;
+    }
+
+    public Duration getConnectionTimeout() {
+        return connectionTimeout;
+    }
+
+    public void setConnectionTimeout(Duration connectionTimeout) {
+        this.connectionTimeout = connectionTimeout;
+    }
+
+    public Duration getMaxLifetime() {
+        return maxLifetime;
+    }
+
+    public void setMaxLifetime(Duration maxLifetime) {
+        this.maxLifetime = maxLifetime;
+    }
+
+    @Override
+    public String getLogLevel() {
+        return logLevel;
+    }
+
+    public void setLogLevel(String logLevel) {
+        this.logLevel = logLevel;
+    }
+}

+ 48 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/logger/MybatisPlusLogger.java

@@ -0,0 +1,48 @@
+package com.qmth.boot.mysql.logger;
+
+import org.apache.ibatis.logging.Log;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MybatisPlusLogger implements Log {
+
+    private static final Logger log = LoggerFactory.getLogger(MybatisPlusLogger.class);
+
+    public MybatisPlusLogger(String clazz) {
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return log.isDebugEnabled();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return log.isTraceEnabled();
+    }
+
+    @Override
+    public void error(String s, Throwable throwable) {
+        log.error(s, throwable);
+    }
+
+    @Override
+    public void error(String s) {
+        log.error(s);
+    }
+
+    @Override
+    public void debug(String s) {
+        log.debug(s);
+    }
+
+    @Override
+    public void trace(String s) {
+        log.trace(s);
+    }
+
+    @Override
+    public void warn(String s) {
+        log.warn(s);
+    }
+}

+ 37 - 0
data-mysql-mp/src/main/java/com/qmth/boot/mysql/query/BaseQuery.java

@@ -0,0 +1,37 @@
+package com.qmth.boot.mysql.query;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+/**
+ * 分页排序查询基类,默认分页20条
+ *
+ * @param <T>
+ */
+public class BaseQuery<T> extends Page<T> {
+
+    private static final long serialVersionUID = -1065595998785067382L;
+
+    public BaseQuery() {
+        super(1, 20);
+    }
+
+    public BaseQuery(long pageNumber, long pageSize) {
+        super(pageNumber, pageSize);
+    }
+
+    public void setPageNumber(long pageNumber) {
+        setCurrent(pageNumber);
+    }
+
+    public long getPageNumber() {
+        return getCurrent();
+    }
+
+    public void setPageSize(long pageSize) {
+        setSize(pageSize);
+    }
+
+    public long getPageSize() {
+        return getSize();
+    }
+}

+ 69 - 0
data-redis/pom.xml

@@ -0,0 +1,69 @@
+<?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.0</version>
+    </parent>
+    <artifactId>data-redis</artifactId>
+    <name>data-redis</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-cache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-concurrent</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-rate-limit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-uid</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>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-data-21</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 49 - 0
data-redis/src/main/java/com/qmth/boot/redis/cache/RedisCacheAutoConfiguration.java

@@ -0,0 +1,49 @@
+package com.qmth.boot.redis.cache;
+
+import com.qmth.boot.core.cache.config.CacheConfig;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+
+import javax.annotation.Resource;
+
+@Configuration
+public class RedisCacheAutoConfiguration extends CachingConfigurerSupport implements RedisConstant {
+
+    @Resource
+    private RedisConnectionFactory redisConnectionFactory;
+
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Resource
+    private CacheConfig cacheConfig;
+
+    @Bean
+    public CacheManager cacheManager() {
+        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
+                //固定key格式与前缀
+                .computePrefixWith(key -> CACHE_PREFIX + KEY_SEPARATOR + key + KEY_SEPARATOR)
+                //序列化方法
+                .serializeKeysWith(
+                        RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
+                .serializeValuesWith(
+                        RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
+        //禁止空值保存
+        if (!cacheConfig.isAllowNullValue()) {
+            config = config.disableCachingNullValues();
+        }
+        //写入失效时长大于0毫秒时才启用
+        if (cacheConfig.getExpireAfterWrite().toMillis() > 0) {
+            config = config.entryTtl(cacheConfig.getExpireAfterWrite());
+        }
+        return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
+    }
+}

+ 15 - 0
data-redis/src/main/java/com/qmth/boot/redis/concurrent/RedisConcurrentAutoConfiguration.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.redis.concurrent;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RedisConcurrentAutoConfiguration implements RedisConstant {
+
+    @Bean
+    public ConcurrentService concurrentService() {
+        return new RedisConcurrentService();
+    }
+}

+ 27 - 0
data-redis/src/main/java/com/qmth/boot/redis/concurrent/RedisConcurrentService.java

@@ -0,0 +1,27 @@
+package com.qmth.boot.redis.concurrent;
+
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+@Service
+public class RedisConcurrentService implements ConcurrentService, RedisConstant {
+
+    @Resource
+    private RedissonClient redissonClient;
+
+    @Override
+    public Lock getLock(String name) {
+        return redissonClient.getLock(LOCK_PREFIX.concat(KEY_SEPARATOR).concat(name));
+    }
+
+    @Override
+    public ReadWriteLock getReadWriteLock(String name) {
+        return redissonClient.getReadWriteLock(LOCK_PREFIX.concat(KEY_SEPARATOR).concat(name));
+    }
+}

+ 69 - 0
data-redis/src/main/java/com/qmth/boot/redis/config/RedisAutoConfiguration.java

@@ -0,0 +1,69 @@
+package com.qmth.boot.redis.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.codec.StringCodec;
+import org.redisson.codec.CompositeCodec;
+import org.redisson.codec.JsonJacksonCodec;
+import org.redisson.config.Config;
+import org.redisson.spring.data.connection.RedissonConnectionFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+import javax.annotation.Resource;
+
+@Configuration
+public class RedisAutoConfiguration {
+
+    @Resource
+    private RedisConfig redisConfig;
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        // 使用 GenericJackson2JsonRedisSerializer 替换默认序列化
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
+        // 解决jackson2无法反序列化LocalDateTime的问题
+        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        mapper.registerModule(new JavaTimeModule());
+        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(mapper);
+        // 设置序列化规则
+        redisTemplate.setKeySerializer(RedisSerializer.string());
+        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
+        redisTemplate.setHashKeySerializer(RedisSerializer.string());
+        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
+        return redisTemplate;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(RedisConnectionFactory.class)
+    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
+        return new RedissonConnectionFactory(redisson);
+    }
+
+    @Bean(destroyMethod = "shutdown")
+    @ConditionalOnMissingBean(RedissonClient.class)
+    public RedissonClient redissonClient() {
+        Config config = new Config();
+        String prefix = redisConfig.isUseSsl() ? "rediss://" : "redis://";
+        config.useSingleServer().setAddress(prefix + redisConfig.getHost() + ":" + redisConfig.getPort())
+                .setDatabase(redisConfig.getDb()).setPassword(redisConfig.getPassword())
+                .setTimeout((int) redisConfig.getTimeout().toMillis());
+        config.setCodec(new CompositeCodec(StringCodec.INSTANCE, StringCodec.INSTANCE, JsonJacksonCodec.INSTANCE));
+        return Redisson.create(config);
+    }
+}

+ 19 - 0
data-redis/src/main/java/com/qmth/boot/redis/config/RedisConfig.java

@@ -0,0 +1,19 @@
+package com.qmth.boot.redis.config;
+
+import java.time.Duration;
+
+public interface RedisConfig {
+
+    String getHost();
+
+    int getPort();
+
+    int getDb();
+
+    String getPassword();
+
+    boolean isUseSsl();
+
+    Duration getTimeout();
+
+}

+ 83 - 0
data-redis/src/main/java/com/qmth/boot/redis/config/bean/RedisConfigBean.java

@@ -0,0 +1,83 @@
+package com.qmth.boot.redis.config.bean;
+
+import com.qmth.boot.core.constant.CoreConstant;
+import com.qmth.boot.redis.config.RedisConfig;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = CoreConstant.CONFIG_PREFIX + ".redis")
+@Validated
+public class RedisConfigBean implements RedisConfig {
+
+    @NotBlank
+    private String host;
+
+    @Min(0)
+    private int port = 6379;
+
+    @Min(0)
+    private int db = 0;
+
+    private String password;
+
+    private boolean useSsl = false;
+
+    @NotNull
+    private Duration timeout = Duration.ofSeconds(3);
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public int getDb() {
+        return db;
+    }
+
+    public void setDb(int db) {
+        this.db = db;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public boolean isUseSsl() {
+        return useSsl;
+    }
+
+    public void setUseSsl(boolean useSsl) {
+        this.useSsl = useSsl;
+    }
+
+    public Duration getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(Duration timeout) {
+        this.timeout = timeout;
+    }
+
+}

+ 15 - 0
data-redis/src/main/java/com/qmth/boot/redis/constant/RedisConstant.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.redis.constant;
+
+public interface RedisConstant {
+
+    String CACHE_PREFIX = "$cache";
+
+    String LOCK_PREFIX = "$lock";
+
+    String RATE_LIMIT_PREFIX = "$rate-limit";
+
+    String MACHINE_ID_PREFIX = "$machine-id";
+
+    String KEY_SEPARATOR = ":";
+
+}

+ 15 - 0
data-redis/src/main/java/com/qmth/boot/redis/rateLimit/RedisRateLimitAutoConfiguration.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.redis.rateLimit;
+
+import com.qmth.boot.core.rateLimit.service.RateLimitService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RedisRateLimitAutoConfiguration implements RedisConstant {
+
+    @Bean
+    public RateLimitService rateLimitService() {
+        return new RedisRateLimitService();
+    }
+}

+ 50 - 0
data-redis/src/main/java/com/qmth/boot/redis/rateLimit/RedisRateLimitService.java

@@ -0,0 +1,50 @@
+package com.qmth.boot.redis.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.service.RateLimitService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.apache.commons.lang3.ArrayUtils;
+import org.redisson.api.RRateLimiter;
+import org.redisson.api.RateIntervalUnit;
+import org.redisson.api.RateType;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class RedisRateLimitService implements RateLimitService, RedisConstant {
+
+    @Resource
+    private RedissonClient redissonClient;
+
+    @Override
+    public boolean accept(String endpoint, String device, RateLimitRule... rules) {
+        if (ArrayUtils.isEmpty(rules)) {
+            return true;
+        }
+        for (RateLimitRule rule : rules) {
+            if (!accept(endpoint, device, rule)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    protected boolean accept(String endpoint, String device, RateLimitRule rule) {
+        String key = RATE_LIMIT_PREFIX.concat(KEY_SEPARATOR).concat(endpoint);
+        if (rule.getTarget() == RateLimitTarget.DEVICE) {
+            // 若限流对象是单个设备,则KEY需要包含设备标识
+            key = key.concat(KEY_SEPARATOR).concat(device);
+        }
+        RRateLimiter limiter = redissonClient.getRateLimiter(key);
+        limiter.trySetRate(rule.getScope() == RateLimitScope.INSTANCE ? RateType.PER_CLIENT : RateType.OVERALL,
+                rule.getCount(), rule.getPeriod().toMillis(), RateIntervalUnit.MILLISECONDS);
+        //限流单元默认24小时后自动清理
+        limiter.expireAsync(24, TimeUnit.HOURS);
+        return limiter.tryAcquire();
+    }
+}

+ 39 - 0
data-redis/src/main/java/com/qmth/boot/redis/uid/RedisMachineService.java

@@ -0,0 +1,39 @@
+package com.qmth.boot.redis.uid;
+
+import com.qmth.boot.core.uid.UidGenerator;
+import com.qmth.boot.core.uid.service.UidMachineService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.redisson.api.RedissonClient;
+
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+
+public class RedisMachineService implements UidMachineService, RedisConstant {
+
+    @Resource
+    private RedissonClient redissonClient;
+
+    private int machineId = -1;
+
+    @Override
+    public int getMachineId() {
+        if (machineId > -1) {
+            return machineId;
+        }
+        for (int i = 0; i <= UidGenerator.MAX_MACHINE_ID; i++) {
+            if (redissonClient.getLock(MACHINE_ID_PREFIX.concat(KEY_SEPARATOR).concat(String.valueOf(i))).tryLock()) {
+                machineId = i;
+                return i;
+            }
+        }
+        throw new RuntimeException("Get machineId from redis faile");
+    }
+
+    @PreDestroy
+    public void destroy() {
+        if (machineId > -1) {
+            redissonClient.getLock(MACHINE_ID_PREFIX.concat(KEY_SEPARATOR).concat(String.valueOf(machineId)))
+                    .unlockAsync();
+        }
+    }
+}

+ 15 - 0
data-redis/src/main/java/com/qmth/boot/redis/uid/RedisUidAutoConfiguration.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.redis.uid;
+
+import com.qmth.boot.core.uid.service.UidMachineService;
+import com.qmth.boot.redis.constant.RedisConstant;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RedisUidAutoConfiguration implements RedisConstant {
+
+    @Bean
+    public UidMachineService uidMachineService() {
+        return new RedisMachineService();
+    }
+}

+ 254 - 0
pom.xml

@@ -0,0 +1,254 @@
+<?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>
+
+    <groupId>com.qmth.boot</groupId>
+    <artifactId>qmth-boot</artifactId>
+    <packaging>pom</packaging>
+    <version>1.0.0</version>
+
+    <modules>
+        <module>starter-api</module>
+        <module>tools-signature</module>
+        <module>tools-common</module>
+        <module>core-models</module>
+        <module>core-concurrent</module>
+        <module>core-uid</module>
+        <module>core-metrics</module>
+        <module>core-rate-limit</module>
+        <module>core-security</module>
+        <module>core-logging</module>
+        <module>core-cache</module>
+        <module>data-redis</module>
+        <module>data-mysql-mp</module>
+    </modules>
+
+    <name>qmth-boot</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <springboot.version>2.3.7.RELEASE</springboot.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- inner modules -->
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>starter-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>tools-signature</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>tools-common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-security</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-uid</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-metrics</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-rate-limit</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-logging</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-models</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-concurrent</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-cache</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>data-redis</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>data-mysql-mp</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <!-- spring modules -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-autoconfigure</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-logging</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-validation</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-cache</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-jdbc</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-data-redis</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-aop</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-test</artifactId>
+                <version>${springboot.version}</version>
+            </dependency>
+            <!-- thirdpart modules -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>3.11</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>2.11.3</version>
+            </dependency>
+            <dependency>
+                <groupId>com.fasterxml.jackson.datatype</groupId>
+                <artifactId>jackson-datatype-jsr310</artifactId>
+                <version>2.11.3</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.ben-manes.caffeine</groupId>
+                <artifactId>caffeine</artifactId>
+                <version>2.8.8</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson</artifactId>
+                <version>3.15.0</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-data-21 -->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-data-21</artifactId>
+                <version>3.15.0</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>3.4.2</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>8.0.23</version>
+            </dependency>
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>4.12</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+            <plugins>
+                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+                <plugin>
+                    <artifactId>maven-clean-plugin</artifactId>
+                    <version>3.1.0</version>
+                </plugin>
+                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+                <plugin>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.8.0</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>2.22.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-install-plugin</artifactId>
+                    <version>2.5.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+                <plugin>
+                    <artifactId>maven-site-plugin</artifactId>
+                    <version>3.7.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-project-info-reports-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+</project>

+ 99 - 0
starter-api/pom.xml

@@ -0,0 +1,99 @@
+<?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>
+        <groupId>com.qmth.boot</groupId>
+        <artifactId>qmth-boot</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>starter-api</artifactId>
+    <name>starter-api</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-signature</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>tools-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-metrics</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-rate-limit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-models</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
+            <plugins>
+                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
+                <plugin>
+                    <artifactId>maven-clean-plugin</artifactId>
+                    <version>3.1.0</version>
+                </plugin>
+                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
+                <plugin>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.8.0</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>2.22.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-install-plugin</artifactId>
+                    <version>2.5.2</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
+                <plugin>
+                    <artifactId>maven-site-plugin</artifactId>
+                    <version>3.7.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-project-info-reports-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+</project>

+ 72 - 0
starter-api/src/main/java/com/qmth/boot/api/annotation/Aac.java

@@ -0,0 +1,72 @@
+package com.qmth.boot.api.annotation;
+
+import com.qmth.boot.core.enums.Platform;
+import com.qmth.boot.tools.signature.SignatureType;
+
+import java.lang.annotation.*;
+
+/**
+ * API接口自定义参数配置
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Documented
+public @interface Aac {
+
+    /**
+     * 是否严格模式
+     *
+     * @return
+     */
+    BOOL strict() default BOOL.NULL;
+
+    /**
+     * 允许的访问来源
+     *
+     * @return
+     */
+    Platform[] platform() default {};
+
+    /**
+     * IP白名单
+     *
+     * @return
+     */
+    String[] ipAllow() default {};
+
+    /**
+     * IP黑名单
+     *
+     * @return
+     */
+    String[] ipDeny() default {};
+
+    /**
+     * 限流规则
+     *
+     * @return
+     */
+    String[] rateLimit() default {};
+
+    /**
+     * 是否需要鉴权
+     *
+     * @return
+     */
+    BOOL auth() default BOOL.NULL;
+
+    /**
+     * 允许的签名类型
+     *
+     * @return
+     */
+    SignatureType[] signType() default {};
+
+    /**
+     * 是否统计接口状态
+     *
+     * @return
+     */
+    BOOL metrics() default BOOL.NULL;
+
+}

+ 7 - 0
starter-api/src/main/java/com/qmth/boot/api/annotation/BOOL.java

@@ -0,0 +1,7 @@
+package com.qmth.boot.api.annotation;
+
+public enum BOOL {
+
+    TRUE, FALSE, NULL;
+
+}

+ 153 - 0
starter-api/src/main/java/com/qmth/boot/api/config/AacConfig.java

@@ -0,0 +1,153 @@
+package com.qmth.boot.api.config;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.core.enums.Platform;
+import com.qmth.boot.core.rateLimit.config.RateLimitRule;
+import com.qmth.boot.tools.signature.SignatureType;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AacConfig {
+
+    public static final String[] EMPTY_STRING_ARRAY = new String[] {};
+
+    public static final Platform[] EMPTY_PLATFORM_ARRAY = new Platform[] {};
+
+    public static final RateLimitRule[] EMPTY_RATE_LIMIT_CONFIG = new RateLimitRule[] {};
+
+    public static final SignatureType[] EMPTY_SIGNATURE_TYPE = new SignatureType[] {};
+
+    private Boolean strict;
+
+    private Platform[] platform = EMPTY_PLATFORM_ARRAY;
+
+    private String[] ipAllow = EMPTY_STRING_ARRAY;
+
+    private String[] ipDeny = EMPTY_STRING_ARRAY;
+
+    private RateLimitRule[] rateLimit = EMPTY_RATE_LIMIT_CONFIG;
+
+    private Boolean auth;
+
+    private SignatureType[] signType = EMPTY_SIGNATURE_TYPE;
+
+    private Boolean metrics;
+
+    public AacConfig(ApiConfig config) {
+        this.strict = config.isGlobalStrict();
+        this.metrics = true;
+        this.auth = config.isGlobalAuth();
+
+        buildRateLimit(config.getGlobalRateLimit());
+    }
+
+    public AacConfig(Aac annotation) {
+        if (annotation == null) {
+            return;
+        }
+        if (annotation.strict() != BOOL.NULL) {
+            this.strict = (annotation.strict() == BOOL.TRUE);
+        }
+        if (annotation.platform().length > 0) {
+            this.platform = annotation.platform();
+        }
+        if (annotation.auth() != BOOL.NULL) {
+            this.auth = (annotation.auth() == BOOL.TRUE);
+        }
+        if (annotation.metrics() != BOOL.NULL) {
+            this.metrics = (annotation.metrics() == BOOL.TRUE);
+        }
+        if (annotation.ipAllow().length > 0) {
+            this.ipAllow = annotation.ipAllow();
+        }
+        if (annotation.ipDeny().length > 0) {
+            this.ipDeny = annotation.ipDeny();
+        }
+        if (annotation.signType().length > 0) {
+            this.signType = annotation.signType();
+        }
+
+        buildRateLimit(annotation.rateLimit());
+    }
+
+    public void merge(AacConfig config) {
+        if (config == null) {
+            return;
+        }
+        if (config.strict != null) {
+            this.strict = config.strict;
+        }
+        if (ArrayUtils.isNotEmpty(config.platform)) {
+            this.platform = config.platform;
+        }
+        if (config.auth != null) {
+            this.auth = config.auth;
+        }
+        if (config.metrics != null) {
+            this.metrics = config.metrics;
+        }
+        if (ArrayUtils.isNotEmpty(config.ipAllow)) {
+            this.ipAllow = config.ipAllow;
+        }
+        if (ArrayUtils.isNotEmpty(config.ipDeny)) {
+            this.ipDeny = config.ipDeny;
+        }
+        if (ArrayUtils.isNotEmpty(config.signType)) {
+            this.signType = config.signType;
+        }
+        if (ArrayUtils.isNotEmpty(config.rateLimit)) {
+            this.rateLimit = config.rateLimit;
+        }
+    }
+
+    public boolean isStrict() {
+        return strict;
+    }
+
+    public Platform[] getPlatform() {
+        return platform;
+    }
+
+    public String[] getIpAllow() {
+        return ipAllow;
+    }
+
+    public String[] getIpDeny() {
+        return ipDeny;
+    }
+
+    public RateLimitRule[] getRateLimit() {
+        return rateLimit;
+    }
+
+    public boolean isAuth() {
+        return auth;
+    }
+
+    public SignatureType[] getSignType() {
+        return signType;
+    }
+
+    public boolean isMetrics() {
+        return metrics;
+    }
+
+    private void buildRateLimit(String[] expressions) {
+        if (ArrayUtils.isNotEmpty(expressions)) {
+            List<RateLimitRule> list = new ArrayList<>(expressions.length);
+            for (String expression : expressions) {
+                RateLimitRule config = RateLimitRule.parse(expression);
+                if (config != null) {
+                    list.add(config);
+                }
+            }
+            if (!list.isEmpty()) {
+                this.rateLimit = list.toArray(new RateLimitRule[0]);
+            }
+        }
+    }
+
+}

+ 15 - 0
starter-api/src/main/java/com/qmth/boot/api/config/ApiConfig.java

@@ -0,0 +1,15 @@
+package com.qmth.boot.api.config;
+
+public interface ApiConfig {
+
+    String getUriPrefix();
+
+    boolean isErrorMapping();
+
+    String[] getGlobalRateLimit();
+
+    boolean isGlobalAuth();
+
+    boolean isGlobalStrict();
+
+}

+ 56 - 0
starter-api/src/main/java/com/qmth/boot/api/config/ApiInterceptorConfig.java

@@ -0,0 +1,56 @@
+package com.qmth.boot.api.config;
+
+import com.qmth.boot.api.interceptor.ExtendInterceptor;
+import com.qmth.boot.api.interceptor.impl.AuthorizationInterceptor;
+import com.qmth.boot.api.interceptor.impl.MetricsInterceptor;
+import com.qmth.boot.api.interceptor.impl.RateLimitInterceptor;
+import com.qmth.boot.api.interceptor.impl.ValveInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+
+@Configuration
+public class ApiInterceptorConfig implements WebMvcConfigurer {
+
+    @Resource
+    private ApiConfig apiConfig;
+
+    @Resource
+    private ValveInterceptor valveInterceptor;
+
+    @Resource
+    private RateLimitInterceptor rateLimitInterceptor;
+
+    @Resource
+    private MetricsInterceptor metricsInterceptor;
+
+    @Resource
+    private AuthorizationInterceptor authorizationInterceptor;
+
+    private ExtendInterceptor extendInterceptor;
+
+    @Autowired(required = false)
+    public void setExtendInterceptor(ExtendInterceptor extendInterceptor) {
+        this.extendInterceptor = extendInterceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        String prefix = apiConfig.getUriPrefix();
+        if (prefix.endsWith("/")) {
+            prefix = prefix.concat("**");
+        } else {
+            prefix = prefix.concat("/**");
+        }
+        registry.addInterceptor(valveInterceptor).addPathPatterns(prefix);
+        registry.addInterceptor(rateLimitInterceptor).addPathPatterns(prefix);
+        registry.addInterceptor(metricsInterceptor).addPathPatterns(prefix);
+        registry.addInterceptor(authorizationInterceptor).addPathPatterns(prefix);
+        if (extendInterceptor != null) {
+            registry.addInterceptor(extendInterceptor).addPathPatterns(prefix);
+        }
+    }
+}

+ 74 - 0
starter-api/src/main/java/com/qmth/boot/api/config/bean/ApiConfigBean.java

@@ -0,0 +1,74 @@
+package com.qmth.boot.api.config.bean;
+
+import com.qmth.boot.api.config.ApiConfig;
+import com.qmth.boot.api.constant.ApiConstant;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotBlank;
+
+@Component
+@ConfigurationProperties(prefix = ApiConstant.CONFIG_PREFIX)
+@Validated
+public class ApiConfigBean implements ApiConfig, ApiConstant {
+
+    @NotBlank
+    private String uriPrefix = DEFAULT_URI_PREFIX;
+
+    private boolean errorMapping = true;
+
+    private boolean globalStrict = false;
+
+    @Nullable
+    private String[] globalRateLimit = null;
+
+    private boolean globalAuth = false;
+
+    @Override
+    public String getUriPrefix() {
+        return uriPrefix;
+    }
+
+    public void setUriPrefix(String uriPrefix) {
+        this.uriPrefix = uriPrefix;
+    }
+
+    @Override
+    public boolean isErrorMapping() {
+        return errorMapping;
+    }
+
+    public void setErrorMapping(boolean errorMapping) {
+        this.errorMapping = errorMapping;
+    }
+
+    @Override
+    public boolean isGlobalStrict() {
+        return globalStrict;
+    }
+
+    public void setGlobalStrict(boolean globalStrict) {
+        this.globalStrict = globalStrict;
+    }
+
+    @Override
+    public String[] getGlobalRateLimit() {
+        return globalRateLimit;
+    }
+
+    public void setGlobalRateLimit(String[] globalRateLimit) {
+        this.globalRateLimit = globalRateLimit;
+    }
+
+    @Override
+    public boolean isGlobalAuth() {
+        return globalAuth;
+    }
+
+    public void setGlobalAuth(boolean globalAuth) {
+        this.globalAuth = globalAuth;
+    }
+
+}

+ 41 - 0
starter-api/src/main/java/com/qmth/boot/api/constant/ApiConstant.java

@@ -0,0 +1,41 @@
+package com.qmth.boot.api.constant;
+
+import com.qmth.boot.core.constant.CoreConstant;
+
+public interface ApiConstant {
+
+    String DEFAULT_URI_PREFIX = "/api";
+
+    String DEFAULT_ERROR_URI = "/error";
+
+    String CONFIG_PREFIX = CoreConstant.CONFIG_PREFIX + ".api";
+
+    String CONFIG_METRICS_ENDPOINT = "metrics-endpoint";
+
+    String IP_UNKNOWN = "unknown";
+
+    String IP_LOCAL_MACHINE = "0:0:0:0:0:0:0:1";
+
+    String IP_LOCALHOST = "127.0.0.1";
+
+    String HEADER_PLATFORM = "platform";
+
+    String HEADER_DEVICE_ID = "deviceId";
+
+    String HEADER_TIME = "time";
+
+    String HEADER_AUTHORIZATION = "authorization";
+
+    String HEADER_TRACE_ID = "traceId";
+
+    String ATTRIBUTE_AAC_CONFIG = "aacConfig";
+
+    String ATTRIBUTE_ACCESS_ENTITY = "accessEntity";
+
+    String ATTRIBUTE_START_TIME = "startTime";
+
+    String ATTRIBUTE_TRACE_ID = "traceId";
+
+    String ATTRIBUTE_CALLER = "caller";
+
+}

+ 47 - 0
starter-api/src/main/java/com/qmth/boot/api/exception/ApiException.java

@@ -0,0 +1,47 @@
+package com.qmth.boot.api.exception;
+
+import org.springframework.http.HttpStatus;
+
+/**
+ * API层通用的异常描述,包含http状态吗,可转换为标准json返回格式
+ */
+public class ApiException extends RuntimeException {
+
+    private static final long serialVersionUID = -6281803364896392700L;
+
+    private HttpStatus status;
+
+    private int code;
+
+    private String error;
+
+    private String message;
+
+    public ApiException(HttpStatus status, int code, String error, String message) {
+        this.status = status;
+        this.code = code;
+        this.error = error;
+        this.message = message;
+    }
+
+    public HttpStatus getStatus() {
+        return status;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public ExceptionResponseEntity responseEntity() {
+        return new ExceptionResponseEntity(code, error, message);
+    }
+}

+ 30 - 0
starter-api/src/main/java/com/qmth/boot/api/exception/DefaultErrorController.java

@@ -0,0 +1,30 @@
+package com.qmth.boot.api.exception;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.api.constant.ApiConstant;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@ConditionalOnExpression("${" + ApiConstant.CONFIG_PREFIX + ".error-mapping:true}")
+@Aac(strict = BOOL.FALSE, auth = BOOL.FALSE)
+public class DefaultErrorController implements ErrorController, ApiConstant {
+
+    private static ExceptionResponseEntity entity = new ExceptionResponseEntity(HttpStatus.NOT_FOUND.value(),
+            "request url not found", null);
+
+    @Override
+    public String getErrorPath() {
+        return DEFAULT_ERROR_URI;
+    }
+
+    @RequestMapping(DEFAULT_ERROR_URI)
+    public ExceptionResponseEntity error() {
+        return entity;
+    }
+
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов