Browse Source

chore: withdrawal tool

master
wangxiang 2 years ago
commit
c0c8734e14
No known key found for this signature in database
GPG Key ID: 1BA7946AB6B232E4
  1. 60
      .gitignore
  2. 107
      README.md
  3. 276
      kicc-common-bom/pom.xml
  4. 50
      kicc-common-core/pom.xml
  5. 22
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/annotation/EnableKiccJacksonAutoConvert.java
  6. 114
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/api/R.java
  7. 33
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/GatewayConfigProperties.java
  8. 46
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/JacksonAutoConfiguration.java
  9. 23
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/RestTemplateConfiguration.java
  10. 45
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/WebMvcConfiguration.java
  11. 25
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/AppConstants.java
  12. 53
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/CacheConstants.java
  13. 53
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/CommonConstants.java
  14. 18
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/RegexConstants.java
  15. 125
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/SecurityConstants.java
  16. 44
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/ServiceNameConstants.java
  17. 97
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/StringPool.java
  18. 35
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/enums/ExceptionEnum.java
  19. 38
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/enums/LoginTypeEnum.java
  20. 34
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/exception/CheckedException.java
  21. 22
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/exception/ValidateCodeException.java
  22. 48
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/factory/YamlPropertySourceFactory.java
  23. 55
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/jackson/KiccJavaTimeModule.java
  24. 27
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/BaseUtil.java
  25. 97
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/ClassUtil.java
  26. 50
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/DateUtil.java
  27. 113
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/FileUtil.java
  28. 499
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/HTMLFilterUtil.java
  29. 38
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/JasyptUtil.java
  30. 203
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/PinyinUtil.java
  31. 87
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/SpringContextHolderUtil.java
  32. 321
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/TimeUtils.java
  33. 186
      kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/WebUtil.java
  34. 5
      kicc-common-core/src/main/resources/META-INF/spring.factories
  35. 17
      kicc-common-core/src/main/resources/banner.txt
  36. 57
      kicc-common-core/src/main/resources/logback-spring.xml
  37. 67
      kicc-common-data/pom.xml
  38. 24
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/annotation/EnableKiccDataRepository.java
  39. 66
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/config/MybatisConfiguration.java
  40. 60
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/config/RedisTemplateConfiguration.java
  41. 37
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/BaseEntity.java
  42. 185
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/CasUser.java
  43. 79
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/CommonEntity.java
  44. 109
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/KiccUser.java
  45. 66
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/SsoUser.java
  46. 46
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/TreeEntity.java
  47. 52
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/enums/DataTypeEnum.java
  48. 75
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/handler/BaseMetaObjectHandler.java
  49. 77
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/handler/KiccTenantLineHandler.java
  50. 70
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/override/TenantLikeExpression.java
  51. 38
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/plugins/KiccPaginationInnerInterceptor.java
  52. 209
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/plugins/KiccTenantLineInnerInterceptor.java
  53. 24
      kicc-common-data/src/main/java/com/cloud/kicc/common/data/properties/TenantProperties.java
  54. 1
      kicc-common-data/src/main/resources/META-INF/spring.factories
  55. 4
      kicc-common-data/src/main/resources/kicc-tenant.yml
  56. 35
      kicc-common-datasource/pom.xml
  57. 53
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/DynamicDataSourceConfiguration.java
  58. 23
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/annotation/EnableDynamicDataSource.java
  59. 45
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/dynamic/DynamicDataSource.java
  60. 91
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/dynamic/DynamicDataSourceJdbcProvider.java
  61. 48
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/support/DynamicDataSourceConstant.java
  62. 52
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/util/ConnUtil.java
  63. 112
      kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/util/DynamicDataSourceUtil.java
  64. 2
      kicc-common-datasource/src/main/resources/META-INF/spring.factories
  65. 53
      kicc-common-feign/pom.xml
  66. 49
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/KiccFeignAutoConfiguration.java
  67. 72
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/annotation/EnableKiccFeignClients.java
  68. 50
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/FeignErrorDecoder.java
  69. 26
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/KiccFeignClientConfiguration.java
  70. 61
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/KiccFeignClientInterceptor.java
  71. 52
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/SentinelAutoConfiguration.java
  72. 132
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelFeign.java
  73. 59
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelFilterConfiguration.java
  74. 178
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelInvocationHandler.java
  75. 122
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/handle/GlobalBizExceptionHandler.java
  76. 34
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/handle/KiccUrlBlockHandler.java
  77. 34
      kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/parser/KiccHeaderRequestOriginParser.java
  78. 223
      kicc-common-feign/src/main/java/org/springframework/cloud/openfeign/KiccFeignClientsRegistrar.java
  79. 4
      kicc-common-feign/src/main/resources/META-INF/spring.factories
  80. 30
      kicc-common-job/pom.xml
  81. 72
      kicc-common-job/src/main/java/com/cloud/kicc/common/job/XxlJobAutoConfiguration.java
  82. 23
      kicc-common-job/src/main/java/com/cloud/kicc/common/job/annotation/EnableKiccXxlJob.java
  83. 21
      kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlAdminProperties.java
  84. 52
      kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlExecutorProperties.java
  85. 25
      kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlJobProperties.java
  86. 33
      kicc-common-log/pom.xml
  87. 36
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/LogAutoConfiguration.java
  88. 24
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/annotation/SysLog.java
  89. 52
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/aspect/SysLogAspect.java
  90. 20
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/event/SysLogEvent.java
  91. 34
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/event/SysLogListener.java
  92. 33
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/init/ApplicationLoggerInitializer.java
  93. 38
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/menus/LogTypeEnum.java
  94. 95
      kicc-common-log/src/main/java/com/cloud/kicc/common/log/util/SysLogUtils.java
  95. 4
      kicc-common-log/src/main/resources/META-INF/spring.factories
  96. 27
      kicc-common-mock/pom.xml
  97. 73
      kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/WithMockSecurityContextFactory.java
  98. 33
      kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/annotation/WithMockOAuth2User.java
  99. 31
      kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/kit/OAuthMockKit.java
  100. 28
      kicc-common-rocketmq/pom.xml
  101. Some files were not shown because too many files have changed in this diff Show More

60
.gitignore vendored

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
### gradle ###
.gradle
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.settings/
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
rebel.xml
### NetBeans ###
nbproject/private/
nbbuild/
nbdist/
.nb-gradle/
### maven ###
target/
*.war
*.ear
*.zip
*.tar
*.tar.gz
*.versionsBackup
### vscode ###
.vscode
### logs ###
/logs/
*.log
### temp ignore ###
*.cache
*.diff
*.patch
*.tmp
*.java~
*.properties~
*.xml~
### system ignore ###
.DS_Store
Thumbs.db
Servers
.metadata
# ui ignore
kicc-ui/

107
README.md

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
<h1 align="center">
<b>
<a href="https://godolphinx.org"><img src="https://godolphinx.org/images/dolphin-platform-logo.svg" /></a><br>
</b>
</h1>
<p align="center"> 一个快速开发软件的平台 </p>
<p align="center">
<a href="https://godolphinx.org/"><b>Website</b></a>
<a href="https://godolphinx.org/microservice/description.html"><b>Documentation</b></a>
</p>
<div align="center">
<a href="https://godolphinx.org">
<img src="https://img.shields.io/npm/l/vue.svg?sanitize=true">
</a>
<a href="https://gitpod.io/#https://github.com/wangxiang4/dolphin">
<img src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square">
</a>
<a href="https://discord.gg/DREuQWrRYQ">
<img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?sanitize=true"/>
</a>
</div>
## 🐬 介绍
海豚生态计划-打造一个web端,安卓端,ios端的一个海豚开发平台生态圈,不接收任何商业化,并且完全免费开源(包含高级功能)。
## 💪 愿景
让人人都可以快速高效的开发软件
## ✨ 特性
- 主体框架:采用最新的`Spring Cloud 2021.0.1`, `Spring Boot 2.6.4`, `Spring Cloud Alibaba 2021.1`版本进行系统设计。
- 统一注册:支持`Nacos`作为注册中心,实现多配置、分群组、分命名空间、多业务模块的注册和发现功能。
- 统一认证:统一`Oauth2`认证协议,并支持自定义grant_type实现手机号码登录,第三方登录集成JustAuth实现微信、支付宝等多种登录模式。
- 业务监控:利用`Spring Boot Admin`来监控各个独立微服务运行状态。
- 内部调用:集成了`Feign`与自定义内部注解,支持内部调用。
- 业务熔断:采用`Sentinel`实现业务熔断处理,避免服务之间出现雪崩。
- 在线文档:通过接入`Knife4j`,实现在线API文档的查看与调试。
- 业务分离:采用前后端分离的框架设计,提高开发效率、降低维护成本、增强系统稳定性和灵活性。
- 多租户功能:集成`Mybatis Plus`,自定义sql执行拦截器,实现SAAS多租户。
- 消息中间件:采用`RocketMQ`,实现服务之间消息转发。
- 分布式事物方案:采用`seata`,实现多个微服务分布式事物一致。
- 分布式定时器:采用`XXL-JOB`,实现多个微服务分布式任务调度。
- 微服务网关:采用`Spring Gateway`实现流量配置动态化、API管理和路由、负载均衡和容错、解决跨域问题、鉴权,限流,熔断,防火墙等等。
## <img width="28" style="vertical-align:middle" src="https://godolphinx.org/images/hacktoberfest-logo.svg"> 黑客节
加入[Github HackToberFest](https://hacktoberfest.com/) 开始为此项目做出贡献.
## 🔨 开发目录
```
├─dolphin -- 父项目,各模块分离,方便微服务扩展
│ ├─doc -- 文档数据-包含项目的一些数据资料
│ ├─docker-cloud -- docker-compose容器配置
│ ├─dolphin-auth -- 认证授权中心,基于 spring security oAuth2
│ ├─dolphin-common -- 公共通用模块,主模块
│ │ ├─dolphin-common-bom -- 全局jar BOM标准定义
│ │ ├─dolphin-common-core -- 公共工具类核心包
│ │ ├─dolphin-common-data -- 数据服务核心包
│ │ ├─dolphin-common-datasource -- 动态切换数据源组件
│ │ ├─dolphin-common-feign -- feign-sentinel服务降级熔断、限流组件
│ │ ├─dolphin-common-job -- 定时任务,基于xxl-job
│ │ ├─dolphin-common-log -- 日志服务
│ │ ├─dolphin-common-mock -- 单元模拟测试工具类
│ │ ├─dolphin-common-rocketmq -- 阿里 rocketmq 消息中间件
│ │ ├─dolphin-common-seata -- 阿里巴巴-seata分布式事务解决方案
│ │ ├─dolphin-common-security -- 安全工具类
│ │ ├─dolphin-common-swagger -- 接口文档
│ │─dolphin-common-demo -- 组件使用案列
│ │ ├─dolphin-common-demo-mq -- 消息中心间演示
│ │ ├─dolphin-common-demo-seata -- 分布式事务解决方案演示
│ │─dolphin-gateway -- 服务网关,基于 spring cloud gateway
│ │─ddolphin-platform -- 微服务平台
│ │ ├─dolphin-platform-api -- 微服务api调用(添加调用的微服务api依赖库,实现调用)
│ │ │ ├─dolphin-common-api -- 通用业务模块公共api模块
│ │ │ ├─dolphin-monitor-api -- 运维监控api模块
│ │ │ ├─dolphin-system-api -- 系统api模块
│ │ │ ├─dolphin-template-api -- 新建api模块模板,只提供基础依赖
│ │ ├─dolphin-platform-biz -- 微服务业务模块
│ │ │ ├─dolphin-common-biz -- 通用业务模块
│ │ │ ├─dolphin-monitor-biz -- 运维监控业务模块
│ │ │ ├─dolphin-system-biz -- 系统业务模块
│ │ │ ├─dolphin-template-biz -- 新建业务模块模板,只提供基础依赖
│ │─dolphin-register -- 注册配置中心
│ │─dolphin-visual 可视化图形界面
│ │ ├─dolphin-rocketmq-dashboard -- RocketMQ可视化监控平台
│ │ ├─dolphin-sentinel-dashboard -- 哨兵流量控制可视化平台
│ │ ├─dolphin-spring-dashboard -- SpringBoot可视化监控平台
│ │ ├─dolphin-xxl-job-admin -- XXL-JOB可视化监控平台
```
## 🤔 一起讨论
加入我们的 [Discord](https://discord.gg/DREuQWrRYQ) 开始与大家交流。
## 🤗 我想成为开发团队的一员!
欢迎😀!我们正在寻找有才华的开发者加入我们,让海豚开发平台变得更好!如果您想加入开发团队,请联系我们,非常欢迎您加入我们!💖
## 在线一键设置
您可以使用 Gitpod,一个在线 IDE(开源免费)来在线贡献或运行示例。
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/wangxiang4/dolphin)
## 📄 执照
[Dolphin Development Platform 是获得MIT许可](https://github.com/wangxiang4/dolphin/blob/master/LICENSE) 的开源软件 。

276
kicc-common-bom/pom.xml

@ -0,0 +1,276 @@ @@ -0,0 +1,276 @@
<?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.cloud</groupId>
<artifactId>kicc-common-bom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>kicc-common-bom</name>
<description>全局jar BOM标准定义(可以设置工程内部的jar的标准,也可以设置第三方依赖jar的标准)</description>
<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>
<kicc.common.version>${project.version}</kicc.common.version>
<spring-boot.version>2.6.3</spring-boot.version>
<security.oauth.version>2.1.8.RELEASE</security.oauth.version>
<log4j2.version>2.17.1</log4j2.version>
<fastjson.version>1.2.78</fastjson.version>
<swagger.core.version>1.5.24</swagger.core.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<sms.version>1.2.0</sms.version>
<git.commit.plugin>4.9.9</git.commit.plugin>
<spring.checkstyle.plugin>0.0.29</spring.checkstyle.plugin>
<captcha.version>2.2.1</captcha.version>
<oss.version>1.0.3</oss.version>
<excel.version>1.1.1</excel.version>
<velocity.version>2.3</velocity.version>
<flowable.version>6.4.2</flowable.version>
<liquibase.version>4.22.0</liquibase.version>
<ureport2.version>2.2.9</ureport2.version>
</properties>
<!-- 定义全局jar版本,模块使用需要再次引入但不用写版本号-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-core</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-data</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-datasource</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-feign</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-rocketmq</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-seata</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-job</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-log</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-mock</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-security</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-swagger</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-system-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-monitor-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-report-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-workflow-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-template-api</artifactId>
<version>${kicc.common.version}</version>
</dependency>
<!--稳定版本,替代spring security2.6.3 bom内置-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>${security.oauth.version}</version>
</dependency>
<!--swagger 最新依赖内置版本-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>${swagger.core.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.core.version}</version>
</dependency>
<!--fastjson 版本-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--阿里云短信服务-->
<dependency>
<groupId>cn.javaer.aliyun</groupId>
<artifactId>aliyun-spring-boot-starter-sms</artifactId>
<version>${sms.version}</version>
</dependency>
<!--orm mybatis-plus相关-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis-generator-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--velocity模板引擎生成简单crud代码-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<!--排除tomcat依赖,使用undertow容器-->
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--指定 log4j 版本-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--oss文件管理,采用S3协议封装,兼容阿里云OSS,腾讯云COS,七牛云,京东云,minio 等不需要引入对应厂商的SDK就可以使用厂商的OSS服务
代码开源地址: https://github.com/pig-mesh/oss-spring-boot-starter -->
<dependency>
<groupId>com.pig4cloud.plugin</groupId>
<artifactId>oss-spring-boot-starter</artifactId>
<version>${oss.version}</version>
</dependency>
<!-- 验证码处理支持很多种验证码风格基于google的aviator计算引擎实现,性能比较高效
代码开源地址: https://github.com/pig-mesh/easy-captcha -->
<dependency>
<groupId>com.pig4cloud.plugin</groupId>
<artifactId>captcha-spring-boot-starter</artifactId>
<version>${captcha.version}</version>
</dependency>
<!-- excel 导入导出处理,基于alibaba的EasyExcel实现,读取大文件不怎么吃内存,性能比较高效
代码开源地址: https://github.com/pig-mesh/excel-spring-boot-starter -->
<dependency>
<groupId>com.pig4cloud.excel</groupId>
<artifactId>excel-spring-boot-starter</artifactId>
<version>${excel.version}</version>
</dependency>
<!-- flowable-SpringBoot依赖 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process-rest</artifactId>
<version>${flowable.version}</version>
</dependency>
<!--
引入建模设计器api请求,具体我也查看了内部是依赖了一个flowable-ui-modeler-logic做核心的逻辑操作
具体实现: https://github.com/flowable/flowable-engine/tree/main/modules/flowable-ui/flowable-ui-modeler-rest
因为Flowable6.5以后开始收费,所以后期打算用camunda,对应的我也去查看了一遍camunda的源代码,发现对应也有此功能,camunda具体实现
是采用了一个engine-rest工程做的api请求,但是区别是camunda是把所有的api请求放在一起了,并没有像flowable分模块功能实现的api请求
比如我只想要导入建模设计器api请求,如果使用camunda就不行了只能把所有的api请求全部导入,camunda内部建模这块的实现也是引用了一个
camunda-bpmn-model工程做核心的逻辑操作,具体引用是在bom.xml引入
具体实现: https://github.com/camunda/camunda-bpm-platform/blob/master/bom/pom.xml
具体实现: https://github.com/camunda/camunda-bpm-platform/blob/master/engine-rest
所以此处可以使用rest工程,因为camunda也有对应的rest工程可以替换
引入此包原因:查看内部源码发现官方写的比我们自己写出来的模型api接口要更加规范,包括预测未来会发生什么样的错误,比如内部保存采用
了版本校验(如果此时你在修改模型时别人提交了你的模型版本,如果没有版本校验就会出问题),以及覆盖了绝大多数处理模型的应用场景,只需要调用
这个包的接口就行了,不需要在自己去写一大把模型操作逻辑,提高开发效率
-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-rest</artifactId>
<version>${flowable.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-log4j2</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 数据库重构工具:构建flowable-ui-modeler-rest数据库 -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase.version}</version>
</dependency>
<dependency>
<groupId>com.bstek.ureport</groupId>
<artifactId>ureport2-console</artifactId>
<version>${ureport2.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!--maven打包插件(打jar包会在classes生成git.properties文件包含了目前git相关信息方便查看git环境错误)-->
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>${git.commit.plugin}</version>
</plugin>
<!--代码格式插件,默认使用spring 规则,跟前端EsLine一样保证代码规范-->
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring.checkstyle.plugin}</version>
</plugin>
</plugins>
</build>
</project>

50
kicc-common-core/pom.xml

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-core</artifactId>
<packaging>jar</packaging>
<description>kicc 公共工具类核心包</description>
<!--考虑这个作为一个单模块使用,后续引入依赖需要注意低耦合-->
<dependencies>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--server-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--hibernate-validator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--json模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>provided</scope>
</dependency>
<!--swagger 依赖支持注解-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies>
</project>

22
kicc-common-core/src/main/java/com/cloud/kicc/common/core/annotation/EnableKiccJacksonAutoConvert.java

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
package com.cloud.kicc.common.core.annotation;
import com.cloud.kicc.common.core.config.JacksonAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*<p>
* 激活 Jackson 自动转换配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ JacksonAutoConfiguration.class })
public @interface EnableKiccJacksonAutoConvert {
}

114
kicc-common-core/src/main/java/com/cloud/kicc/common/core/api/R.java

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
package com.cloud.kicc.common.core.api;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*<p>
* 响应信息主体
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功标记
*/
public static Integer SUCCESS = 200;
/**
* 失败标记
*/
public static Integer FAIL = 500;
/**
* 未认证
*/
public static Integer UNAUTH = 401;
@Getter
@Setter
@ApiModelProperty("状态编码")
private int code;
@Getter
@Setter
@ApiModelProperty("提示消息")
private String msg;
@Getter
@Setter
@ApiModelProperty("结果集数量统计")
private long total;
@Getter
@Setter
@ApiModelProperty("结果集")
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, "成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, "成功");
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> ok(T data, long total) {
return restData(data, SUCCESS, null, total);
}
public static <T> R<T> error() {
return restResult(null, FAIL, "失败");
}
public static <T> R<T> error(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> error(T data) {
return restResult(data, FAIL, null);
}
public static <T> R<T> error(T data, String msg) {
return restResult(data, FAIL, msg);
}
public static <T> R<T> unAuth(String msg) {
return restResult(null, UNAUTH, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
private static <T> R<T> restData(T data, int code, String msg, long total) {
R<T> apiData = new R<>();
apiData.setCode(code);
apiData.setMsg(msg);
apiData.setTotal(total);
apiData.setData(data);
return apiData;
}
}

33
kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/GatewayConfigProperties.java

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
package com.cloud.kicc.common.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.List;
/**
*<p>
* 网关配置文件
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Data
@RefreshScope
@ConfigurationProperties("gateway")
public class GatewayConfigProperties {
/**
* 网关解密登录前端密码
*/
private String encodeKey;
/**
* 网关忽略不需要校验验证码是否合法的客户端
*/
private List<String> ignoreClients;
}

46
kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/JacksonAutoConfiguration.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
package com.cloud.kicc.common.core.config;
import cn.hutool.core.date.DatePattern;
import com.cloud.kicc.common.core.jackson.KiccJavaTimeModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
/**
*<p>
* 配置全局JacksonConfig,影响mvc层的对象传输日期格式
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class)
public class JacksonAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
// 针对于Date类型,文本格式化,已经实现前端返回时间戳
builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
// 解决返回给前端的Long类型数据失去精度,将Long转换为String
builder.serializerByType(Long.class, ToStringSerializer.instance);
// 针对于JDK新时间类,序列化时带有T的问题,自定义格式化字符串
builder.modules(new KiccJavaTimeModule());
};
}
}

23
kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/RestTemplateConfiguration.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package com.cloud.kicc.common.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
*<p>
* Rest 配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Configuration(proxyBeanMethods = false)
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

45
kicc-common-core/src/main/java/com/cloud/kicc/common/core/config/WebMvcConfiguration.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
package com.cloud.kicc.common.core.config;
import cn.hutool.core.date.DatePattern;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.format.DateTimeFormatter;
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
/**
*<p>
* 全局DateTimeFormat
* 针对GET请求传入参数转换
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = SERVLET)
public class WebMvcConfiguration implements WebMvcConfigurer {
/**
* 增加GET请求参数中时间类型转换 {@link com.cloud.kicc.common.core.jackson.KiccJavaTimeModule}
* <ul>
* <li>HH:mm:ss -> LocalTime</li>
* <li>yyyy-MM-dd -> LocalDate</li>
* <li>yyyy-MM-dd HH:mm:ss -> LocalDateTime</li>
* </ul>
* @param registry
*/
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN));
registrar.setDateFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN));
registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
registrar.registerFormatters(registry);
}
}

25
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/AppConstants.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 应用前缀
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/24
*/
public interface AppConstants {
String APP_SYSTEM = "/system";
String APP_MONITOR = "/monitor";
String APP_COMMON = "/common";
String APP_WORKFLOW = "/workflow";
String APP_REPORT = "/report";
String APP_BIGSCREEN = "/bigscreen";
}

53
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/CacheConstants.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 缓存的key 常量
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public interface CacheConstants {
/**
* oauth 缓存前缀
*/
String OAUTH_ACCESS = ServiceNameConstants.AUTH_SERVICE + ":access:";
/**
* oauth 缓存令牌前缀
*/
String OAUTH_TOKEN = ServiceNameConstants.AUTH_SERVICE + ":token:";
/**
* 验证码前缀
*/
String VERIFICATION_CODE = ServiceNameConstants.SYSTEM_SERVICE + ":verification_code:";
/**
* oauth 客户端信息缓存
*/
String OAUTH_CLIENT_DETAILS = ServiceNameConstants.AUTH_SERVICE + ":client_details";
/**
* 菜单信息缓存
*/
String MENU_DETAILS = ServiceNameConstants.AUTH_SERVICE + ":menu_details";
/**
* 用户信息缓存
*/
String USER_DETAILS = ServiceNameConstants.AUTH_SERVICE + ":user_details";
/**
* 字典信息缓存
*/
String DICT_DETAILS = ServiceNameConstants.AUTH_SERVICE + ":dict_details";
/**
* 全局配置缓存
*/
String CONFIG_PARAM = ServiceNameConstants.AUTH_SERVICE + ":config_param";
}

53
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/CommonConstants.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 通用常量
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public interface CommonConstants {
/**
* 正常
*/
String STATUS_NORMAL = "0";
/**
* 锁定
*/
String STATUS_LOCK = "9";
/**
* 编码
*/
String UTF8 = "UTF-8";
/**
* JSON 资源
*/
String CONTENT_TYPE = "application/json; charset=utf-8";
/**
* 前端工程名
*/
String FRONT_END_PROJECT = "kicc-ui";
/**
* 后端工程名
*/
String BACK_END_PROJECT = "kicc";
/**
* 当前页
*/
String CURRENT = "current";
/**
* size
*/
String SIZE = "size";
}

18
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/RegexConstants.java

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 正则表达式常量
*</p>
*
* @Author: wangxiang4
* @Date: 2022/4/18
*/
public interface RegexConstants {
/**
* 匹配网址正则表达式
*/
String MATCHER_URL = "(((^https?:(?:\\/\\/)?)(?:[-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9.-]+(?::\\d+)?|(?:www.|[-;:&=\\+\\$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[\\+~%\\/.\\w-_]*)?\\??(?:[-\\+=&;%@.\\w_{\\s\\S}]*)#?(?:[\\w]*))?)$";
}

125
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/SecurityConstants.java

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 安全常量
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public interface SecurityConstants {
/**
* 角色前缀
*/
String ROLE = "ROLE_";
/**
* 项目的license
*/
String PROJECT_LICENSE = "长沙康来生物有限公司";
/**
* 内部接口调用密钥
*/
String FROM_IN = "kG8qA6qG1aP5aR3g";
/**
* 内部接口调用Key标志
*/
String FROM = "from";
/**
* 请求header
*/
String HEADER_FROM_IN = FROM + "=" + FROM_IN;
/**
* 默认登录URL
*/
String OAUTH_TOKEN_URL = "/oauth/token";
/**
* grant_type
*/
String REFRESH_TOKEN = "refresh_token";
/**
* 手机号登录
*/
String APP = "app";
/**
* {bcrypt} 加密的特征码
*/
String BCRYPT = "{bcrypt}";
/**
* sys_oauth_client_details 表的字段不包括client_idclient_secret
*/
String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
/**
* JdbcClientDetailsService 查询语句
*/
String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
/**
* 默认的查询语句
*/
String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
/**
* 按条件client_id 查询
*/
String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
/***
* 资源服务器默认bean名称
*/
String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
/**
* 用户信息
*/
String DETAILS_USER = "user_info";
/**
* 协议字段
*/
String DETAILS_LICENSE = "license";
/**
* 验证码有效期,默认 60秒
*/
long CODE_TIME = 60;
/**
* 手机验证码长度
*/
String PHONE_CODE_SIZE = "6";
/**
* 客户端模式
*/
String CLIENT_CREDENTIALS = "client_credentials";
/**
* 客户端ID
*/
String CLIENT_ID = "clientId";
/**
* 模拟测试账户
*/
String MOCK_USERNAME = "admin";
/**
* 模拟测试密码
*/
String MOCK_PASSWORD = "kanglai123";
}

44
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/ServiceNameConstants.java

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
package com.cloud.kicc.common.core.constant;
/**
*<p>
* 服务名称
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public interface ServiceNameConstants {
/**
* 认证服务的SERVICE_ID
*/
String AUTH_SERVICE = "kicc-auth";
/**
* SYSTEM模块
*/
String SYSTEM_SERVICE = "kicc-system-biz";
/**
* MONITOR模块
*/
String MONITOR_SERVICE = "kicc-monitor-biz";
/**
* COMMON模块
*/
String COMMON_SERVICE = "kicc-common-biz";
/**
* seata分布式事务演示-订单模块
*/
String SEATA_ORDER_SERVICE = "kicc-seata-order";
/**
* seata分布式事务演示-积分模块
*/
String SEATA_POINT_SERVICE = "kicc-seata-point";
}

97
kicc-common-core/src/main/java/com/cloud/kicc/common/core/constant/StringPool.java

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
package com.cloud.kicc.common.core.constant;
/**
* Copy to kicc.common.util
* <p>
* Pool of <code>String</code> constants to prevent repeating of
* hard-coded <code>String</code> literals in the code.
* Due to fact that these are <code>public static final</code>
* they will be inlined by java compiler and
* reference to this class will be dropped.
* There is <b>no</b> performance gain of using this pool.
* Read: https://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.5
* <ul>
* <li>Literal strings within the same class in the same package represent references to the same <code>String</code> object.</li>
* <li>Literal strings within different classes in the same package represent references to the same <code>String</code> object.</li>
* <li>Literal strings within different classes in different packages likewise represent references to the same <code>String</code> object.</li>
* <li>Strings computed by constant expressions are computed at compile time and then treated as if they were literals.</li>
* <li>Strings computed by concatenation at run time are newly created and therefore distinct.</li>
* </ul>
* @author wangxiang4
*/
public interface StringPool {
String AMPERSAND = "&";
String AND = "and";
String AT = "@";
String ASTERISK = "*";
String STAR = ASTERISK;
String BACK_SLASH = "\\";
String COLON = ":";
String COMMA = ",";
String DASH = "-";
String DOLLAR = "$";
String DOT = ".";
String DOTDOT = "..";
String DOT_CLASS = ".class";
String DOT_JAVA = ".java";
String DOT_XML = ".xml";
String EMPTY = "";
String EQUALS = "=";
String FALSE = "false";
String SLASH = "/";
String HASH = "#";
String HAT = "^";
String LEFT_BRACE = "{";
String LEFT_BRACKET = "(";
String LEFT_CHEV = "<";
String DOT_NEWLINE = ",\n";
String NEWLINE = "\n";
String N = "n";
String NO = "no";
String NULL = "null";
String OFF = "off";
String ON = "on";
String PERCENT = "%";
String PIPE = "|";
String PLUS = "+";
String QUESTION_MARK = "?";
String EXCLAMATION_MARK = "!";
String QUOTE = "\"";
String RETURN = "\r";
String TAB = "\t";
String RIGHT_BRACE = "}";
String RIGHT_BRACKET = ")";
String RIGHT_CHEV = ">";
String SEMICOLON = ";";
String SINGLE_QUOTE = "'";
String BACKTICK = "`";
String SPACE = " ";
String TILDA = "~";
String LEFT_SQ_BRACKET = "[";
String RIGHT_SQ_BRACKET = "]";
String TRUE = "true";
String UNDERSCORE = "_";
String UTF_8 = "UTF-8";
String US_ASCII = "US-ASCII";
String ISO_8859_1 = "ISO-8859-1";
String Y = "y";
String YES = "yes";
String ONE = "1";
String ZERO = "0";
String DOLLAR_LEFT_BRACE = "${";
String HASH_LEFT_BRACE = "#{";
String CRLF = "\r\n";
String HTML_NBSP = "&nbsp;";
String HTML_AMP = "&amp";
String HTML_QUOTE = "&quot;";
String HTML_LT = "&lt;";
String HTML_GT = "&gt;";
// ---------------------------------------------------------------- array
String[] EMPTY_ARRAY = new String[0];
byte[] BYTES_NEW_LINE = StringPool.NEWLINE.getBytes();
}

35
kicc-common-core/src/main/java/com/cloud/kicc/common/core/enums/ExceptionEnum.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package com.cloud.kicc.common.core.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
*<p>
* API错误页面响应状态枚举
*</p>
*
* @Author: wangxiang4
* @Since: 2023/8/16
*/
@Getter
@RequiredArgsConstructor
public enum ExceptionEnum {
UNAUTHORIZED_ACCESS(401, "禁止访问"),
PAGE_NOT_ACCESS(403, "页面无法访问"),
PAGE_NOT_FOUND(404, "网页未找到"),
ERROR(500, "错误"),
NET_WORK_ERROR(10000, "前端Js错误"),
PAGE_NOT_DATA(10100, "无数据页面");
/**
* 状态
*/
private final int value;
/**
* 描述
*/
private final String description;
}

38
kicc-common-core/src/main/java/com/cloud/kicc/common/core/enums/LoginTypeEnum.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package com.cloud.kicc.common.core.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
*<p>
* 社交登录类型
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Getter
@RequiredArgsConstructor
public enum LoginTypeEnum {
/**
* 账号密码登录
*/
PWD("PWD", "账号密码登录"),
/**
* 验证码登录
*/
SMS("SMS", "验证码登录");
/**
* 类型
*/
private final String type;
/**
* 描述
*/
private final String description;
}

34
kicc-common-core/src/main/java/com/cloud/kicc/common/core/exception/CheckedException.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
package com.cloud.kicc.common.core.exception;
import lombok.NoArgsConstructor;
/**
*<p>
* 检查异常
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@NoArgsConstructor
public class CheckedException extends RuntimeException {
private static final long serialVersionUID = 1L;
public CheckedException(String message) {
super(message);
}
public CheckedException(Throwable cause) {
super(cause);
}
public CheckedException(String message, Throwable cause) {
super(message, cause);
}
public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

22
kicc-common-core/src/main/java/com/cloud/kicc/common/core/exception/ValidateCodeException.java

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
package com.cloud.kicc.common.core.exception;
/**
*<p>
* 验证码异常
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public class ValidateCodeException extends RuntimeException {
private static final long serialVersionUID = -7285211528095468156L;
public ValidateCodeException() {
}
public ValidateCodeException(String msg) {
super(msg);
}
}

48
kicc-common-core/src/main/java/com/cloud/kicc/common/core/factory/YamlPropertySourceFactory.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package com.cloud.kicc.common.core.factory;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
/**
*<p>
* 加载yml格式的自定义配置文件
* @link https://blog.csdn.net/zxl8899/article/details/106382719/
*</p>
*
* @Author: wangxiang4
* @Date: 2022/3/7
*/
@AllArgsConstructor
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
assert sourceName != null;
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException) {
throw (FileNotFoundException) e.getCause();
}
throw e;
}
}
}

55
kicc-common-core/src/main/java/com/cloud/kicc/common/core/jackson/KiccJavaTimeModule.java

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
package com.cloud.kicc.common.core.jackson;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
*<p>
* java 8 时间默认序列化
*</p>
*
* @Author: wangxiang4
* @Date: 2022/3/10
*/
public class KiccJavaTimeModule extends SimpleModule {
public KiccJavaTimeModule() {
super(PackageVersion.VERSION);
// ======================= 时间序列化规则 ===============================
// LocalDateTime序列化时间戳
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
// LocalDate序列化时间戳
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
// LocalTime序列化时间戳
this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
// Instant 类型序列化
this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
// ======================= 时间反序列化规则 ==============================
// yyyy-MM-dd HH:mm:ss
this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
// yyyy-MM-dd
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
// HH:mm:ss
this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
// Instant 反序列化
this.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
}
}

27
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/BaseUtil.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
package com.cloud.kicc.common.core.util;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import lombok.experimental.UtilityClass;
/**
*<p>
* 基础工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/3/21
*/
@UtilityClass
public class BaseUtil {
/**
* 雪花算法生成全局ID
* @Param
* @return
*/
public Long snowflakeId () {
SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
return snowflakeGenerator.next();
}
}

97
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/ClassUtil.java

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
package com.cloud.kicc.common.core.util;
import lombok.experimental.UtilityClass;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.web.method.HandlerMethod;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
*<p>
* 扩展类工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@UtilityClass
public class ClassUtil extends org.springframework.util.ClassUtils {
private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();
/**
* 获取方法参数信息
* @param constructor 构造器
* @param parameterIndex 参数序号
* @return {MethodParameter}
*/
public MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
return methodParameter;
}
/**
* 获取方法参数信息
* @param method 方法
* @param parameterIndex 参数序号
* @return {MethodParameter}
*/
public MethodParameter getMethodParameter(Method method, int parameterIndex) {
MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
return methodParameter;
}
/**
* 获取Annotation
* @param method Method
* @param annotationType 注解类
* @param <A> 泛型标记
* @return {Annotation}
*/
public <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
Class<?> targetClass = method.getDeclaringClass();
// The method may be on an interface, but we need attributes from the target
// class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtil.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original
// method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 先找方法,再找方法上的类
A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
;
if (null != annotation) {
return annotation;
}
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
}
/**
* 获取Annotation
* @param handlerMethod HandlerMethod
* @param annotationType 注解类
* @param <A> 泛型标记
* @return {Annotation}
*/
public <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
// 先找方法,再找方法上的类
A annotation = handlerMethod.getMethodAnnotation(annotationType);
if (null != annotation) {
return annotation;
}
// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
Class<?> beanType = handlerMethod.getBeanType();
return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
}
}

50
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/DateUtil.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
package com.cloud.kicc.common.core.util;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
*<p>
* 时间 util
*</p>
*
* @Author: wangxiang4
* @since: 2023/6/13
*/
public class DateUtil extends cn.hutool.core.date.DateUtil {
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
public static String formatDate(long dateTime, String pattern) {
return format(new Date(dateTime), pattern);
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
return parse(str.toString());
}
public static final String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static final String formatUTC(final Date date, final String format) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return simpleDateFormat.format(date);
}
}

113
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/FileUtil.java

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
package com.cloud.kicc.common.core.util;
import cn.hutool.core.io.IoUtil;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URLEncoder;
import java.text.DecimalFormat;
/**
* @author yong
* @date 2020/3/22
* @description 文件工具类
*/
public class FileUtil extends cn.hutool.core.io.FileUtil {
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
/**
* 转换文件大小
*
* @param fileS
* @return
*/
public static String fileSize(long fileS) {
DecimalFormat df = new DecimalFormat("#.00");
String fileSizeString = "";
String wrongSize = "0B";
if (fileS == 0) {
return wrongSize;
}
if (fileS < 1024) {
fileSizeString = df.format((double) fileS) + "B";
} else if (fileS < 1048576) {
fileSizeString = df.format((double) fileS / 1024) + "KB";
} else if (fileS < 1073741824) {
fileSizeString = df.format((double) fileS / 1048576) + "MB";
} else {
fileSizeString = df.format((double) fileS / 1073741824) + "GB";
}
return fileSizeString;
}
public static void copyInputStreamToFile(InputStream source, File destination) throws IOException {
try {
copyToFile(source, destination);
} finally {
IoUtil.close(source);
}
}
public static void copyToFile(InputStream source, File destination) throws IOException {
FileOutputStream output = openOutputStream(destination);
try {
IoUtil.copy(source, output);
output.close();
} finally {
IoUtil.close(output);
}
}
public static FileOutputStream openOutputStream(File file) throws IOException {
return openOutputStream(file, false);
}
public static FileOutputStream openOutputStream(File file, boolean append) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (!file.canWrite()) {
throw new IOException("File '" + file + "' cannot be written to");
}
} else {
File parent = file.getParentFile();
if (parent != null && !parent.mkdirs() && !parent.isDirectory()) {
throw new IOException("Directory '" + parent + "' could not be created");
}
}
return new FileOutputStream(file, append);
}
/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
throws UnsupportedEncodingException {
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
} else if (agent.contains("Chrome")) {
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}

499
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/HTMLFilterUtil.java

@ -0,0 +1,499 @@ @@ -0,0 +1,499 @@
package com.cloud.kicc.common.core.util;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*<p>
* HTML过滤器用于去除XSS漏洞隐患
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
public final class HTMLFilterUtil {
/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("\"");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
// @xxx could grow large... maybe use sesat's ReferenceMap
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>();
/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<>();
/**
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
private final String[] vNeedClosingTags;
/**
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
**/
private final String[] vRemoveBlanks;
/**
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
/**
* flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "<b text </b>"
* becomes "<b> text </b>"). If set to false, unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;
/**
* Default constructor.
*/
public HTMLFilterUtil() {
vAllowed = new HashMap<>();
final ArrayList<String> a_atts = new ArrayList<>();
a_atts.add("href");
a_atts.add("target");
vAllowed.put("a", a_atts);
final ArrayList<String> img_atts = new ArrayList<>();
img_atts.add("src");
img_atts.add("width");
img_atts.add("height");
img_atts.add("alt");
vAllowed.put("img", img_atts);
final ArrayList<String> no_atts = new ArrayList<>();
vAllowed.put("b", no_atts);
vAllowed.put("strong", no_atts);
vAllowed.put("i", no_atts);
vAllowed.put("em", no_atts);
vSelfClosingTags = new String[]{"img"};
vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
vDisallowed = new String[]{};
vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
vProtocolAtts = new String[]{"src", "href"};
vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
stripComment = true;
encodeQuotes = true;
alwaysMakeTags = true;
}
/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
@SuppressWarnings("unchecked")
public HTMLFilterUtil(final Map<String, Object> conf) {
assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
vDisallowed = (String[]) conf.get("vDisallowed");
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
}
private void reset() {
vTagCounts.clear();
}
// ---------------------------------------------------------------
// my versions of some PHP library functions
public static String chr(final int decimal) {
return String.valueOf((char) decimal);
}
public static String htmlSpecialChars(final String s) {
String result = s;
result = regexReplace(P_AMP, "&amp;", result);
result = regexReplace(P_QUOTE, "&quot;", result);
result = regexReplace(P_LEFT_ARROW, "&lt;", result);
result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
return result;
}
// ---------------------------------------------------------------
/**
* given a user submitted input String, filter out any invalid or restricted html.
*
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input) {
reset();
String s = input;
s = escapeComments(s);
s = balanceHTML(s);
s = checkTags(s);
s = processRemoveBlanks(s);
s = validateEntities(s);
return s;
}
public boolean isAlwaysMakeTags() {
return alwaysMakeTags;
}
public boolean isStripComments() {
return stripComment;
}
private String escapeComments(final String s) {
final Matcher m = P_COMMENTS.matcher(s);
final StringBuffer buf = new StringBuffer();
if (m.find()) {
final String match = m.group(1); // (.*?)
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
}
m.appendTail(buf);
return buf.toString();
}
private String balanceHTML(String s) {
if (alwaysMakeTags) {
//
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
s = regexReplace(P_BODY_TO_END, "<$1>", s);
s = regexReplace(P_XML_CONTENT, "$1<$2", s);
} else {
//
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);
//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}
return s;
}
private String checkTags(String s) {
Matcher m = P_TAGS.matcher(s);
final StringBuffer buf = new StringBuffer();
while (m.find()) {
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);
// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
final StringBuilder sBuilder = new StringBuilder(buf.toString());
for (String key : vTagCounts.keySet()) {
for (int ii = 0; ii < vTagCounts.get(key); ii++) {
sBuilder.append("</").append(key).append(">");
}
}
s = sBuilder.toString();
return s;
}
private String processRemoveBlanks(final String s) {
String result = s;
for (String tag : vRemoveBlanks) {
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
}
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
}
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
}
return result;
}
private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
Matcher m = regex_pattern.matcher(s);
return m.replaceAll(replacement);
}
private String processTag(final String s) {
// ending tags
Matcher m = P_END_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
if (allowed(name)) {
if (false == inArray(name, vSelfClosingTags)) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) - 1);
return "</" + name + ">";
}
}
}
}
// starting tags
m = P_START_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
final String body = m.group(2);
String ending = m.group(3);
// debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
if (allowed(name)) {
final StringBuilder params = new StringBuilder();
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
final List<String> paramNames = new ArrayList<>();
final List<String> paramValues = new ArrayList<>();
while (m2.find()) {
paramNames.add(m2.group(1)); // ([a-z0-9]+)
paramValues.add(m2.group(3)); // (.*?)
}
while (m3.find()) {
paramNames.add(m3.group(1)); // ([a-z0-9]+)
paramValues.add(m3.group(3)); // ([^\"\\s']+)
}
String paramName, paramValue;
for (int ii = 0; ii < paramNames.size(); ii++) {
paramName = paramNames.get(ii).toLowerCase();
paramValue = paramValues.get(ii);
// debug( "paramName='" + paramName + "'" );
// debug( "paramValue='" + paramValue + "'" );
// debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );
if (allowedAttribute(name, paramName)) {
if (inArray(paramName, vProtocolAtts)) {
paramValue = processParamProtocol(paramValue);
}
params.append(' ').append(paramName).append("=\"").append(paramValue).append("\"");
}
}
if (inArray(name, vSelfClosingTags)) {
ending = " /";
}
if (inArray(name, vNeedClosingTags)) {
ending = "";
}
if (ending == null || ending.length() < 1) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) + 1);
} else {
vTagCounts.put(name, 1);
}
} else {
ending = " /";
}
return "<" + name + params + ending + ">";
} else {
return "";
}
}
// comments
m = P_COMMENT.matcher(s);
if (!stripComment && m.find()) {
return "<" + m.group() + ">";
}
return "";
}
private String processParamProtocol(String s) {
s = decodeEntities(s);
final Matcher m = P_PROTOCOL.matcher(s);
if (m.find()) {
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols)) {
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith("#//")) {
s = "#" + s.substring(3);
}
}
}
return s;
}
private String decodeEntities(String s) {
StringBuffer buf = new StringBuffer();
Matcher m = P_ENTITY.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.decode(match).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENTITY_UNICODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENCODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
s = validateEntities(s);
return s;
}
private String validateEntities(final String s) {
StringBuffer buf = new StringBuffer();
// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find()) {
final String one = m.group(1); // ([^&;]*)
final String two = m.group(2); // (?=(;|&|$))
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);
return encodeQuotes(buf.toString());
}
private String encodeQuotes(final String s) {
if (encodeQuotes) {
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find()) {
final String one = m.group(1); // (>|^)
final String two = m.group(2); // ([^<]+?)
final String three = m.group(3); // (<|$)
// 不替换双引号为&quot;,防止json格式无效 regexReplace(P_QUOTE, "&quot;", two)
m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three));
}
m.appendTail(buf);
return buf.toString();
} else {
return s;
}
}
private String checkEntity(final String preamble, final String term) {
return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&amp;" + preamble;
}
private boolean isValidEntity(final String entity) {
return inArray(entity, vAllowedEntities);
}
private static boolean inArray(final String s, final String[] array) {
for (String item : array) {
if (item != null && item.equals(s)) {
return true;
}
}
return false;
}
private boolean allowed(final String name) {
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}
private boolean allowedAttribute(final String name, final String paramName) {
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}

38
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/JasyptUtil.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package com.cloud.kicc.common.core.util;
import com.ulisesbocchio.jasyptspringboot.encryptor.DefaultLazyEncryptor;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
*<p>
* Jasypt加解密单元测试
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/24
*/
public class JasyptUtil {
public static void main(String[] args) {
testEnvironmentProperties();
}
public static void testEnvironmentProperties() {
System.setProperty("jasypt.encryptor.password", "kicc");
PasswordEncoder ENCODER = new BCryptPasswordEncoder();
System.out.println(ENCODER.encode("123456"));
StringEncryptor stringEncryptor = new DefaultLazyEncryptor(new StandardEnvironment());
//加密方法
System.out.println(stringEncryptor.encrypt("kicc"));
//解密方法
System.out.println(stringEncryptor.decrypt("6GBMom2U/XAHuMG3OSkOMw=="));
}
}

203
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/PinyinUtil.java

@ -0,0 +1,203 @@ @@ -0,0 +1,203 @@
package com.cloud.kicc.common.core.util;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
/**
*<p>
* 拼音工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/24
*/
public class PinyinUtil {
/** 汉字对应ascii范围 */
private static int[] pinyinValue = new int[] { -20319, -20317, -20304, -20295, -20292, -20283, -20265, -20257, -20242, -20230, -20051, -20036, -20032, -20026, -20002, -19990, -19986, -19982,
-19976, -19805, -19784, -19775, -19774, -19763, -19756, -19751, -19746, -19741, -19739, -19728, -19725, -19715, -19540, -19531, -19525, -19515, -19500, -19484, -19479, -19467, -19289,
-19288, -19281, -19275, -19270, -19263, -19261, -19249, -19243, -19242, -19238, -19235, -19227, -19224, -19218, -19212, -19038, -19023, -19018, -19006, -19003, -18996, -18977, -18961,
-18952, -18783, -18774, -18773, -18763, -18756, -18741, -18735, -18731, -18722, -18710, -18697, -18696, -18526, -18518, -18501, -18490, -18478, -18463, -18448, -18447, -18446, -18239,
-18237, -18231, -18220, -18211, -18201, -18184, -18183, -18181, -18012, -17997, -17988, -17970, -17964, -17961, -17950, -17947, -17931, -17928, -17922, -17759, -17752, -17733, -17730,
-17721, -17703, -17701, -17697, -17692, -17683, -17676, -17496, -17487, -17482, -17468, -17454, -17433, -17427, -17417, -17202, -17185, -16983, -16970, -16942, -16915, -16733, -16708,
-16706, -16689, -16664, -16657, -16647, -16474, -16470, -16465, -16459, -16452, -16448, -16433, -16429, -16427, -16423, -16419, -16412, -16407, -16403, -16401, -16393, -16220, -16216,
-16212, -16205, -16202, -16187, -16180, -16171, -16169, -16158, -16155, -15959, -15958, -15944, -15933, -15920, -15915, -15903, -15889, -15878, -15707, -15701, -15681, -15667, -15661,
-15659, -15652, -15640, -15631, -15625, -15454, -15448, -15436, -15435, -15419, -15416, -15408, -15394, -15385, -15377, -15375, -15369, -15363, -15362, -15183, -15180, -15165, -15158,
-15153, -15150, -15149, -15144, -15143, -15141, -15140, -15139, -15128, -15121, -15119, -15117, -15110, -15109, -14941, -14937, -14933, -14930, -14929, -14928, -14926, -14922, -14921,
-14914, -14908, -14902, -14894, -14889, -14882, -14873, -14871, -14857, -14678, -14674, -14670, -14668, -14663, -14654, -14645, -14630, -14594, -14429, -14407, -14399, -14384, -14379,
-14368, -14355, -14353, -14345, -14170, -14159, -14151, -14149, -14145, -14140, -14137, -14135, -14125, -14123, -14122, -14112, -14109, -14099, -14097, -14094, -14092, -14090, -14087,
-14083, -13917, -13914, -13910, -13907, -13906, -13905, -13896, -13894, -13878, -13870, -13859, -13847, -13831, -13658, -13611, -13601, -13406, -13404, -13400, -13398, -13395, -13391,
-13387, -13383, -13367, -13359, -13356, -13343, -13340, -13329, -13326, -13318, -13147, -13138, -13120, -13107, -13096, -13095, -13091, -13076, -13068, -13063, -13060, -12888, -12875,
-12871, -12860, -12858, -12852, -12849, -12838, -12831, -12829, -12812, -12802, -12607, -12597, -12594, -12585, -12556, -12359, -12346, -12320, -12300, -12120, -12099, -12089, -12074,
-12067, -12058, -12039, -11867, -11861, -11847, -11831, -11798, -11781, -11604, -11589, -11536, -11358, -11340, -11339, -11324, -11303, -11097, -11077, -11067, -11055, -11052, -11045,
-11041, -11038, -11024, -11020, -11019, -11018, -11014, -10838, -10832, -10815, -10800, -10790, -10780, -10764, -10587, -10544, -10533, -10519, -10331, -10329, -10328, -10322, -10315,
-10309, -10307, -10296, -10281, -10274, -10270, -10262, -10260, -10256, -10254 };
private static String[] pinyinStr = new String[] { "a", "ai", "an", "ang", "ao", "ba", "bai", "ban", "bang", "bao", "bei", "ben", "beng", "bi", "bian", "biao", "bie", "bin", "bing", "bo", "bu",
"ca", "cai", "can", "cang", "cao", "ce", "ceng", "cha", "chai", "chan", "chang", "chao", "che", "chen", "cheng", "chi", "chong", "chou", "chu", "chuai", "chuan", "chuang", "chui", "chun",
"chuo", "ci", "cong", "cou", "cu", "cuan", "cui", "cun", "cuo", "da", "dai", "dan", "dang", "dao", "de", "deng", "di", "dian", "diao", "die", "ding", "diu", "dong", "dou", "du", "duan",
"dui", "dun", "duo", "e", "en", "er", "fa", "fan", "fang", "fei", "fen", "feng", "fo", "fou", "fu", "ga", "gai", "gan", "gang", "gao", "ge", "gei", "gen", "geng", "gong", "gou", "gu",
"gua", "guai", "guan", "guang", "gui", "gun", "guo", "ha", "hai", "han", "hang", "hao", "he", "hei", "hen", "heng", "hong", "hou", "hu", "hua", "huai", "huan", "huang", "hui", "hun",
"huo", "ji", "jia", "jian", "jiang", "jiao", "jie", "jin", "jing", "jiong", "jiu", "ju", "juan", "jue", "jun", "ka", "kai", "kan", "kang", "kao", "ke", "ken", "keng", "kong", "kou", "ku",
"kua", "kuai", "kuan", "kuang", "kui", "kun", "kuo", "la", "lai", "lan", "lang", "lao", "le", "lei", "leng", "li", "lia", "lian", "liang", "liao", "lie", "lin", "ling", "liu", "long",
"lou", "lu", "lv", "luan", "lue", "lun", "luo", "ma", "mai", "man", "mang", "mao", "me", "mei", "men", "meng", "mi", "mian", "miao", "mie", "min", "ming", "miu", "mo", "mou", "mu", "na",
"nai", "nan", "nang", "nao", "ne", "nei", "nen", "neng", "ni", "nian", "niang", "niao", "nie", "nin", "ning", "niu", "nong", "nu", "nv", "nuan", "nue", "nuo", "o", "ou", "pa", "pai",
"pan", "pang", "pao", "pei", "pen", "peng", "pi", "pian", "piao", "pie", "pin", "ping", "po", "pu", "qi", "qia", "qian", "qiang", "qiao", "qie", "qin", "qing", "qiong", "qiu", "qu",
"quan", "que", "qun", "ran", "rang", "rao", "re", "ren", "reng", "ri", "rong", "rou", "ru", "ruan", "rui", "run", "ruo", "sa", "sai", "san", "sang", "sao", "se", "sen", "seng", "sha",
"shai", "shan", "shang", "shao", "she", "shen", "sheng", "shi", "shou", "shu", "shua", "shuai", "shuan", "shuang", "shui", "shun", "shuo", "si", "song", "sou", "su", "suan", "sui", "sun",
"suo", "ta", "tai", "tan", "tang", "tao", "te", "teng", "ti", "tian", "tiao", "tie", "ting", "tong", "tou", "tu", "tuan", "tui", "tun", "tuo", "wa", "wai", "wan", "wang", "wei", "wen",
"weng", "wo", "wu", "xi", "xia", "xian", "xiang", "xiao", "xie", "xin", "xing", "xiong", "xiu", "xu", "xuan", "xue", "xun", "ya", "yan", "yang", "yao", "ye", "yi", "yin", "ying", "yo",
"yong", "you", "yu", "yuan", "yue", "yun", "za", "zai", "zan", "zang", "zao", "ze", "zei", "zen", "zeng", "zha", "zhai", "zhan", "zhang", "zhao", "zhe", "zhen", "zheng", "zhi", "zhong",
"zhou", "zhu", "zhua", "zhuai", "zhuan", "zhuang", "zhui", "zhun", "zhuo", "zi", "zong", "zou", "zu", "zuan", "zui", "zun", "zuo" };
/**
* 获取所给中文的每个汉字首字母组成首字母字符串
*
* @param chinese 汉字字符串
* @return 首字母字符串
*/
public static String getAllFirstLetter(String chinese) {
if (StrUtil.isBlank(chinese)) {
return StrUtil.EMPTY;
}
int len = chinese.length();
final StrBuilder strBuilder = new StrBuilder(len);
for (int i = 0; i < len; i++) {
strBuilder.append(getFirstLetter(chinese.charAt(i)));
}
return strBuilder.toString();
}
/**
* 获取拼音首字母<br>
* 传入汉字返回拼音首字母<br>
* 如果传入为字母返回其小写形式<br>
* 感谢帝都宁静 提供方法
*
* @param ch 汉字
* @return 首字母小写
*/
public static char getFirstLetter(char ch) {
if (ch >= 'a' && ch <= 'z') {
return ch;
}
if (ch >= 'A' && ch <= 'Z') {
return Character.toLowerCase(ch);
}
final byte[] bys = String.valueOf(ch).getBytes(CharsetUtil.CHARSET_GBK);
if (bys.length == 1) {
return ch;
}
int count = (bys[0] + 256) * 256 + bys[1] + 256;
if (count < 45217) {
return ch;
} else if (count < 45253) {
return 'a';
} else if (count < 45761) {
return 'b';
} else if (count < 46318) {
return 'c';
} else if (count < 46826) {
return 'd';
} else if (count < 47010) {
return 'e';
} else if (count < 47297) {
return 'f';
} else if (count < 47614) {
return 'g';
} else if (count < 48119) {
return 'h';
} else if (count < 49062) {
return 'j';
} else if (count < 49324) {
return 'k';
} else if (count < 49896) {
return 'l';
} else if (count < 50371) {
return 'm';
} else if (count < 50614) {
return 'n';
} else if (count < 50622) {
return 'o';
} else if (count < 50906) {
return 'p';
} else if (count < 51387) {
return 'q';
} else if (count < 51446) {
return 'r';
} else if (count < 52218) {
return 's';
} else if (count < 52698) {
return 't';
} else if (count < 52980) {
return 'w';
} else if (count < 53689) {
return 'x';
} else if (count < 54481) {
return 'y';
} else if (count < 55290) {
return 'z';
}
return ch;
}
/**
* 汉字转拼音
* <hr>
* example : 张三 zhangsan
*
* @param chinese 汉字
* @return 对应的拼音
* @since 4.0.11
*/
public static String getPinYin(String chinese) {
final StrBuilder result = StrUtil.strBuilder();
String strTemp = null;
int len = chinese.length();
for (int j = 0; j < len; j++) {
strTemp = chinese.substring(j, j + 1);
int ascii = getChsAscii(strTemp);
if (ascii > 0) {
//非汉字
result.append((char)ascii);
} else {
for (int i = pinyinValue.length - 1; i >= 0; i--) {
if (pinyinValue[i] <= ascii) {
result.append(pinyinStr[i]);
break;
}
}
}
}
return result.toString();
}
//------------------------------------------------------------------------------------------------------- Private method start
/**
* 获取汉字对应的ascii码
* @param chs 汉字
* @return ascii码
*/
private static int getChsAscii(String chs) {
int asc = 0;
byte[] bytes = chs.getBytes(CharsetUtil.CHARSET_GBK);
switch (bytes.length) {
case 1:
// 英文字符
asc = bytes[0];
break;
case 2:
// 中文字符
int hightByte = 256 + bytes[0];
int lowByte = 256 + bytes[1];
asc = (256 * hightByte + lowByte) - 256 * 256;
break;
default:
throw new UtilException("Illegal resource string");
}
return asc;
}
}

87
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/SpringContextHolderUtil.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
package com.cloud.kicc.common.core.util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
/**
*<p>
* Spring工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Slf4j
@Service
@Lazy(false)
public class SpringContextHolderUtil implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolderUtil.applicationContext = applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 发布事件
* @param event
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
@SneakyThrows
public void destroy() {
SpringContextHolderUtil.clearHolder();
}
}

321
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/TimeUtils.java

@ -0,0 +1,321 @@ @@ -0,0 +1,321 @@
package com.cloud.kicc.common.core.util;
import java.util.Arrays;
import java.util.Date;
/**
*<p>
* 时间计算工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/1/15
*/
public class TimeUtils {
public static String toTimeString(long time) {
TimeUtils t = new TimeUtils(time);
int day = t.get(TimeUtils.DAY);
int hour = t.get(TimeUtils.HOUR);
int minute = t.get(TimeUtils.MINUTE);
int second = t.get(TimeUtils.SECOND);
StringBuilder sb = new StringBuilder();
if (day > 0){
sb.append(day).append("天");
}
if (hour > 0){
sb.append(hour).append("时");
}
if (minute > 0){
sb.append(minute).append("分");
}
if (second > 0){
sb.append(second).append("秒");
}
return sb.toString();
}
/**
* 时间字段常量表示
*/
public final static int SECOND = 0;
/**
* 时间字段常量表示
*/
public final static int MINUTE = 1;
/**
* 时间字段常量表示
*/
public final static int HOUR = 2;
/**
* 时间字段常量表示
*/
public final static int DAY = 3;
/**
* 各常量允许的最大值
*/
private final int[] maxFields = { 59, 59, 23, Integer.MAX_VALUE - 1 };
/**
* 各常量允许的最小值
*/
private final int[] minFields = { 0, 0, 0, Integer.MIN_VALUE };
/**
* 默认的字符串格式时间分隔符
*/
private String timeSeparator = ":";
/**
* 时间数据容器
*/
private int[] fields = new int[4];
/**
* 无参构造将各字段置为 0
*/
public TimeUtils() {
this(0, 0, 0, 0);
}
/**
* 使用时分构造一个时间
* @param hour 小时
* @param minute 分钟
*/
public TimeUtils(int hour, int minute) {
this(0, hour, minute, 0);
}
/**
* 使用时秒构造一个时间
* @param hour 小时
* @param minute 分钟
* @param second
*/
public TimeUtils(int hour, int minute, int second) {
this(0, hour, minute, second);
}
/**
* 使用一个字符串构造时间<br/>
* Time time = new Time("14:22:23");
* @param time 字符串格式的时间默认采用:作为分隔符
*/
public TimeUtils(String time) {
this(time, null);
// System.out.println(time);
}
/**
* 使用时间毫秒构建时间
* @param time
*/
public TimeUtils(long time){
this(new Date(time));
}
/**
* 使用日期对象构造时间
* @param date
*/
public TimeUtils(Date date){
this(DateUtil.formatUTC(date, "HH:mm:ss"));
}
/**
* 使用天秒构造时间进行全字符的构造
* @param day
* @param hour
* @param minute
* @param second
*/
public TimeUtils(int day, int hour, int minute, int second) {
initialize(day, hour, minute, second);
}
/**
* 使用一个字符串构造时间指定分隔符<br/>
* Time time = new Time("14-22-23", "-");
* @param time 字符串格式的时间
*/
public TimeUtils(String time, String timeSeparator) {
if(timeSeparator != null) {
setTimeSeparator(timeSeparator);
}
parseTime(time);
}
/**
* 设置时间字段的值
* @param field 时间字段常量
* @param value 时间字段的值
*/
public void set(int field, int value) {
if(value < minFields[field]) {
throw new IllegalArgumentException(value + ", time value must be positive.");
}
fields[field] = value % (maxFields[field] + 1);
// 进行进位计算
int carry = value / (maxFields[field] + 1);
if(carry > 0) {
int upFieldValue = get(field + 1);
set(field + 1, upFieldValue + carry);
}
}
/**
* 获得时间字段的值
* @param field 时间字段常量
* @return 该时间字段的值
*/
public int get(int field) {
if(field < 0 || field > fields.length - 1) {
throw new IllegalArgumentException(field + ", field value is error.");
}
return fields[field];
}
/**
* 将时间进行运算即加上一个时间
* @param time 需要加的时间
* @return 运算后的时间
*/
public TimeUtils addTime(TimeUtils time) {
TimeUtils result = new TimeUtils();
int up = 0; // 进位标志
for (int i = 0; i < fields.length; i++) {
int sum = fields[i] + time.fields[i] + up;
up = sum / (maxFields[i] + 1);
result.fields[i] = sum % (maxFields[i] + 1);
}
return result;
}
/**
* 将时间进行运算即减去一个时间
* @param time 需要减的时间
* @return 运算后的时间
*/
public TimeUtils subtractTime(TimeUtils time) {
TimeUtils result = new TimeUtils();
int down = 0; // 退位标志
for (int i = 0, k = fields.length - 1; i < k; i++) {
int difference = fields[i] + down;
if (difference >= time.fields[i]) {
difference -= time.fields[i];
down = 0;
} else {
difference += maxFields[i] + 1 - time.fields[i];
down = -1;
}
result.fields[i] = difference;
}
result.fields[DAY] = fields[DAY] - time.fields[DAY] + down;
return result;
}
/**
* 获得时间字段的分隔符
* @return
*/
public String getTimeSeparator() {
return timeSeparator;
}
/**
* 设置时间字段的分隔符用于字符串格式的时间
* @param timeSeparator 分隔符字符串
*/
public void setTimeSeparator(String timeSeparator) {
this.timeSeparator = timeSeparator;
}
private void initialize(int day, int hour, int minute, int second) {
set(DAY, day);
set(HOUR, hour);
set(MINUTE, minute);
set(SECOND, second);
}
private void parseTime(String time) {
if(time == null) {
initialize(0, 0, 0, 0);
return;
}
String t = time;
int field = DAY;
set(field--, 0);
int p = -1;
while((p = t.indexOf(timeSeparator)) > -1) {
parseTimeField(time, t.substring(0, p), field--);
t = t.substring(p + timeSeparator.length());
}
parseTimeField(time, t, field--);
}
private void parseTimeField(String time, String t, int field) {
if(field < SECOND || t.length() < 1) {
parseTimeException(time);
}
char[] chs = t.toCharArray();
int n = 0;
for(int i = 0; i < chs.length; i++) {
if(chs[i] <= ' ') {
continue;
}
if(chs[i] >= '0' && chs[i] <= '9') {
n = n * 10 + chs[i] - '0';
continue;
}
parseTimeException(time);
}
set(field, n);
}
private void parseTimeException(String time) {
throw new IllegalArgumentException(time + ", time format error, HH"
+ this.timeSeparator + "mm" + this.timeSeparator + "ss");
}
public String toString() {
StringBuilder sb = new StringBuilder(16);
sb.append(fields[DAY]).append(',').append(' ');
buildString(sb, HOUR).append(timeSeparator);
buildString(sb, MINUTE).append(timeSeparator);
buildString(sb, SECOND);
return sb.toString();
}
private StringBuilder buildString(StringBuilder sb, int field) {
if(fields[field] < 10) {
sb.append('0');
}
return sb.append(fields[field]);
}
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + Arrays.hashCode(fields);
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final TimeUtils other = (TimeUtils) obj;
if (!Arrays.equals(fields, other.fields)) {
return false;
}
return true;
}
}

186
kicc-common-core/src/main/java/com/cloud/kicc/common/core/util/WebUtil.java

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
package com.cloud.kicc.common.core.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.json.JSONUtil;
import com.cloud.kicc.common.core.constant.CommonConstants;
import com.cloud.kicc.common.core.exception.CheckedException;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
/**
*<p>
* 扩展用于 Web 应用程序的各种实用程序工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Slf4j
@UtilityClass
public class WebUtil extends org.springframework.web.util.WebUtils {
private final String BASIC_ = "Basic ";
private final String UNKNOWN = "unknown";
/**
* 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解
* @param handlerMethod HandlerMethod
* @return 是否ajax请求
*/
public boolean isBody(HandlerMethod handlerMethod) {
ResponseBody responseBody = ClassUtil.getAnnotation(handlerMethod, ResponseBody.class);
return responseBody != null;
}
/**
* 读取cookie
* @param name cookie name
* @return cookie value
*/
public String getCookieVal(String name) {
if (WebUtil.getRequest().isPresent()) {
return getCookieVal(WebUtil.getRequest().get(), name);
}
return null;
}
/**
* 读取cookie
* @param request HttpServletRequest
* @param name cookie name
* @return cookie value
*/
public String getCookieVal(HttpServletRequest request, String name) {
Cookie cookie = getCookie(request, name);
return cookie != null ? cookie.getValue() : null;
}
/**
* 清除 某个指定的cookie
* @param response HttpServletResponse
* @param key cookie key
*/
public void removeCookie(HttpServletResponse response, String key) {
setCookie(response, key, null, 0);
}
/**
* 设置cookie
* @param response HttpServletResponse
* @param name cookie name
* @param value cookie value
* @param maxAgeInSeconds maxage
*/
public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAgeInSeconds);
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
/**
* 获取 HttpServletRequest
* @return {HttpServletRequest}
*/
public Optional<HttpServletRequest> getRequest() {
return Optional
.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
}
/**
* 获取 HttpServletResponse
* @return {HttpServletResponse}
*/
public HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
/**
* 返回json
* @param response HttpServletResponse
* @param result 结果对象
*/
public void renderJson(HttpServletResponse response, Object result) {
renderJson(response, result, MediaType.APPLICATION_JSON_VALUE);
}
/**
* 返回json
* @param response HttpServletResponse
* @param result 结果对象
* @param contentType contentType
*/
public void renderJson(HttpServletResponse response, Object result, String contentType) {
response.setCharacterEncoding(CommonConstants.UTF8);
response.setContentType(contentType);
try (PrintWriter out = response.getWriter()) {
out.append(JSONUtil.toJsonStr(result));
}
catch (IOException e) {
log.error(e.getMessage(), e);
}
}
/**
* 从request 获取CLIENT_ID
* @return
*/
@SneakyThrows
public String getClientId(ServerHttpRequest request) {
String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return splitClient(header)[0];
}
@SneakyThrows
public String getClientId() {
if (WebUtil.getRequest().isPresent()) {
String header = WebUtil.getRequest().get().getHeader(HttpHeaders.AUTHORIZATION);
return splitClient(header)[0];
}
return null;
}
@NotNull
private static String[] splitClient(String header) {
if (header == null || !header.startsWith(BASIC_)) {
throw new CheckedException("请求头中client信息为空!");
}
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
}
catch (IllegalArgumentException e) {
throw new CheckedException("Failed to decode basic authentication token");
}
String token = new String(decoded, StandardCharsets.UTF_8);
int delim = token.indexOf(":");
if (delim == -1) {
throw new CheckedException("Invalid basic authentication token");
}
return new String[] { token.substring(0, delim), token.substring(delim + 1) };
}
}

5
kicc-common-core/src/main/resources/META-INF/spring.factories

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cloud.kicc.common.core.config.WebMvcConfiguration,\
com.cloud.kicc.common.core.config.RestTemplateConfiguration,\
com.cloud.kicc.common.core.config.GatewayConfigProperties,\
com.cloud.kicc.common.core.util.SpringContextHolderUtil

17
kicc-common-core/src/main/resources/banner.txt

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
${AnsiColor.BRIGHT_GREEN}
___ __ ___ ________ ________
|\ \|\ \ |\ \|\ ____\|\ ____\
\ \ \/ /|\ \ \ \ \___|\ \ \___|
\ \ ___ \ \ \ \ \ \ \ \
\ \ \\ \ \ \ \ \ \____\ \ \____
\ \__\\ \__\ \__\ \_______\ \_______\
\|__| \|__|\|__|\|_______|\|_______|
www.kanglailab.com
长沙康来生物有限公司 Microservice Architecture
${AnsiColor.DEFAULT}

57
kicc-common-core/src/main/resources/logback-spring.xml

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--基础日志配置,工程继承可以进行替换-->
<configuration debug="false" scan="false">
<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
<property name="log.path" value="logs/${spring.application.name}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 控制台日志输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 日志文件调试输出 -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- 日志文件错误输出 -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
</root>
</configuration>

67
kicc-common-data/pom.xml

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
<?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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-data</artifactId>
<packaging>jar</packaging>
<description>kicc 数据服务核心包</description>
<!--考虑这个作为一个单模块使用,目前依赖了工具类核心包,后续引入依赖需要注意低耦合-->
<dependencies>
<!--数据服务核心包-->
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-core</artifactId>
</dependency>
<!--支持jackson注解-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--数据源管理需要哪个就开启哪个-->
<!--Oracle-->
<!--<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<version>${oracle.version}</version>
</dependency>-->
<!--postgresql-->
<!--<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>-->
<!--对外部api提供mvc相关服务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!--security 依赖-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
</dependencies>
</project>

24
kicc-common-data/src/main/java/com/cloud/kicc/common/data/annotation/EnableKiccDataRepository.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package com.cloud.kicc.common.data.annotation;
import com.cloud.kicc.common.data.config.MybatisConfiguration;
import com.cloud.kicc.common.data.config.RedisTemplateConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*<p>
* 开启数据存储和访问层
*</p>
*
* @Author: wangxiang4
* @Since: 2023/9/15
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ MybatisConfiguration.class, RedisTemplateConfiguration.class })
public @interface EnableKiccDataRepository {
}

66
kicc-common-data/src/main/java/com/cloud/kicc/common/data/config/MybatisConfiguration.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
package com.cloud.kicc.common.data.config;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.MybatisMapWrapperFactory;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.cloud.kicc.common.core.factory.YamlPropertySourceFactory;
import com.cloud.kicc.common.data.handler.BaseMetaObjectHandler;
import com.cloud.kicc.common.data.handler.KiccTenantLineHandler;
import com.cloud.kicc.common.data.plugins.KiccPaginationInnerInterceptor;
import com.cloud.kicc.common.data.plugins.KiccTenantLineInnerInterceptor;
import com.cloud.kicc.common.data.properties.TenantProperties;
import lombok.RequiredArgsConstructor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
*<p>
* mybatis plus 统一配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Configuration(proxyBeanMethods = false)
@MapperScan("com.cloud.kicc.**.mapper")
@EnableConfigurationProperties(TenantProperties.class)
@PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:kicc-tenant.yml")
@RequiredArgsConstructor
public class MybatisConfiguration {
private final TenantProperties tenantProperties;
/**
* 插件配置
* 多租户插件, 自动拼接多租户id进行增删改查
* 分页插件, 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new KiccTenantLineInnerInterceptor(new KiccTenantLineHandler(tenantProperties)));
interceptor.addInnerInterceptor(new KiccPaginationInnerInterceptor());
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
/**
* 审计字段自动填充
* @return {@link MetaObjectHandler}
*/
@Bean
public BaseMetaObjectHandler mybatisPlusMetaObjectHandler() {
return new BaseMetaObjectHandler();
}
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer(){
return configuration -> configuration.setObjectWrapperFactory(new MybatisMapWrapperFactory());
}
}

60
kicc-common-data/src/main/java/com/cloud/kicc/common/data/config/RedisTemplateConfiguration.java

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
package com.cloud.kicc.common.data.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.EnableCaching;
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.*;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
*<p>
* Redis 配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@EnableCaching
@Configuration(proxyBeanMethods = false)
public class RedisTemplateConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.java());
redisTemplate.setHashValueSerializer(RedisSerializer.java());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}

37
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/BaseEntity.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
package com.cloud.kicc.common.data.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
*<p>
* 基础模型
*</p>
*
* @Author: wangxiang4
* @Date: 2021/12/28
*/
@Data
public class BaseEntity implements Serializable {
protected static final long serialVersionUID = 1L;
/** 多租户ID */
@ApiModelProperty("多租户ID")
protected String tenantId;
/** 当前用户 */
@ApiModelProperty("当前用户")
@TableField(exist = false)
protected KiccUser currentUser;
/** 自定义sql过滤 */
@TableField(exist = false)
@JsonIgnore
private String sqlFilter;
}

185
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/CasUser.java

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
package com.cloud.kicc.common.data.entity;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*<p>
* CAS统一认证用户数据
*</p>
*
* @Author: wangxiang4
* @Since: 2023/8/16
*/
@Setter
@Getter
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class CasUser extends User {
private static final long serialVersionUID = 1L;
/** 用户ID */
private String id;
/** 昵称 */
private String nickName;
/** 邮箱 */
private String email;
/** 手机号 */
private String phone;
/** 性别 */
private String sex;
/** 头像地址 */
private String avatar;
/** 最后登陆ip */
private String loginIp;
/** 最后登陆时间 */
private LocalDateTime loginTime;
/** 状态 */
private String ssoStatus;
/** 创建ID */
private String ssoCreateById;
/** 创建人 */
private String ssoCreateByName;
/** 创建时间 */
private LocalDateTime ssoCreateTime;
/** 更新ID */
private String ssoUpdateById;
/** 更新人 */
private String ssoUpdateByName;
/** 更新时间 */
private LocalDateTime ssoUpdateTime;
/** 备注 */
private String remarks;
/** 角色ID */
private String roleId;
/** 多租户ID */
private String tenantId;
/** sso扩展信息 */
private Map<String, String> exPrincipals = new ConcurrentHashMap<>(3);
public CasUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public CasUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
public CasUser(String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities,
String id,
String nickName,
String email,
String phone,
String sex,
String avatar,
String loginIp,
LocalDateTime loginTime,
String ssoStatus,
String ssoCreateById,
String ssoCreateByName,
LocalDateTime ssoCreateTime,
String ssoUpdateById,
String ssoUpdateByName,
LocalDateTime ssoUpdateTime,
String remarks) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.id = id;
this.nickName = nickName;
this.email = email;
this.phone = phone;
this.sex = sex;
this.avatar = avatar;
this.loginIp = loginIp;
this.loginTime = loginTime;
this.ssoStatus = ssoStatus;
this.ssoCreateById = ssoCreateById;
this.ssoCreateByName = ssoCreateByName;
this.ssoCreateTime = ssoCreateTime;
this.ssoUpdateById = ssoUpdateById;
this.ssoUpdateByName = ssoUpdateByName;
this.ssoUpdateTime = ssoUpdateTime;
this.remarks = remarks;
}
public CasUser(String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities,
String id,
String nickName,
String email,
String phone,
String sex,
String avatar,
String loginIp,
LocalDateTime loginTime,
String ssoStatus,
String ssoCreateById,
String ssoCreateByName,
LocalDateTime ssoCreateTime,
String ssoUpdateById,
String ssoUpdateByName,
LocalDateTime ssoUpdateTime,
String remarks,
String roleId,
String tenantId) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.id = id;
this.nickName = nickName;
this.email = email;
this.phone = phone;
this.sex = sex;
this.avatar = avatar;
this.loginIp = loginIp;
this.loginTime = loginTime;
this.ssoStatus = ssoStatus;
this.ssoCreateById = ssoCreateById;
this.ssoCreateByName = ssoCreateByName;
this.ssoCreateTime = ssoCreateTime;
this.ssoUpdateById = ssoUpdateById;
this.ssoUpdateByName = ssoUpdateByName;
this.ssoUpdateTime = ssoUpdateTime;
this.remarks = remarks;
this.roleId = roleId;
this.tenantId = tenantId;
}
}

79
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/CommonEntity.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
package com.cloud.kicc.common.data.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
*<p>
* 通用模型
*</p>
*
* @Author: wangxiang4
* @Date: 2021/12/28
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class CommonEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 创建id */
@ApiModelProperty("创建id")
@TableField(value = "create_by_id", fill = FieldFill.INSERT)
protected String createById;
/** 创建者 */
@ApiModelProperty("创建人")
@TableField(value = "create_by_name", fill = FieldFill.INSERT)
protected String createByName;
/** 创建时间 */
@ApiModelProperty("创建时间")
@TableField(value = "create_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime createTime;
/** 更新id */
@ApiModelProperty("更新id")
@TableField(value = "update_by_id", fill = FieldFill.UPDATE)
protected String updateById;
/** 更新者 */
@ApiModelProperty("更新者")
@TableField(value = "update_by_name", fill = FieldFill.UPDATE)
protected String updateByName;
/** 更新时间 */
@ApiModelProperty("更新时间")
@TableField(value = "update_time", fill = FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime updateTime;
/** 备注 */
@ApiModelProperty("备注")
protected String remarks;
/** 删除标志(0代表存在 1代表删除)*/
@TableLogic
@JsonIgnore
protected String delFlag;
/** 开始时间 */
@TableField(exist = false)
@JsonIgnore
private String beginTime;
/** 结束时间 */
@TableField(exist = false)
@JsonIgnore
private String endTime;
}

109
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/KiccUser.java

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
package com.cloud.kicc.common.data.entity;
import cn.hutool.core.util.ObjectUtil;
import com.cloud.kicc.common.core.constant.SecurityConstants;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.time.LocalDateTime;
import java.util.List;
/**
*<p>
* 扩展用户数据
*</p>
*
* @Author: wangxiang4
* @Since: 2023/8/16
*/
@Getter
@Setter
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@ApiModel(description = "完整用户信息")
public class KiccUser extends CasUser {
@ApiModelProperty("扩展用户ID")
private String id;
@ApiModelProperty("CAS用户ID")
private String casUserId;
@ApiModelProperty("用户类型 {@link com.cloud.kicc.system.api.enums.UserTypeEnum }")
private String userType;
@ApiModelProperty("机构ID")
private String deptId;
@ApiModelProperty("机构名称")
private String deptName;
@ApiModelProperty("地图标记点位置图片旋转值")
private Double mapOrientation;
@ApiModelProperty("地图设计器默认中心点位置")
private String mapCenter;
@ApiModelProperty("帐号状态(0正常 1停用)")
private String status;
@ApiModelProperty("指定登录后首页跳转")
private String homePath;
@ApiModelProperty("角色ID集合")
private String[] roleIds;
@ApiModelProperty("菜单按钮权限")
private String[] permissions;
@ApiModelProperty("多租户ID集合")
private String[] tenantIds;
@ApiModelProperty("创建ID")
private String createById;
@ApiModelProperty("创建人")
private String createByName;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新ID")
private String updateById;
@ApiModelProperty("更新人")
private String updateByName;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
public KiccUser() {
super(SecurityConstants.MOCK_USERNAME, SecurityConstants.MOCK_PASSWORD, AuthorityUtils.createAuthorityList());
}
@JsonCreator
public KiccUser(@JsonProperty("username") String username,
@JsonProperty("password") String password,
@JsonProperty("enabled") boolean enabled,
@JsonProperty("accountNonExpired") boolean accountNonExpired,
@JsonProperty("credentialsNonExpired") boolean credentialsNonExpired,
@JsonProperty("accountNonLocked") boolean accountNonLocked,
@JsonProperty("authorities") List<SimpleGrantedAuthority> authorities) {
super(ObjectUtil.defaultIfBlank(username, SecurityConstants.MOCK_USERNAME),
ObjectUtil.defaultIfBlank(password, SecurityConstants.MOCK_PASSWORD),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
ObjectUtil.defaultIfNull(authorities, AuthorityUtils.createAuthorityList()));
}
}

66
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/SsoUser.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
package com.cloud.kicc.common.data.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cloud.kicc.common.data.entity.CommonEntity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
*<p>
* SSO用户统一表
*</p>
*
* @Author: wangxiang4
* @Since: 2023/8/6
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cas_sso_user")
@ApiModel(description = "SSO用户表")
public class SsoUser extends CommonEntity {
private static final long serialVersionUID = 1L;
/** 用户ID */
private String id;
/** 用户名 */
private String userName;
/** 昵称 */
private String nickName;
/** 密码 */
private String password;
/** 用户邮箱 */
private String email;
/** 手机号码 */
private String phone;
/** 用户性别(0男 1女 2未知)*/
private String sex;
/** 头像路径 */
private String avatar;
/** 最后登陆IP */
private String loginIp;
/** 帐号状态(0正常 1停用)*/
private String status;
/** 最后登陆时间 */
private LocalDateTime loginTime;
/** 新密码 */
@TableField(exist = false)
private String newPassword;
}

46
kicc-common-data/src/main/java/com/cloud/kicc/common/data/entity/TreeEntity.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
package com.cloud.kicc.common.data.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
*<p>
* 树结构模型
*</p>
*
* @Author: wangxiang4
* @Date: 2021/12/28
*/
@Data
public class TreeEntity<T> extends CommonEntity {
private static final long serialVersionUID = 1L;
/** 编号 **/
@ApiModelProperty("编号")
private String id;
/** 父级编号 **/
@ApiModelProperty("父级编号")
private String parentId;
/** 名称 */
@ApiModelProperty("名称")
protected String name;
/** 排序 **/
@ApiModelProperty("排序")
private Integer sort;
@ApiModelProperty("子级集合")
@TableField(exist = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
protected List<T> children;
}

52
kicc-common-data/src/main/java/com/cloud/kicc/common/data/enums/DataTypeEnum.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package com.cloud.kicc.common.data.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*<p>
* 数据类型
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Getter
@AllArgsConstructor
public enum DataTypeEnum {
/**
* mysql
*/
MYSQL("mysql", "com.mysql.cj.jdbc.Driver"),
/**
* sqlserver
*/
SQLSERVER("sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver"),
/**
* oracle
*/
ORACLE("oracle", "oracle.jdbc.driver.OracleDriver"),
/**
* Postgresql
*/
POSTGRESQL("postgresql", "org.postgresql.Driver"),
/**
* sqlite
*/
SQLITE("sqlite", "org.sqlite.JDBC");
/**
* 类型
*/
private final String type;
/**
* 驱动
*/
private final String driverClassName;
}

75
kicc-common-data/src/main/java/com/cloud/kicc/common/data/handler/BaseMetaObjectHandler.java

@ -0,0 +1,75 @@ @@ -0,0 +1,75 @@
package com.cloud.kicc.common.data.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.cloud.kicc.common.data.entity.CasUser;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.time.LocalDateTime;
import java.util.Optional;
/**
*<p>
* 公共字段自动填充
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public class BaseMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Object createTime = getFieldValByName("createTime", metaObject);
if (createTime == null) {
setFieldValByName("createTime", LocalDateTime.now(), metaObject);
}
if (Optional.ofNullable(getUser()).isPresent()) {
Object createById = getFieldValByName("createById", metaObject);
if (createById == null) {
setFieldValByName("createById", getUser().getId(), metaObject);
}
Object createByName = getFieldValByName("createByName", metaObject);
if (createByName == null) {
setFieldValByName("createByName", getUser().getUsername(), metaObject);
}
}
}
@Override
public void updateFill(MetaObject metaObject) {
Object fieldValue = getFieldValByName("updateTime", metaObject);
if (fieldValue == null) {
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
if (Optional.ofNullable(getUser()).isPresent()) {
Object updateById = getFieldValByName("updateById", metaObject);
if (updateById == null) {
setFieldValByName("updateById", getUser().getId(), metaObject);
}
Object updateByName = getFieldValByName("updateByName", metaObject);
if (updateByName == null) {
setFieldValByName("updateByName", getUser().getUsername(), metaObject);
}
}
}
/**
* 获取用户
*/
protected CasUser getUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Optional.ofNullable(authentication).isPresent()) {
Object principal = authentication.getPrincipal();
if (principal instanceof CasUser) {
return (CasUser) principal;
}
}
return null;
}
}

77
kicc-common-data/src/main/java/com/cloud/kicc/common/data/handler/KiccTenantLineHandler.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
package com.cloud.kicc.common.data.handler;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.cloud.kicc.common.data.entity.CasUser;
import com.cloud.kicc.common.data.override.TenantLikeExpression;
import com.cloud.kicc.common.data.properties.TenantProperties;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;
import java.util.Optional;
/**
*<p>
* 多租户拦截处理
*</p>
*
* @Author: wangxiang4
* @Date: 2022/4/2
*/
public class KiccTenantLineHandler implements TenantLineHandler {
private TenantProperties tenantProperties;
public KiccTenantLineHandler(TenantProperties tenantProperties) {
this.tenantProperties = tenantProperties;
}
/**
* 默认为tenant_id字段,尽量不要去改动,因为会牵扯到实体类中的TenantId字段
* @return String: 表中的多租户字段
*/
@Override
public String getTenantIdColumn() {
return TenantLineHandler.super.getTenantIdColumn();
}
@Override
public boolean ignoreTable(String tableName) {
return tenantProperties.getExclusionTable().contains(tableName);
}
/**
* 新增数据字段中存在多租户字段是否忽略此多租户字段拼接新增
* @Param columns: 当前新增表所有字段
* @Param tenantIdColumn: 多租户字段
* @return boolean: 是否忽略此多租户字段拼接新增
*/
@Override
public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
return TenantLineHandler.super.ignoreInsert(columns, tenantIdColumn);
}
@Override
public Expression getTenantId() {
// 返回当前用户所属的多租户ID进行条件拼接
return ObjectUtil.isNotEmpty(getUser()) ? new TenantLikeExpression(getUser().getTenantId()): null;
}
/**
* 获取用户
*/
protected CasUser getUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Optional.ofNullable(authentication).isPresent()) {
Object principal = authentication.getPrincipal();
if (principal instanceof CasUser) {
return (CasUser) principal;
}
}
return null;
}
}

70
kicc-common-data/src/main/java/com/cloud/kicc/common/data/override/TenantLikeExpression.java

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
package com.cloud.kicc.common.data.override;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import java.util.Objects;
/**
*<p>
* 重写StringValue,支持多租户like拼接查询
* 由于内部StringExpression会拼接默认会加''
* 而多租户like条件经过处理不许需要加'',如果加上会导致数据查不出
*</p>
*
* @Author: wangxiang4
* @Date: 2022/5/11
*/
public class TenantLikeExpression extends ASTNodeAccessImpl implements Expression {
private String value = "";
public TenantLikeExpression() {
}
public TenantLikeExpression(String escapedValue) {
this.value = escapedValue;
}
public String getValue() {
return this.value;
}
public void setValue(String string) {
this.value = string;
}
@Override
public void accept(ExpressionVisitor expressionVisitor) {
}
@Override
public String toString() {
return this.value;
}
public TenantLikeExpression withValue(String value) {
this.setValue(value);
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o != null && this.getClass() == o.getClass()) {
TenantLikeExpression that = (TenantLikeExpression)o;
return Objects.equals(this.value, that.value);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(new Object[]{this.value});
}
}

38
kicc-common-data/src/main/java/com/cloud/kicc/common/data/plugins/KiccPaginationInnerInterceptor.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package com.cloud.kicc.common.data.plugins;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ParameterUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.SQLException;
/**
*<p>
* 分页拦截器
* 重构分页插件, size 小于 0 , 直接设置为 0, 防止错误查询全表
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
public class KiccPaginationInnerInterceptor extends PaginationInnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
// size 小于 0 直接设置为 0 , 即不查询任何数据
if (null != page && page.getSize() < 0) {
page.setSize(0);
}
super.beforeQuery(executor, ms, page, rowBounds, resultHandler, boundSql);
}
}

209
kicc-common-data/src/main/java/com/cloud/kicc/common/data/plugins/KiccTenantLineInnerInterceptor.java

@ -0,0 +1,209 @@ @@ -0,0 +1,209 @@
package com.cloud.kicc.common.data.plugins;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.cloud.kicc.common.core.exception.CheckedException;
import com.cloud.kicc.common.data.override.TenantLikeExpression;
import lombok.NoArgsConstructor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;
/**
*<p>
* 租户线路内部拦截器
* 重构租户线路内部拦截器,支持查询多个多租户ID,还支持查询多个租户的共享数据,默认只支持一个多租户id查询
* 支持多租户ID不存在时,查询所有租户ID数据
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@NoArgsConstructor
public class KiccTenantLineInnerInterceptor extends TenantLineInnerInterceptor {
private TenantLineHandler tenantLineHandler;
public KiccTenantLineInnerInterceptor(final TenantLineHandler tenantLineHandler) {
super(tenantLineHandler);
this.tenantLineHandler = tenantLineHandler;
}
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
if (ObjectUtil.isNotEmpty(tenantLineHandler.getTenantId())){
super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
}
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
if (ObjectUtil.isNotEmpty(tenantLineHandler.getTenantId())) {
super.beforePrepare(sh, connection, transactionTimeout);
}
}
@Override
protected void processInsert(Insert insert, int index, String sql, Object obj) {
if (!this.tenantLineHandler.ignoreTable(insert.getTable().getName())) {
List<Column> columns = insert.getColumns();
if (!CollectionUtils.isEmpty(columns)) {
String tenantIdColumn = this.tenantLineHandler.getTenantIdColumn();
if (!this.tenantLineHandler.ignoreInsert(columns, tenantIdColumn)) {
columns.add(new Column(tenantIdColumn));
List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
// 替换likeExpression支持查询多个租户ID的条件,包括查询数据对应多个多租户ID的数据
List<String> tenantIds = StrUtil.split(this.tenantLineHandler.getTenantId().toString(), ",");
StringBuilder statementBuilder = new StringBuilder();
tenantIds.forEach(tenantId -> {
LikeExpression likeExpression = new LikeExpression();
likeExpression.setLeftExpression(new Column(tenantIdColumn));
likeExpression.setRightExpression(new StringValue("%" + tenantId + "%"));
statementBuilder.append(likeExpression + " OR ");
});
if (statementBuilder.length() == 0) {
throw new CheckedException("当前用户没有分配租户");
}
statementBuilder.delete(statementBuilder.length()-4, statementBuilder.length());
TenantLikeExpression tenantLikeExpression = new TenantLikeExpression(statementBuilder.toString());
Parenthesis parenthesis = new Parenthesis(tenantLikeExpression);
duplicateUpdateColumns.add(parenthesis);
}
Select select = insert.getSelect();
if (select != null) {
this.processInsertSelect(select.getSelectBody());
} else {
if (insert.getItemsList() == null) {
throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId", new Object[0]);
}
ItemsList itemsList = insert.getItemsList();
if (itemsList instanceof MultiExpressionList) {
((MultiExpressionList)itemsList).getExpressionLists().forEach((el) -> {
el.getExpressions().add(new StringValue(this.tenantLineHandler.getTenantId().toString()));
});
} else {
((ExpressionList)itemsList).getExpressions().add(new StringValue(this.tenantLineHandler.getTenantId().toString()));
}
}
}
}
}
}
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
Table table = update.getTable();
if (!this.tenantLineHandler.ignoreTable(table.getName())) {
update.setWhere(this.andLikeExpression(table, update.getWhere()));
}
}
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
if (!this.tenantLineHandler.ignoreTable(delete.getTable().getName())) {
delete.setWhere(this.andLikeExpression(delete.getTable(), delete.getWhere()));
}
}
/** 重写andExpression表达式,支持like查询多个参数 */
protected Expression andLikeExpression(Table table, Expression where) {
// 替换likeExpression支持查询多个租户ID的条件,包括查询数据对应多个多租户ID的数据
List<String> tenantIds = StrUtil.split(this.tenantLineHandler.getTenantId().toString(), ",");
StringBuilder statementBuilder = new StringBuilder();
tenantIds.forEach(tenantId -> {
LikeExpression likeExpression = new LikeExpression();
likeExpression.setLeftExpression(this.getAliasColumn(table));
likeExpression.setRightExpression(new StringValue("%" + tenantId + "%"));
statementBuilder.append(likeExpression + " OR ");
});
if (statementBuilder.length() == 0) {
throw new CheckedException("当前用户没有分配租户");
}
statementBuilder.delete(statementBuilder.length()-4, statementBuilder.length());
TenantLikeExpression tenantLikeExpression = new TenantLikeExpression(statementBuilder.toString());
Parenthesis parenthesis = new Parenthesis(tenantLikeExpression);
if (null != where) {
return where instanceof OrExpression ? new AndExpression(parenthesis, new Parenthesis(where)) : new AndExpression(parenthesis, where);
} else {
return parenthesis;
}
}
@Override
protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
if (CollectionUtils.isEmpty(tables)) {
return currentExpression;
} else {
// 替换likeExpression支持查询多个租户ID的条件,包括查询数据对应多个多租户ID的数据
List<Parenthesis> likeParenthesisExpressions = tables.stream()
.filter(x -> !this.tenantLineHandler.ignoreTable(x.getName()))
.map(item -> {
List<String> tenantIds = StrUtil.split(this.tenantLineHandler.getTenantId().toString(), ",");
StringBuilder statementBuilder = new StringBuilder();
tenantIds.forEach(tenantId -> {
LikeExpression likeExpression = new LikeExpression();
likeExpression.setLeftExpression(this.getAliasColumn(item));
likeExpression.setRightExpression(new StringValue("%" + tenantId + "%"));
statementBuilder.append(likeExpression + " OR ");
});
if (statementBuilder.length() == 0) {
throw new CheckedException("当前用户没有分配租户");
}
statementBuilder.delete(statementBuilder.length()-4, statementBuilder.length());
TenantLikeExpression tenantLikeExpression = new TenantLikeExpression(statementBuilder.toString());
Parenthesis parenthesis = new Parenthesis(tenantLikeExpression);
return parenthesis;
}).collect(Collectors.toList());
if (CollectionUtils.isEmpty(likeParenthesisExpressions)) {
return currentExpression;
} else {
Expression injectExpression = likeParenthesisExpressions.get(0);
if (likeParenthesisExpressions.size() > 1) {
for(int i = 1; i < likeParenthesisExpressions.size(); ++i) {
injectExpression = new AndExpression(injectExpression, likeParenthesisExpressions.get(i));
}
}
if (currentExpression == null) {
return injectExpression;
} else {
return currentExpression instanceof OrExpression ? new AndExpression(new Parenthesis(currentExpression), injectExpression) : new AndExpression(currentExpression, injectExpression);
}
}
}
}
}

24
kicc-common-data/src/main/java/com/cloud/kicc/common/data/properties/TenantProperties.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package com.cloud.kicc.common.data.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
*<p>
* 多租户配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/4/2+
*/
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
private Boolean enableInsert;
private List<String> exclusionTable;
}

1
kicc-common-data/src/main/resources/META-INF/spring.factories

@ -0,0 +1 @@ @@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=

4
kicc-common-data/src/main/resources/kicc-tenant.yml

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
tenant:
# 需要排除的多租户的表
exclusionTable:
- sys_tenant

35
kicc-common-datasource/pom.xml

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
<?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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-datasource</artifactId>
<packaging>jar</packaging>
<description>kicc 动态切换数据源组件</description>
<!--考虑这个作为一个单模块使用,后续引入依赖需要注意低耦合-->
<dependencies>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-data</artifactId>
</dependency>
<!--mybatis-dynamic-datasource-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${apache.collections4.version}</version>
</dependency>
</dependencies>
</project>

53
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/DynamicDataSourceConfiguration.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package com.cloud.kicc.common.datasource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.cloud.kicc.common.datasource.dynamic.DynamicDataSourceJdbcProvider;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
*<p>
* 动态数据源切换配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Configuration(proxyBeanMethods = false)
public class DynamicDataSourceConfiguration {
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider(DataSourceProperties dataSourceProperties, DynamicDataSourceProperties dynamicDataSourceProperties) {
String driverClassName = dataSourceProperties.getDriverClassName();
String url = dataSourceProperties.getUrl();
String username = dataSourceProperties.getUsername();
String password = dataSourceProperties.getPassword();
DataSourceProperty master = dynamicDataSourceProperties.getDatasource().get(dynamicDataSourceProperties.getPrimary());
if (master != null) {
driverClassName = master.getDriverClassName();
url = master.getUrl();
username = master.getUsername();
password = master.getPassword();
}
return new DynamicDataSourceJdbcProvider(dynamicDataSourceProperties, driverClassName, url, username, password);
}
@Bean
public DataSource dataSource(DynamicDataSourceProperties dynamicDataSourceProperties) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
dataSource.setStrict(dynamicDataSourceProperties.getStrict());
dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
dataSource.setSeata(dynamicDataSourceProperties.getSeata());
return dataSource;
}
}

23
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/annotation/EnableDynamicDataSource.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package com.cloud.kicc.common.datasource.annotation;
import com.cloud.kicc.common.datasource.DynamicDataSourceConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*<p>
* 开启动态数据源
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(DynamicDataSourceConfiguration.class)
public @interface EnableDynamicDataSource {
}

45
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/dynamic/DynamicDataSource.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
package com.cloud.kicc.common.datasource.dynamic;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
*<p>
* 动态数据源
*</p>
*
* @Author: wangxiang4
* @Since: 2023/7/3
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class DynamicDataSource {
/**
* 数据源ID
*/
private String id;
/**
* 驱动类
*/
private String driverClass;
/**
* 数据库链接
*/
private String url;
/**
* 数据库账号名
*/
private String username;
/**
* 数据库密码
*/
private String password;
}

91
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/dynamic/DynamicDataSourceJdbcProvider.java

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
package com.cloud.kicc.common.datasource.dynamic;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.cloud.kicc.common.datasource.support.DynamicDataSourceConstant;
import com.cloud.kicc.common.datasource.util.ConnUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
/**
*<p>
* 动态数据源初始加载
*</p>
*
* @Author: wangxiang4
* @Since: 2023/7/3
*/
public class DynamicDataSourceJdbcProvider extends AbstractJdbcDataSourceProvider {
private final String driverClassName;
private final String url;
private final String username;
private final String password;
private final DynamicDataSourceProperties dynamicDataSourceProperties;
public DynamicDataSourceJdbcProvider(DynamicDataSourceProperties dynamicDataSourceProperties,
String driverClassName,
String url,
String username,
String password) {
super(driverClassName, url, username, password);
this.dynamicDataSourceProperties = dynamicDataSourceProperties;
this.driverClassName = driverClassName;
this.url = url;
this.username = username;
this.password = password;
}
@Override
protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
// 构建数据源集合
Map<String, DataSourceProperty> map = new HashMap<>(16);
// 构建主数据源
DataSourceProperty masterProperty = new DataSourceProperty();
masterProperty.setDriverClassName(driverClassName);
masterProperty.setUrl(url);
masterProperty.setUsername(username);
masterProperty.setPassword(password);
map.put(dynamicDataSourceProperties.getPrimary(), masterProperty);
// 构建yml数据源
Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
if (!datasource.isEmpty()) {
datasource.remove(dynamicDataSourceProperties.getPrimary());
map.putAll(datasource);
}
// 构建动态数据源
ResultSet rs = statement.executeQuery(DynamicDataSourceConstant.DYNAMIC_DATASOURCE_GROUP_STATEMENT);
while (rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String driver = rs.getString("driverClass");
String url = rs.getString("url");
String username = rs.getString("username");
String password = rs.getString("password");
try {
if (StrUtil.isAllNotBlank(id, driver, url, username, password)) {
// 测试链接是否生效
Boolean result = ConnUtil.dbTest(driver, url, username, password);
if (result) {
DataSourceProperty jdbcProperty = new DataSourceProperty();
// 设置SQL链接
jdbcProperty.setDriverClassName(driver);
jdbcProperty.setUrl(url);
jdbcProperty.setUsername(username);
jdbcProperty.setPassword(password);
map.put(name, jdbcProperty);
}
}
} catch (Exception e) {
System.err.printf("数据源:"+ name + "初始化失败!");
}
}
return map;
}
}

48
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/support/DynamicDataSourceConstant.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package com.cloud.kicc.common.datasource.support;
/**
*<p>
* 数据源常量
*</p>
*
* @Author: wangxiang4
* @Since: 2023/7/3
*/
public interface DynamicDataSourceConstant {
/**
* 数据源查询基础
*/
String DYNAMIC_DATASOURCE_BASE_STATEMENT = "SELECT id, name, driver_class as driverClass, url, username, password FROM sys_datasource";
/**
* 数据源查询SQL
*/
String DYNAMIC_DATASOURCE_SINGLE_STATEMENT = DYNAMIC_DATASOURCE_BASE_STATEMENT + " WHERE del_flag = 0 AND id = ?";
/**
* 数据源查询SQL
*/
String DYNAMIC_DATASOURCE_GROUP_STATEMENT = DYNAMIC_DATASOURCE_BASE_STATEMENT + " WHERE del_flag = 0";
/**
* 数据源错误提示
*/
String DYNAMIC_DATASOURCE_NOT_FOUND = "数据源信息有误,数据加载失败";
/**
* oracle驱动类
*/
String ORACLE_DRIVER_CLASS = "oracle.jdbc.OracleDriver";
/**
* oracle校验
*/
String ORACLE_VALIDATE_STATEMENT = "select 1 from dual";
/**
* 通用校验
*/
String COMMON_VALIDATE_STATEMENT = "select 1";
}

52
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/util/ConnUtil.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package com.cloud.kicc.common.datasource.util;
import lombok.SneakyThrows;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
*<p>
* 数据库工具类
*</p>
*
* @Author: wangxiang4
* @Since: 2023/7/3
*/
public class ConnUtil {
/**
* 测试数据库链接
*/
@SneakyThrows
public static Boolean dbTest(String driverClass, String url, String username, String password) {
Connection conn = null;
try {
//测试驱动类
Class.forName(driverClass);
//创建连接
conn = DriverManager.getConnection(url, username, password);
conn.setAutoCommit(Boolean.FALSE);
return true;
} finally {
//关闭连接
dbClose(conn);
}
}
/**
* 关闭数据库链接
*/
private static void dbClose(Connection conn) {
try {
//关闭数据源连接
if (conn != null) {
conn.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

112
kicc-common-datasource/src/main/java/com/cloud/kicc/common/datasource/util/DynamicDataSourceUtil.java

@ -0,0 +1,112 @@ @@ -0,0 +1,112 @@
package com.cloud.kicc.common.datasource.util;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.cloud.kicc.common.datasource.dynamic.DynamicDataSource;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.LRUMap;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
*<p>
* 动态数据源核心处理工具
*</p>
*
* @Author: wangxiang4
* @Since: 2023/7/3
*/
@Setter
@Slf4j
@Component
public class DynamicDataSourceUtil {
public static DynamicRoutingDataSource dynamicRoutingDataSource;
public static DynamicDataSourceProperties dynamicDataSourceProperties;
private static DefaultDataSourceCreator defaultDataSourceCreator;
private static int MAX_DATASOURCE_COUNT = 300;
// 最多保存三百个数据源,按使用率淘汰
private static final LRUMap<String, DynamicDataSource> linksProperties = new LRUMap(MAX_DATASOURCE_COUNT);
public DynamicDataSourceUtil(DataSource dynamicRoutingDataSource,
DynamicDataSourceProperties dynamicDataSourceProperties,
DefaultDataSourceCreator defaultDataSourceCreator) {
DynamicDataSourceUtil.dynamicRoutingDataSource = (DynamicRoutingDataSource) dynamicRoutingDataSource;
DynamicDataSourceUtil.dynamicDataSourceProperties = dynamicDataSourceProperties;
DynamicDataSourceUtil.defaultDataSourceCreator = defaultDataSourceCreator;
}
/**
* 创建并切换至远程数据源
* @param dynamicDataSource 切换数据源
*/
public static void switchToDataSource(DynamicDataSource dynamicDataSource) {
String dbKey = dynamicDataSource.getId();
String removeKey = null;
boolean insert = true;
if (dynamicRoutingDataSource.getDataSources().containsKey(dynamicDataSource.getId())) {
synchronized (linksProperties) {
if (linksProperties.get(dbKey).equals(dynamicDataSource)) {
insert = false;
}
}
}
if (insert) {
// 创建数据源配置
DataSourceProperty dataSourceProperty = new DataSourceProperty();
// 拷贝数据源配置
BeanUtils.copyProperties(dynamicDataSource, dataSourceProperty);
// 创建动态数据源
DataSource dataSource = defaultDataSourceCreator.createDataSource(dataSourceProperty);
// 添加最新数据源
dynamicRoutingDataSource.addDataSource(dbKey, dataSource);
synchronized (linksProperties) {
if (linksProperties.size() == MAX_DATASOURCE_COUNT) {
removeKey = linksProperties.firstKey();
}
linksProperties.put(dbKey, dynamicDataSource);
}
}
// 切换数据源
DynamicDataSourceContextHolder.push(dbKey);
if (removeKey != null) {
try {
dynamicRoutingDataSource.removeDataSource(removeKey);
} catch (Exception e) {
log.error("移除数据源失败:{}", e.getMessage());
}
}
}
/**
* 移除当前设置的远程数据源清除上次清除之后切换的所有数据源
* 需要先调用 switchToDataSource切换数据源
*/
public static void clearSwitchDataSource() {
DynamicDataSourceContextHolder.poll();
}
/**
* 获取当前数据源的数据链接(切库后的)
* 用完之后一定要关闭
* @return
* @throws SQLException
*/
public static Connection getCurrentConnection() throws SQLException {
return dynamicRoutingDataSource.getConnection();
}
public static boolean containsLink(String key) {
return linksProperties.containsKey(key);
}
}

2
kicc-common-datasource/src/main/resources/META-INF/spring.factories

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cloud.kicc.common.datasource.util.DynamicDataSourceUtil

53
kicc-common-feign/pom.xml

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
<?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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-feign</artifactId>
<packaging>jar</packaging>
<description>feign-sentinel服务降级熔断、限流组件</description>
<!--考虑这个作为一个单模块使用,目前依赖了工具类核心包,后续引入依赖需要注意低耦合-->
<dependencies>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- okhttp 扩展 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!-- LB 扩展 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--caffeine 替换LB 默认缓存实现-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!--oauth server 依赖-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>

49
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/KiccFeignAutoConfiguration.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
package com.cloud.kicc.common.feign;
import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.cloud.kicc.common.feign.sentinel.ext.KiccSentinelFeign;
import com.cloud.kicc.common.feign.sentinel.handle.KiccUrlBlockHandler;
import com.cloud.kicc.common.feign.sentinel.parser.KiccHeaderRequestOriginParser;
import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
*<p>
* sentinel 配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class KiccFeignAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return KiccSentinelFeign.builder();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new KiccUrlBlockHandler();
}
@Bean
@ConditionalOnMissingBean
public RequestOriginParser requestOriginParser() {
return new KiccHeaderRequestOriginParser();
}
}

72
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/annotation/EnableKiccFeignClients.java

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
package com.cloud.kicc.common.feign.annotation;
import com.cloud.kicc.common.feign.config.FeignErrorDecoder;
import com.cloud.kicc.common.feign.config.KiccFeignClientConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.cloud.openfeign.KiccFeignClientsRegistrar;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*<p>
* 扩展Feign请求接口支持自动熔断降级
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
@Import({ KiccFeignClientsRegistrar.class, KiccFeignClientConfiguration.class })
public @interface EnableKiccFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the array of 'basePackages'.
*/
String[] basePackages() default { "com.cloud.kicc" };
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] defaultConfiguration() default { FeignErrorDecoder.class };
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath
* scanning.
* @return
*/
Class<?>[] clients() default {};
}

50
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/FeignErrorDecoder.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
package com.cloud.kicc.common.feign.config;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.cloud.kicc.common.core.api.R;
import feign.FeignException;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
*<p>
* 自定义feign错误响应数据
*</p>
*
* @Author: wangxiang4
* @Date: 2022/5/16
*/
@Slf4j
@Configuration
public class FeignErrorDecoder extends ErrorDecoder.Default {
@Override
@SneakyThrows
public Exception decode(String methodKey, Response response) {
Exception exception = super.decode(methodKey, response);
// 如果是RetryableException,则返回继续重试
if (exception instanceof RetryableException) {
return exception;
}
try {
// 如果是FeignException,则对其进行处理,并抛出Exception
if (exception instanceof FeignException && ((FeignException) exception).responseBody().isPresent()) {
ByteBuffer responseBody = ((FeignException) exception).responseBody().get();
String bodyText = StandardCharsets.UTF_8.newDecoder().decode(responseBody.asReadOnlyBuffer()).toString();
return new Exception(JSONUtil.isJson(bodyText) ? JSONUtil.toBean(bodyText, R.class).getMsg() : bodyText);
}
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
return exception;
}
}

26
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/KiccFeignClientConfiguration.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package com.cloud.kicc.common.feign.config;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
/**
*<p>
* 拦截器传递 header 中oauth token,使用hystrix 的信号量模式
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@ConditionalOnProperty("security.oauth2.client.client-id")
public class KiccFeignClientConfiguration {
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource, AccessTokenContextRelay accessTokenContextRelay) {
return new KiccFeignClientInterceptor(oAuth2ClientContext, resource, accessTokenContextRelay);
}
}

61
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/config/KiccFeignClientInterceptor.java

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
package com.cloud.kicc.common.feign.config;
import cn.hutool.core.collection.CollUtil;
import com.cloud.kicc.common.core.constant.SecurityConstants;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import java.util.Collection;
/**
*<p>
* 扩展OAuth2FeignRequestInterceptor
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Slf4j
public class KiccFeignClientInterceptor extends OAuth2FeignRequestInterceptor {
private final OAuth2ClientContext oAuth2ClientContext;
private final AccessTokenContextRelay accessTokenContextRelay;
/**
* 在授权标头中使用提供的 OAuth2ClientContext Bearer 令牌的默认构造函数
* @param oAuth2ClientContext provided context
* @param resource type of resource to be accessed
* @param accessTokenContextRelay
*/
public KiccFeignClientInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource,
AccessTokenContextRelay accessTokenContextRelay) {
super(oAuth2ClientContext, resource);
this.oAuth2ClientContext = oAuth2ClientContext;
this.accessTokenContextRelay = accessTokenContextRelay;
}
/**
* 使用提供的名称和提取的提取物的标题创建模板
* 1. 如果使用 非web 请求header 区别
* 2. 根据authentication 还原请求token
* @param template
*/
@Override
public void apply(RequestTemplate template) {
Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
return;
}
// 进行token中转,传递token
accessTokenContextRelay.copyToken();
if (oAuth2ClientContext != null && oAuth2ClientContext.getAccessToken() != null) {
super.apply(template);
}
}
}

52
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/SentinelAutoConfiguration.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package com.cloud.kicc.common.feign.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.cloud.kicc.common.feign.sentinel.ext.KiccSentinelFeign;
import com.cloud.kicc.common.feign.sentinel.ext.KiccSentinelFilterConfiguration;
import com.cloud.kicc.common.feign.sentinel.handle.KiccUrlBlockHandler;
import com.cloud.kicc.common.feign.sentinel.parser.KiccHeaderRequestOriginParser;
import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
/**
*<p>
* sentinel 配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Configuration(proxyBeanMethods = false)
@Import(KiccSentinelFilterConfiguration.class)
@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
public class SentinelAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return KiccSentinelFeign.builder();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new KiccUrlBlockHandler();
}
@Bean
@ConditionalOnMissingBean
public RequestOriginParser requestOriginParser() {
return new KiccHeaderRequestOriginParser();
}
}

132
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelFeign.java

@ -0,0 +1,132 @@ @@ -0,0 +1,132 @@
package com.cloud.kicc.common.feign.sentinel.ext;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
*<p>
* 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public final class KiccSentinelFeign {
private KiccSentinelFeign() {
}
public static KiccSentinelFeign.Builder builder() {
return new KiccSentinelFeign.Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignContext feignContext;
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public KiccSentinelFeign.Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign build() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 查找 FeignClient 上的降级策略
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
Class<?> fallback = feignClient.fallback();
Class<?> fallbackFactory = feignClient.fallbackFactory();
// 查找 FeignClient 上的上下文唯一ID,也就是是注入的bean名称
String beanName = feignClient.contextId();
if (!StringUtils.hasText(beanName)) {
beanName = feignClient.name();
}
// 开始处理自动降级
Object fallbackInstance;
FallbackFactory<?> fallbackFactoryInstance;
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
return new KiccSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class);
return new KiccSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
return new KiccSentinelInvocationHandler(target, dispatch);
}
private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(String.format(
"No %s instance of type %s found for feign client %s", type, fallbackType, name));
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(String.format(
"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name));
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.build();
}
private Object getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
field.setAccessible(true);
try {
return field.get(instance);
}
catch (IllegalAccessException e) {
// ignore
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignContext.class);
}
}
}

59
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelFilterConfiguration.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
package com.cloud.kicc.common.feign.sentinel.ext;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.util.StringUtils;
import java.util.Optional;
/**
*<p>
* 避免哨兵拦截请求 spring cloud 2021 不兼容 的问题
* 重新注入Sentinel拦截核心Bean
* 会出现:The dependencies of some of the beans in the application context form a cycle 循环依赖问题
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class KiccSentinelFilterConfiguration {
@Bean
public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) {
return new SentinelWebInterceptor(sentinelWebMvcConfig);
}
@Bean
public SentinelWebMvcConfig sentinelWebMvcConfig(SentinelProperties properties,
Optional<UrlCleaner> urlCleanerOptional, Optional<BlockExceptionHandler> blockExceptionHandlerOptional,
Optional<RequestOriginParser> requestOriginParserOptional) {
SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
if (blockExceptionHandlerOptional.isPresent()) {
blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
} else {
if (StringUtils.hasText(properties.getBlockPage())) {
sentinelWebMvcConfig.setBlockExceptionHandler(
((request, response, e) -> response.sendRedirect(properties.getBlockPage())));
} else {
sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
}
}
urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
return sentinelWebMvcConfig;
}
}

178
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/ext/KiccSentinelInvocationHandler.java

@ -0,0 +1,178 @@ @@ -0,0 +1,178 @@
package com.cloud.kicc.common.feign.sentinel.ext;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.cloud.kicc.common.core.api.R;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
*<p>
* 支持自动降级注入 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler}
* 重新写入一些提示降级信息
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Slf4j
public class KiccSentinelInvocationHandler implements InvocationHandler {
public static final String EQUALS = "equals";
public static final String HASH_CODE = "hashCode";
public static final String TO_STRING = "toString";
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory<?> fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
KiccSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
KiccSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (EQUALS.equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
}
catch (IllegalArgumentException e) {
return false;
}
}
else if (HASH_CODE.equals(method.getName())) {
return hashCode();
}
else if (TO_STRING.equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget<?> hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
}
else {
String resourceName = methodMetadata.template().method().toUpperCase() + ':' + hardCodedTarget.url()
+ methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
return fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args);
}
catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
}
else {
// 若是R类型 执行自动降级返回R
if (R.class == method.getReturnType()) {
log.error("feign 服务间调用异常", ex);
return R.error(ex.getLocalizedMessage());
}
else {
throw ex;
}
}
}
finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
}
else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SentinelInvocationHandler) {
KiccSentinelInvocationHandler other = (KiccSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

122
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/handle/GlobalBizExceptionHandler.java

@ -0,0 +1,122 @@ @@ -0,0 +1,122 @@
package com.cloud.kicc.common.feign.sentinel.handle;
import com.alibaba.csp.sentinel.Tracer;
import com.cloud.kicc.common.core.api.R;
import com.cloud.kicc.common.core.exception.CheckedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
*<p>
* 全局异常处理器结合sentinel 全局异常处理器不能作用在 oauth server
* 因为授权那边有自己的异常处理不能覆盖授权异常处理
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/18
*/
@Slf4j
@RestControllerAdvice
@ConditionalOnExpression("!'${security.oauth2.client.clientId}'.isEmpty()")
public class GlobalBizExceptionHandler {
/**
* 全局异常.
* @param e the e
* @return R
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R handleGlobalException(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
// 业务异常交由 sentinel 记录
Tracer.trace(e);
return R.error(e.getLocalizedMessage());
}
/**
* 通用前端错误提示自定义全局异常
* @param e the e
* @return R
*/
@ExceptionHandler(CheckedException.class)
@ResponseStatus(HttpStatus.NETWORK_AUTHENTICATION_REQUIRED)
public R handleGlobalCommonException(CheckedException e) {
log.error("自定义异常信息 ex={}", e.getMessage(), e);
// 业务异常交由 sentinel 记录
Tracer.trace(e);
return R.error(e.getLocalizedMessage());
}
/**
* 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link org.springframework.util.Assert}抛出
* @see Assert#hasLength(String, String)
* @see Assert#hasText(String, String)
* @see Assert#isTrue(boolean, String)
* @see Assert#isNull(Object, String)
* @see Assert#notNull(Object, String)
* @param exception 参数校验异常
* @return API返回结果对象包装后的错误输出结果
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.OK)
public R handleIllegalArgumentException(IllegalArgumentException exception) {
log.error("非法参数,ex = {}", exception.getMessage(), exception);
return R.error(exception.getMessage());
}
/**
* AccessDeniedException
* @param e the e
* @return R
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public R handleAccessDeniedException(AccessDeniedException e) {
String msg = SpringSecurityMessageSource.getAccessor().getMessage("AbstractAccessDecisionManager.accessDenied",
e.getMessage());
log.warn("拒绝授权异常信息 ex={}", msg);
return R.error(e.getLocalizedMessage());
}
/**
* validation Exception
* @param exception
* @return R
*/
@ExceptionHandler({ MethodArgumentNotValidException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleBodyValidException(MethodArgumentNotValidException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage());
return R.error(fieldErrors.get(0).getDefaultMessage());
}
/**
* validation Exception (以form-data形式传参)
* @param exception
* @return R
*/
@ExceptionHandler({ BindException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R bindExceptionHandler(BindException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage());
return R.error(fieldErrors.get(0).getDefaultMessage());
}
}

34
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/handle/KiccUrlBlockHandler.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
package com.cloud.kicc.common.feign.sentinel.handle;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.cloud.kicc.common.core.api.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*<p>
* sentinel统一降级限流返回处理
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Slf4j
public class KiccUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
log.error("sentinel 降级 资源名称{}", e.getRule().getResource(), e);
response.setContentType(ContentType.JSON.toString());
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().print(JSONUtil.toJsonStr(R.error(e.getMessage())));
}
}

34
kicc-common-feign/src/main/java/com/cloud/kicc/common/feign/sentinel/parser/KiccHeaderRequestOriginParser.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
package com.cloud.kicc.common.feign.sentinel.parser;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import javax.servlet.http.HttpServletRequest;
/**
*<p>
* sentinel 请求头解析判断
* 配置Sentinel授权规则,根据什么条件进行判断
* 白名单/黑名单
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class KiccHeaderRequestOriginParser implements RequestOriginParser {
/**
* 请求头获取allow
*/
private static final String ALLOW = "Allow";
/**
* Parse the origin from given HTTP request.
* @param request HTTP request
* @return parsed origin
*/
@Override
public String parseOrigin(HttpServletRequest request) {
return request.getHeader(ALLOW);
}
}

223
kicc-common-feign/src/main/java/org/springframework/cloud/openfeign/KiccFeignClientsRegistrar.java

@ -0,0 +1,223 @@ @@ -0,0 +1,223 @@
package org.springframework.cloud.openfeign;
import com.cloud.kicc.common.feign.KiccFeignAutoConfiguration;
import lombok.Getter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
/**
*<p>
* feign 自动配置功能内置基于mica
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class KiccFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {
@Getter
private ClassLoader beanClassLoader;
@Getter
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerFeignClients(registry);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
private void registerFeignClients(BeanDefinitionRegistry registry) {
List<String> feignClients = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 如果 spring.factories 里为空
if (feignClients.isEmpty()) {
return;
}
for (String className : feignClients) {
try {
Class<?> clazz = beanClassLoader.loadClass(className);
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz, FeignClient.class);
if (attributes == null) {
continue;
}
// 如果已经存在该 bean,支持原生的 Feign
if (registry.containsBeanDefinition(className)) {
continue;
}
registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
validate(attributes);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
// 兼容最新版本的 spring-cloud-openfeign,尚未发布
StringBuilder aliasBuilder = new StringBuilder(18);
if (attributes.containsKey("contextId")) {
String contextId = getContextId(attributes);
aliasBuilder.append(contextId);
definition.addPropertyValue("contextId", contextId);
}
else {
aliasBuilder.append(name);
}
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// alias
String alias = aliasBuilder.append("FeignClient").toString();
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
private Class<?> getSpringFactoriesLoaderFactoryClass() {
return KiccFeignAutoConfiguration.class;
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
private String getName(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return FeignClientsRegistrar.getName(name);
}
private String getContextId(Map<String, Object> attributes) {
String contextId = (String) attributes.get("contextId");
if (!StringUtils.hasText(contextId)) {
return getName(attributes);
}
contextId = resolve(contextId);
return FeignClientsRegistrar.getName(contextId);
}
private String resolve(String value) {
if (StringUtils.hasText(value)) {
return this.environment.resolvePlaceholders(value);
}
return value;
}
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
return FeignClientsRegistrar.getUrl(url);
}
private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path"));
return FeignClientsRegistrar.getPath(path);
}
@Nullable
private String getQualifier(@Nullable Map<String, Object> client) {
if (client == null) {
return null;
}
String qualifier = (String) client.get("qualifier");
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
@Nullable
private String getClientName(@Nullable Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException(
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

4
kicc-common-feign/src/main/resources/META-INF/spring.factories

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cloud.kicc.common.feign.KiccFeignAutoConfiguration,\
com.cloud.kicc.common.feign.sentinel.SentinelAutoConfiguration,\
com.cloud.kicc.common.feign.sentinel.handle.GlobalBizExceptionHandler

30
kicc-common-job/pom.xml

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-job</artifactId>
<packaging>jar</packaging>
<description>kicc 定时任务,基于xxl-job</description>
<!--考虑这个作为一个单模块使用,后续引入依赖需要注意低耦合-->
<dependencies>
<!--xxl job-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job.version}</version>
</dependency>
<!--提供服务发现能力-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies>
</project>

72
kicc-common-job/src/main/java/com/cloud/kicc/common/job/XxlJobAutoConfiguration.java

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
package com.cloud.kicc.common.job;
import com.cloud.kicc.common.job.properties.XxlExecutorProperties;
import com.cloud.kicc.common.job.properties.XxlJobProperties;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import java.util.stream.Collectors;
/**
*<p>
* xxl-job自动装配
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobAutoConfiguration {
/**
* 服务名称 包含 XXL_JOB_ADMIN 则说明是 Admin
*/
private static final String XXL_JOB_ADMIN = "xxl-job-admin";
/**
* 配置xxl-job 执行器提供自动发现 xxl-job-admin 能力
* @param xxlJobProperties xxl 配置
* @param environment 环境变量
* @param discoveryClient 注册发现客户端
* @return
*/
@Bean
public XxlJobSpringExecutor xxlJobSpringExecutor(XxlJobProperties xxlJobProperties, Environment environment,
DiscoveryClient discoveryClient) {
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
XxlExecutorProperties executor = xxlJobProperties.getExecutor();
// 应用名默认为服务名
String appName = executor.getAppname();
if (!StringUtils.hasText(appName)) {
appName = environment.getProperty("spring.application.name");
}
xxlJobSpringExecutor.setAppname(appName);
xxlJobSpringExecutor.setAddress(executor.getAddress());
xxlJobSpringExecutor.setIp(executor.getIp());
xxlJobSpringExecutor.setPort(executor.getPort());
xxlJobSpringExecutor.setAccessToken(executor.getAccessToken());
xxlJobSpringExecutor.setLogPath(executor.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
// 如果配置为空则获取注册中心的服务列表 "http://127.0.0.1:8057/xxl-job-admin"
if (!StringUtils.hasText(xxlJobProperties.getAdmin().getAddresses())) {
String serverList = discoveryClient.getServices().stream().filter(s -> s.contains(XXL_JOB_ADMIN))
.flatMap(s -> discoveryClient.getInstances(s).stream()).map(instance -> String
.format("http://%s:%s/%s", instance.getHost(), instance.getPort(), XXL_JOB_ADMIN))
.collect(Collectors.joining(","));
xxlJobSpringExecutor.setAdminAddresses(serverList);
}
else {
xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
}
return xxlJobSpringExecutor;
}
}

23
kicc-common-job/src/main/java/com/cloud/kicc/common/job/annotation/EnableKiccXxlJob.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package com.cloud.kicc.common.job.annotation;
import com.cloud.kicc.common.job.XxlJobAutoConfiguration;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
*<p>
* 激活xxl-job配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ XxlJobAutoConfiguration.class })
public @interface EnableKiccXxlJob {
}

21
kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlAdminProperties.java

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
package com.cloud.kicc.common.job.properties;
import lombok.Data;
/**
*<p>
* xxl-job管理平台配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Data
public class XxlAdminProperties {
/**
* 调度中心部署跟地址 [选填]如调度中心集群部署存在多个地址则用逗号分隔 执行器将会使用该地址进行"执行器心跳注册""任务结果回调"为空则关闭自动注册
*/
private String addresses;
}

52
kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlExecutorProperties.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package com.cloud.kicc.common.job.properties;
import lombok.Data;
/**
*<p>
* xxl-job执行器配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Data
public class XxlExecutorProperties {
/**
* 执行器AppName [选填]执行器心跳注册分组依据为空则关闭自动注册
*/
private String appname;
/**
* 服务注册地址,优先使用该配置作为注册地址 为空时使用内嵌服务 IP:PORT 作为注册地址 从而更灵活的支持容器类型执行器动态IP和动态映射端口问题
*/
private String address;
/**
* 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP 该IP不会绑定Host仅作为通讯实用地址信息用于 "执行器注册"
* "调度中心请求并触发任务"
*/
private String ip;
/**
* 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口
*/
private Integer port = 0;
/**
* 执行器通讯TOKEN [选填]非空时启用
*/
private String accessToken;
/**
* 执行器运行日志文件存储磁盘路径 [选填] 需要对该路径拥有读写权限为空则使用默认路径
*/
private String logPath = "logs/applogs/xxl-job/jobhandler";
/**
* 执行器日志保存天数 [选填] 值大于3天时生效启用执行器Log文件定期清理功能否则不生效
*/
private Integer logRetentionDays = 30;
}

25
kicc-common-job/src/main/java/com/cloud/kicc/common/job/properties/XxlJobProperties.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package com.cloud.kicc.common.job.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
*<p>
* xxl-job配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/19
*/
@Data
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobProperties {
@NestedConfigurationProperty
private XxlAdminProperties admin = new XxlAdminProperties();
@NestedConfigurationProperty
private XxlExecutorProperties executor = new XxlExecutorProperties();
}

33
kicc-common-log/pom.xml

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-log</artifactId>
<packaging>jar</packaging>
<description>kicc 日志服务</description>
<!--考虑这个作为一个单模块使用,目前依赖了工具类核心包与system接口模块,后续引入依赖需要注意低耦合-->
<dependencies>
<!--运维监控api模块-->
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-monitor-api</artifactId>
</dependency>
<!--安全依赖获取上下文信息-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>

36
kicc-common-log/src/main/java/com/cloud/kicc/common/log/LogAutoConfiguration.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
package com.cloud.kicc.common.log;
import com.cloud.kicc.common.log.aspect.SysLogAspect;
import com.cloud.kicc.common.log.event.SysLogListener;
import com.cloud.kicc.monitor.api.feign.RemoteLogService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
/**
*<p>
* 日志自动配置
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@EnableAsync
@RequiredArgsConstructor
@ConditionalOnWebApplication
@Configuration(proxyBeanMethods = false)
public class LogAutoConfiguration {
@Bean
public SysLogListener sysLogListener(RemoteLogService remoteLogService) {
return new SysLogListener(remoteLogService);
}
@Bean
public SysLogAspect sysLogAspect() {
return new SysLogAspect();
}
}

24
kicc-common-log/src/main/java/com/cloud/kicc/common/log/annotation/SysLog.java

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
package com.cloud.kicc.common.log.annotation;
import java.lang.annotation.*;
/**
*<p>
* 操作日志注解
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 描述
* @return {String}
*/
String value();
}

52
kicc-common-log/src/main/java/com/cloud/kicc/common/log/aspect/SysLogAspect.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package com.cloud.kicc.common.log.aspect;
import com.cloud.kicc.common.core.util.SpringContextHolderUtil;
import com.cloud.kicc.common.log.annotation.SysLog;
import com.cloud.kicc.common.log.event.SysLogEvent;
import com.cloud.kicc.common.log.menus.LogTypeEnum;
import com.cloud.kicc.common.log.util.SysLogUtils;
import com.cloud.kicc.monitor.api.entity.OperLog;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
*<p>
* 操作日志使用spring event异步入库
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Aspect
@Slf4j
public class SysLogAspect {
@Around("@annotation(sysLog)")
@SneakyThrows
public Object around(ProceedingJoinPoint point, SysLog sysLog) {
Long startTime = System.currentTimeMillis();
String strClassName = point.getTarget().getClass().getName();
String strMethodName = point.getSignature().getName();
log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName);
OperLog operLog = SysLogUtils.getSysLog();
operLog.setTitle(sysLog.value());
Object obj;
try {
obj = point.proceed();
} catch (Exception e) {
operLog.setType(LogTypeEnum.ERROR.getType());
operLog.setErrorMsg(e.getMessage());
throw e;
} finally {
Long endTime = System.currentTimeMillis();
operLog.setExecuteTime((endTime - startTime) + "毫秒");
// 发送异步日志事件
SpringContextHolderUtil.publishEvent(new SysLogEvent(operLog));
}
return obj;
}
}

20
kicc-common-log/src/main/java/com/cloud/kicc/common/log/event/SysLogEvent.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package com.cloud.kicc.common.log.event;
import com.cloud.kicc.monitor.api.entity.OperLog;
import org.springframework.context.ApplicationEvent;
/**
*<p>
* 系统日志事件
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class SysLogEvent extends ApplicationEvent {
public SysLogEvent(OperLog source) {
super(source);
}
}

34
kicc-common-log/src/main/java/com/cloud/kicc/common/log/event/SysLogListener.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
package com.cloud.kicc.common.log.event;
import com.cloud.kicc.common.core.constant.SecurityConstants;
import com.cloud.kicc.monitor.api.entity.OperLog;
import com.cloud.kicc.monitor.api.feign.RemoteLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
/**
*<p>
* 异步监听日志事件
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Slf4j
@RequiredArgsConstructor
public class SysLogListener {
private final RemoteLogService remoteLogService;
@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
OperLog operLog = (OperLog) event.getSource();
remoteLogService.saveLog(operLog, SecurityConstants.FROM_IN);
}
}

33
kicc-common-log/src/main/java/com/cloud/kicc/common/log/init/ApplicationLoggerInitializer.java

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
package com.cloud.kicc.common.log.init;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
*<p>
* 通过环境变量的形式设置springboot内部logback路径
* 达到每个服务都可以动态配置的目的
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class ApplicationLoggerInitializer implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String appName = environment.getProperty("spring.application.name");
String logBase = environment.getProperty("LOGGING_PATH", "logs");
// spring boot admin 直接加载日志
System.setProperty("logging.file.name", String.format("%s/%s/debug.log", logBase, appName));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

38
kicc-common-log/src/main/java/com/cloud/kicc/common/log/menus/LogTypeEnum.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package com.cloud.kicc.common.log.menus;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
*<p>
* 日志类型
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Getter
@RequiredArgsConstructor
public enum LogTypeEnum {
/**
* 正常日志类型
*/
NORMAL("0", "正常日志"),
/**
* 错误日志类型
*/
ERROR("9", "错误日志");
/**
* 类型
*/
private final String type;
/**
* 描述
*/
private final String description;
}

95
kicc-common-log/src/main/java/com/cloud/kicc/common/log/util/SysLogUtils.java

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
package com.cloud.kicc.common.log.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import com.cloud.kicc.common.data.entity.CasUser;
import com.cloud.kicc.common.log.menus.LogTypeEnum;
import com.cloud.kicc.monitor.api.entity.OperLog;
import lombok.experimental.UtilityClass;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;
/**
*<p>
* 系统日志工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@UtilityClass
public class SysLogUtils {
public OperLog getSysLog() {
HttpServletRequest request = ((ServletRequestAttributes) Objects
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
OperLog sysLog = new OperLog();
sysLog.setOperIp(ServletUtil.getClientIP(request));
sysLog.setType(LogTypeEnum.NORMAL.getType());
sysLog.setOperAddr(ServletUtil.getClientIP(request));
sysLog.setOperUrl(URLUtil.getPath(request.getRequestURI()));
sysLog.setMethod(request.getMethod());
sysLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
sysLog.setOperParam(HttpUtil.toParams(request.getParameterMap()));
sysLog.setClientId(getClientId(request));
sysLog.setServiceId(getClientId(request));
sysLog.setOperTime(LocalDateTime.now());
if (ObjectUtil.isNotEmpty(getUser())) {
sysLog.setOperName(getUser().getUsername());
sysLog.setCreateById(getUser().getId());
sysLog.setCreateByName(getUser().getUsername());
sysLog.setUpdateById(getUser().getId());
sysLog.setUpdateByName(getUser().getUsername());
}
return sysLog;
}
/**
* 获取客户端
* @return clientId
*/
private String getClientId(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof OAuth2Authentication) {
OAuth2Authentication auth2Authentication = (OAuth2Authentication) authentication;
return auth2Authentication.getOAuth2Request().getClientId();
}
if (authentication instanceof UsernamePasswordAuthenticationToken) {
BasicAuthenticationConverter basicAuthenticationConverter = new BasicAuthenticationConverter();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = basicAuthenticationConverter
.convert(request);
if (usernamePasswordAuthenticationToken != null) {
return usernamePasswordAuthenticationToken.getName();
}
}
return null;
}
/**
* 获取用户
*/
protected CasUser getUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Optional.ofNullable(authentication).isPresent()) {
Object principal = authentication.getPrincipal();
if (principal instanceof CasUser) {
return (CasUser) principal;
}
}
return null;
}
}

4
kicc-common-log/src/main/resources/META-INF/spring.factories

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cloud.kicc.common.log.LogAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=\
com.cloud.kicc.common.log.init.ApplicationLoggerInitializer

27
kicc-common-mock/pom.xml

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-mock</artifactId>
<packaging>jar</packaging>
<description>kicc oauth 2.0 单元模拟测试工具类</description>
<dependencies>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
</dependencies>
</project>

73
kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/WithMockSecurityContextFactory.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
package com.cloud.kicc.common.mock;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.cloud.kicc.common.core.util.SpringContextHolderUtil;
import com.cloud.kicc.common.mock.annotation.WithMockOAuth2User;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
/**
*<p>
* oauth2 上下文生成处理器
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class WithMockSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2User> {
@Override
public SecurityContext createSecurityContext(WithMockOAuth2User oAuth2User) {
// 1. 请求认证中心获取token
String token = getToken(oAuth2User);
// 2. 解析认证中心返回用户
OAuth2Authentication authentication = getUser(token);
// 3. 构建 oauth2 上下文
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
// 4. 上下文保存 token
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(token);
OAuth2ClientContext clientContext = SpringContextHolderUtil.getBean(OAuth2ClientContext.class);
clientContext.setAccessToken(accessToken);
return context;
}
/**
* 请求认证中心获取token
* @param oAuth2User 账号密码
* @return String token
*/
private String getToken(WithMockOAuth2User oAuth2User) {
OAuth2ProtectedResourceDetails clientProperties = SpringContextHolderUtil.getBean(OAuth2ProtectedResourceDetails.class);
String result = HttpRequest.post(clientProperties.getAccessTokenUri())
.basicAuth(clientProperties.getClientId(), clientProperties.getClientSecret())
.form("username", oAuth2User.username())
.form("password", oAuth2User.password())
.form("grant_type", "password")
.form("scope", clientProperties.getScope())
.execute()
.body();
return JSONUtil.parseObj(result).getStr("access_token");
}
/**
* 使用token 获取用户详情
* @param token token
* @return user详细
*/
private OAuth2Authentication getUser(String token) {
RemoteTokenServices tokenServices = SpringContextHolderUtil.getBean(RemoteTokenServices.class);
return tokenServices.loadAuthentication(token);
}
}

33
kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/annotation/WithMockOAuth2User.java

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
package com.cloud.kicc.common.mock.annotation;
import com.cloud.kicc.common.core.constant.SecurityConstants;
import com.cloud.kicc.common.mock.WithMockSecurityContextFactory;
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
*<p>
* WithMockOAuth2User 注解
* 用于单元测试模拟登录用户测试接口
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockSecurityContextFactory.class)
public @interface WithMockOAuth2User {
/**
* 用户名
*/
String username() default SecurityConstants.MOCK_USERNAME;
/**
* 密码
*/
String password() default SecurityConstants.MOCK_PASSWORD;
}

31
kicc-common-mock/src/main/java/com/cloud/kicc/common/mock/kit/OAuthMockKit.java

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
package com.cloud.kicc.common.mock.kit;
import com.cloud.kicc.common.core.util.SpringContextHolderUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
/**
*<p>
* Mock 工具类
*</p>
*
* @Author: wangxiang4
* @Date: 2022/2/17
*/
public class OAuthMockKit {
/**
* mock 请求增加统一请求头
* @return RequestPostProcessor 类似于拦截器
*/
public static RequestPostProcessor token() {
return mockRequest -> {
OAuth2ClientContext clientContext = SpringContextHolderUtil.getBean(OAuth2ClientContext.class);
String token = clientContext.getAccessToken().getValue();
mockRequest.addHeader(HttpHeaders.AUTHORIZATION, String.format("Bearer: %s", token));
return mockRequest;
};
}
}

28
kicc-common-rocketmq/pom.xml

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
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.cloud</groupId>
<artifactId>kicc-tool</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>kicc-common-rocketmq</artifactId>
<packaging>jar</packaging>
<description>阿里 rocketmq 消息中间件</description>
<dependencies>
<dependency>
<groupId>com.cloud</groupId>
<artifactId>kicc-common-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
</dependencies>
</project>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save