mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 06:57:10 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99a212a047 | |||
| ae7a267c1d | |||
| 49bd289e29 | |||
| ef6621bf92 | |||
| 35e2cdc3bf | |||
| 70f8fc01c0 | |||
| 3bbd04add2 | |||
|
|
d99a6a2c2d | ||
| 62334d882c | |||
| cf5ef36af5 | |||
| 5e9a3f3e93 | |||
| 7d97026480 | |||
|
|
06f5a0f346 | ||
|
|
1ce5c023cf | ||
|
|
da4c8154bf | ||
|
|
5129fea34d | ||
|
|
335dc35e2b | ||
| 1b7f541e7d | |||
| 934a5f7b6d | |||
|
|
4e7cd51868 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
## [v2.12.0](https://github.com/continew-org/continew-starter/compare/v2.11.0...v2.12.0) (2025-05-17)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 【security/password】添加密码编码器相关常量类 ([1b7f541](https://github.com/continew-org/continew-starter/commit/1b7f541e7d133cd431a9ca097bdac46ea85073be))
|
||||
|
||||
- 【license】新增 License 模块 (Gitee#51@aiming317、dom-w、httpsjt) ([da4c815](https://github.com/continew-org/continew-starter/commit/da4c8154bf6ddae7c0d0c6719efcc36537ed5983)) ([1ce5c02](https://github.com/continew-org/continew-starter/commit/1ce5c023cf8fe78849fba9fe0f7c0fcfac09c491)) ([7d97026](https://github.com/continew-org/continew-starter/commit/7d97026480b244319fa322c854a5e2d2552cc786)) ([06f5a0f](https://github.com/continew-org/continew-starter/commit/06f5a0f34680c93efe525b8102d24622b8b20893)) ([5e9a3f3](https://github.com/continew-org/continew-starter/commit/5e9a3f3e93ab55a6bc09828e124705e23543f72e))
|
||||
- 【core】新增 JSON 格式字符串校验器 ([cf5ef36](https://github.com/continew-org/continew-starter/commit/cf5ef36af5179550e9c8cb75332497813488aee3))
|
||||
- 【extension/crud】PageQuery 和 SortQuery 完善带参构造方法 ([70f8fc0](https://github.com/continew-org/continew-starter/commit/70f8fc01c0cd5636316705f9f9c425cda3f1d736))
|
||||
- 【core】新增双斜杠 DOUBLE_SLASH 字符串常量 ([ef6621b](https://github.com/continew-org/continew-starter/commit/ef6621bf92e61def508d4c133c7c17c3c7327bf8))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【web】优化文件工具类下载文件逻辑,减少堆内存占用 (GitHub#12@BruceMaa) ([4e7cd51](https://github.com/continew-org/continew-starter/commit/4e7cd5186850b5229233a744bc06b02188849029))
|
||||
- 统一请求参数、响应参数注释 ([934a5f7](https://github.com/continew-org/continew-starter/commit/934a5f7b6d90e64ab070ff9067ff9a9b73e46f11))
|
||||
- 【log】抽取 isRecord 方法方便复用,移除已过期的 timeTtl ([49bd289](https://github.com/continew-org/continew-starter/commit/49bd289e29c6ff3b2f1553ea9bf787ade65df810))
|
||||
- 调整代码风格 null != xx => xx != null(更符合大众风格) ([ae7a267](https://github.com/continew-org/continew-starter/commit/ae7a267c1d5b4b0fafc54e08e915383b49e47b01))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【cache/redisson】兼容redis没配置密码时出现redisson实例化失败的问题 (Gitee#54@muxuanya) ([335dc35](https://github.com/continew-org/continew-starter/commit/335dc35e2be11f7ebfa45e5334eb365f4ee229dc))
|
||||
- 【idempotent】修复幂等处理切面,未设置超时时间的问题 (Gitee#56@sheng_chao_111) ([5129fea](https://github.com/continew-org/continew-starter/commit/5129fea34dfedea9d5d6df50dd4e65e7bec7e651))
|
||||
- 【file/excel】修复导出时无法正确捕捉异常的问题 (Gitee#59@chengangi) ([d99a6a2](https://github.com/continew-org/continew-starter/commit/d99a6a2c2d7b8e513ad8cbb8b7deef6a6d4be04a))
|
||||
- 【web】修复默认 Response 类 msg 传递污染的问题 ([3bbd04a](https://github.com/continew-org/continew-starter/commit/3bbd04add2946a9619e8edbd94cb9bbb23c688a8))
|
||||
- 【web】修复 /file/ 注册资源映射时被解析为 /file//** 的问题 ([35e2cdc](https://github.com/continew-org/continew-starter/commit/35e2cdc3bf2c2137f86e72612b3572be148b5823))
|
||||
|
||||
## [v2.11.0](https://github.com/continew-org/continew-starter/compare/v2.10.0...v2.11.0) (2025-04-13)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
@@ -87,17 +87,17 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
.version(projectProperties.getVersion())
|
||||
.description(projectProperties.getDescription());
|
||||
ProjectProperties.Contact contact = projectProperties.getContact();
|
||||
if (null != contact) {
|
||||
if (contact != null) {
|
||||
info.contact(new Contact().name(contact.getName()).email(contact.getEmail()).url(contact.getUrl()));
|
||||
}
|
||||
ProjectProperties.License license = projectProperties.getLicense();
|
||||
if (null != license) {
|
||||
if (license != null) {
|
||||
info.license(new License().name(license.getName()).url(license.getUrl()));
|
||||
}
|
||||
OpenAPI openApi = new OpenAPI();
|
||||
openApi.info(info);
|
||||
Components components = properties.getComponents();
|
||||
if (null != components) {
|
||||
if (components != null) {
|
||||
openApi.components(components);
|
||||
// 鉴权配置
|
||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||
@@ -118,11 +118,11 @@ public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
@ConditionalOnMissingBean
|
||||
public GlobalOpenApiCustomizer globalOpenApiCustomizer(SpringDocExtensionProperties properties) {
|
||||
return openApi -> {
|
||||
if (null != openApi.getPaths()) {
|
||||
if (openApi.getPaths() != null) {
|
||||
openApi.getPaths().forEach((s, pathItem) -> {
|
||||
// 为所有接口添加鉴权
|
||||
Components components = properties.getComponents();
|
||||
if (null != components && MapUtil.isNotEmpty(components.getSecuritySchemes())) {
|
||||
if (components != null && MapUtil.isNotEmpty(components.getSecuritySchemes())) {
|
||||
Map<String, SecurityScheme> securitySchemeMap = components.getSecuritySchemes();
|
||||
pathItem.readOperations().forEach(operation -> {
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
|
||||
@@ -11,17 +11,234 @@
|
||||
<description>ContiNew Starter BOM</description>
|
||||
|
||||
<properties>
|
||||
<revision>2.11.0</revision>
|
||||
<revision>2.12.0</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- 扩展模块 - CRUD - 核心模块 -->
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-crud-core</artifactId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON 模块 - Jackson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-json-jackson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - SaToken -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-auth-satoken</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 认证模块 - JustAuth -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-auth-justauth</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 数据访问模块 - MyBatis Flex -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mf</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 缓存模块 - Spring Cache -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-springcache</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 缓存模块 - JetCache -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-jetcache</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 密码编码器 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-password</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 安全模块 - 加密 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-crypto</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 安全模块 - 脱敏 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-mask</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 安全模块 - XSS 过滤 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-xss</artifactId>
|
||||
</dependency>
|
||||
<!-- 安全模块 - 敏感词 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-sensitivewords</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 限流模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-ratelimiter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 幂等模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-idempotent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 链路追踪模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-trace</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 图形验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-captcha-graphic</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 行为验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-captcha-behavior</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息模块 - 邮件 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-messaging-mail</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息模块 - WebSocket -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-messaging-websocket</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 基于拦截器实现(Spring Boot Actuator HttpTrace 增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 日志模块 - 基于 AOP 实现 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-aop</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 日志模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件处理模块 - Excel -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 存储模块 - 本地存储 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-local</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 存储模块 - 对象存储 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-oss</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 存储模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- License 模块 - 生成器 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license-generator</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- License 模块 - 校验器 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license-verifier</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- License 模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - CRUD - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
@@ -34,246 +251,36 @@
|
||||
<artifactId>continew-starter-extension-crud-mf</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 数据权限 - 核心模块 -->
|
||||
<!-- 扩展模块 - CRUD - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
<artifactId>continew-starter-extension-crud-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 数据权限 - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-datapermission-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 多租户 - 核心模块 -->
|
||||
<!-- 扩展模块 - 数据权限 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
<artifactId>continew-starter-extension-datapermission-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展模块 - 多租户 - MyBatis Plus ORM 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-extension-tenant-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - JustAuth -->
|
||||
<!-- 扩展模块 - 多租户 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-auth-justauth</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 认证模块 - SaToken -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-auth-satoken</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mp</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - MyBatis Flex -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-mf</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据访问模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-data-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - JetCache -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-jetcache</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Spring Cache -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-springcache</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息模块 - WebSocket -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-messaging-websocket</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息模块 - 邮件 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-messaging-mail</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 行为验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-captcha-behavior</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 验证码模块 - 图形验证码 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-captcha-graphic</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件处理模块 - Excel -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-file-excel</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储模块 - 本地存储 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-local</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储模块 - 对象存储 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-storage-oss</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 基于拦截器实现(Spring Boot Actuator HttpTrace 增强版) -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-interceptor</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 基于 AOP 实现 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-aop</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-log-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 链路追踪模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-trace</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 幂等模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-idempotent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 限流模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-ratelimiter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - XSS 过滤 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-xss</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 敏感词 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-sensitivewords</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 加密 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-crypto</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 脱敏 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-mask</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 密码编码器 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-password</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-api-doc</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON 模块 - Jackson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-json-jackson</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
<artifactId>continew-starter-extension-tenant-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>ContiNew Starter 缓存模块 - JetCache</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- JetCache(一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作) -->
|
||||
<!-- JetCache(基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作) -->
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
<artifactId>jetcache-autoconfigure</artifactId>
|
||||
|
||||
@@ -93,7 +93,7 @@ public class RedissonAutoConfiguration {
|
||||
private void buildClusterModeConfig(Config config, String protocolPrefix) {
|
||||
ClusterServersConfig clusterServersConfig = config.useClusterServers();
|
||||
ClusterServersConfig customClusterServersConfig = properties.getClusterServersConfig();
|
||||
if (null != customClusterServersConfig) {
|
||||
if (customClusterServersConfig != null) {
|
||||
BeanUtil.copyProperties(customClusterServersConfig, clusterServersConfig);
|
||||
clusterServersConfig.setNodeAddresses(customClusterServersConfig.getNodeAddresses());
|
||||
}
|
||||
@@ -102,8 +102,10 @@ public class RedissonAutoConfiguration {
|
||||
List<String> nodeList = redisProperties.getCluster().getNodes();
|
||||
nodeList.stream().map(node -> protocolPrefix + node).forEach(clusterServersConfig::addNodeAddress);
|
||||
}
|
||||
// 兼容 Redis 没配置密码的情况
|
||||
if (CharSequenceUtil.isBlank(clusterServersConfig.getPassword())) {
|
||||
clusterServersConfig.setPassword(redisProperties.getPassword());
|
||||
String password = redisProperties.getPassword();
|
||||
clusterServersConfig.setPassword(CharSequenceUtil.isNotBlank(password) ? password : null);
|
||||
}
|
||||
// Key 前缀
|
||||
if (CharSequenceUtil.isNotBlank(properties.getKeyPrefix())) {
|
||||
@@ -120,7 +122,7 @@ public class RedissonAutoConfiguration {
|
||||
private void buildSentinelModeConfig(Config config, String protocolPrefix) {
|
||||
SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
|
||||
SentinelServersConfig customSentinelServersConfig = properties.getSentinelServersConfig();
|
||||
if (null != customSentinelServersConfig) {
|
||||
if (customSentinelServersConfig != null) {
|
||||
BeanUtil.copyProperties(customSentinelServersConfig, sentinelServersConfig);
|
||||
sentinelServersConfig.setSentinelAddresses(customSentinelServersConfig.getSentinelAddresses());
|
||||
}
|
||||
@@ -129,8 +131,10 @@ public class RedissonAutoConfiguration {
|
||||
List<String> nodeList = redisProperties.getSentinel().getNodes();
|
||||
nodeList.stream().map(node -> protocolPrefix + node).forEach(sentinelServersConfig::addSentinelAddress);
|
||||
}
|
||||
// 兼容 Redis 没配置密码的情况
|
||||
if (CharSequenceUtil.isBlank(sentinelServersConfig.getPassword())) {
|
||||
sentinelServersConfig.setPassword(redisProperties.getPassword());
|
||||
String password = redisProperties.getPassword();
|
||||
sentinelServersConfig.setPassword(CharSequenceUtil.isNotBlank(password) ? password : null);
|
||||
}
|
||||
if (CharSequenceUtil.isBlank(sentinelServersConfig.getMasterName())) {
|
||||
sentinelServersConfig.setMasterName(redisProperties.getSentinel().getMaster());
|
||||
@@ -150,13 +154,15 @@ public class RedissonAutoConfiguration {
|
||||
private void buildSingleModeConfig(Config config, String protocolPrefix) {
|
||||
SingleServerConfig singleServerConfig = config.useSingleServer();
|
||||
SingleServerConfig customSingleServerConfig = properties.getSingleServerConfig();
|
||||
if (null != customSingleServerConfig) {
|
||||
if (customSingleServerConfig != null) {
|
||||
BeanUtil.copyProperties(properties.getSingleServerConfig(), singleServerConfig);
|
||||
}
|
||||
// 下方配置如果为空,则使用 Redis 的配置
|
||||
singleServerConfig.setDatabase(redisProperties.getDatabase());
|
||||
// 兼容 Redis 没配置密码的情况
|
||||
if (CharSequenceUtil.isBlank(singleServerConfig.getPassword())) {
|
||||
singleServerConfig.setPassword(redisProperties.getPassword());
|
||||
String password = redisProperties.getPassword();
|
||||
singleServerConfig.setPassword(CharSequenceUtil.isNotBlank(password) ? password : null);
|
||||
}
|
||||
if (CharSequenceUtil.isBlank(singleServerConfig.getAddress())) {
|
||||
singleServerConfig.setAddress(protocolPrefix + redisProperties
|
||||
|
||||
@@ -72,7 +72,7 @@ public class SpringCacheAutoConfiguration implements CachingConfigurer {
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||
.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapperCopy)));
|
||||
CacheProperties.Redis redisCacheProperties = cacheProperties.getRedis();
|
||||
if (null != redisCacheProperties.getTimeToLive()) {
|
||||
if (redisCacheProperties.getTimeToLive() != null) {
|
||||
redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisCacheProperties.getTimeToLive());
|
||||
}
|
||||
if (!redisCacheProperties.isCacheNullValues()) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<artifactId>easy-captcha</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JS 引擎(一个纯编译的 JavaScript 引擎) -->
|
||||
<!-- JS 引擎(纯编译的 JavaScript 引擎) -->
|
||||
<dependency>
|
||||
<groupId>org.openjdk.nashorn</groupId>
|
||||
<artifactId>nashorn-core</artifactId>
|
||||
|
||||
@@ -34,6 +34,21 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String ENABLED = "enabled";
|
||||
|
||||
/**
|
||||
* Web 配置
|
||||
*/
|
||||
public static final String WEB = CONTINEW_STARTER + StringConstants.DOT + "web";
|
||||
|
||||
/**
|
||||
* Web-跨域配置
|
||||
*/
|
||||
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
|
||||
|
||||
/**
|
||||
* Web-响应配置
|
||||
*/
|
||||
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
|
||||
|
||||
/**
|
||||
* 安全配置
|
||||
*/
|
||||
@@ -60,29 +75,19 @@ public class PropertiesConstants {
|
||||
public static final String SECURITY_XSS = SECURITY + StringConstants.DOT + "xss";
|
||||
|
||||
/**
|
||||
* Web 配置
|
||||
* 限流配置
|
||||
*/
|
||||
public static final String WEB = CONTINEW_STARTER + StringConstants.DOT + "web";
|
||||
public static final String RATE_LIMITER = CONTINEW_STARTER + StringConstants.DOT + "rate-limiter";
|
||||
|
||||
/**
|
||||
* Web-跨域配置
|
||||
* 幂等配置
|
||||
*/
|
||||
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
|
||||
public static final String IDEMPOTENT = CONTINEW_STARTER + StringConstants.DOT + "idempotent";
|
||||
|
||||
/**
|
||||
* Web-响应配置
|
||||
* 链路追踪配置
|
||||
*/
|
||||
public static final String WEB_RESPONSE = WEB + StringConstants.DOT + "response";
|
||||
|
||||
/**
|
||||
* 日志配置
|
||||
*/
|
||||
public static final String LOG = CONTINEW_STARTER + StringConstants.DOT + "log";
|
||||
|
||||
/**
|
||||
* 存储配置
|
||||
*/
|
||||
public static final String STORAGE = CONTINEW_STARTER + StringConstants.DOT + "storage";
|
||||
public static final String TRACE = CONTINEW_STARTER + StringConstants.DOT + "trace";
|
||||
|
||||
/**
|
||||
* 验证码配置
|
||||
@@ -109,6 +114,31 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String MESSAGING_WEBSOCKET = MESSAGING + StringConstants.DOT + "websocket";
|
||||
|
||||
/**
|
||||
* 日志配置
|
||||
*/
|
||||
public static final String LOG = CONTINEW_STARTER + StringConstants.DOT + "log";
|
||||
|
||||
/**
|
||||
* 存储配置
|
||||
*/
|
||||
public static final String STORAGE = CONTINEW_STARTER + StringConstants.DOT + "storage";
|
||||
|
||||
/**
|
||||
* License 配置
|
||||
*/
|
||||
public static final String LICENSE = CONTINEW_STARTER + StringConstants.DOT + "license";
|
||||
|
||||
/**
|
||||
* License 生成器配置
|
||||
*/
|
||||
public static final String LICENSE_GENERATOR = LICENSE + StringConstants.DOT + "generator";
|
||||
|
||||
/**
|
||||
* License 校验器配置
|
||||
*/
|
||||
public static final String LICENSE_VERIFIER = LICENSE + StringConstants.DOT + "verifier";
|
||||
|
||||
/**
|
||||
* CRUD 配置
|
||||
*/
|
||||
@@ -124,21 +154,6 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String TENANT = CONTINEW_STARTER + StringConstants.DOT + "tenant";
|
||||
|
||||
/**
|
||||
* 限流配置
|
||||
*/
|
||||
public static final String RATE_LIMITER = CONTINEW_STARTER + StringConstants.DOT + "rate-limiter";
|
||||
|
||||
/**
|
||||
* 幂等配置
|
||||
*/
|
||||
public static final String IDEMPOTENT = CONTINEW_STARTER + StringConstants.DOT + "idempotent";
|
||||
|
||||
/**
|
||||
* 链路追踪配置
|
||||
*/
|
||||
public static final String TRACE = CONTINEW_STARTER + StringConstants.DOT + "trace";
|
||||
|
||||
private PropertiesConstants() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,11 @@ public class StringConstants {
|
||||
*/
|
||||
public static final String SLASH = "/";
|
||||
|
||||
/**
|
||||
* 双斜杠 {@code "//"}
|
||||
*/
|
||||
public static final String DOUBLE_SLASH = "//";
|
||||
|
||||
/**
|
||||
* 反斜杠 {@code "\\"}
|
||||
*/
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ExceptionUtils {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
if (null != throwable) {
|
||||
if (throwable != null) {
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public class ExceptionUtils {
|
||||
try {
|
||||
return exSupplier.get();
|
||||
} catch (Exception e) {
|
||||
if (null != exConsumer) {
|
||||
if (exConsumer != null) {
|
||||
exConsumer.accept(e);
|
||||
}
|
||||
return defaultValue;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class Validator {
|
||||
* @param exceptionType 异常类型
|
||||
*/
|
||||
protected static void throwIfNotNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) {
|
||||
throwIf(null != obj, message, exceptionType);
|
||||
throwIf(obj != null, message, exceptionType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +193,7 @@ public class Validator {
|
||||
protected static void throwIf(BooleanSupplier conditionSupplier,
|
||||
String message,
|
||||
Class<? extends RuntimeException> exceptionType) {
|
||||
if (null != conditionSupplier && conditionSupplier.getAsBoolean()) {
|
||||
if (conditionSupplier != null && conditionSupplier.getAsBoolean()) {
|
||||
throw ReflectUtil.newInstance(exceptionType, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 枚举校验注解校验器
|
||||
* 枚举校验器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author Jasmine
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.core.validation.constraints;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
/**
|
||||
* JSON 格式字符串校验注解
|
||||
*
|
||||
* <p>
|
||||
* 校验字符串是否为 JSON 格式字符串
|
||||
* {@code @JsonString(message = "必须为有效的 JSON 格式")} <br />
|
||||
* </p>
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.12.0
|
||||
*/
|
||||
@Documented
|
||||
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = JsonStringValidator.class)
|
||||
public @interface JsonString {
|
||||
|
||||
/**
|
||||
* 提示消息
|
||||
*
|
||||
* @return 提示消息
|
||||
*/
|
||||
String message() default "必须为有效的 JSON 格式";
|
||||
|
||||
/**
|
||||
* 分组
|
||||
*
|
||||
* @return 分组
|
||||
*/
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
/**
|
||||
* 负载
|
||||
*
|
||||
* @return 负载
|
||||
*/
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.core.validation.constraints;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* JSON 格式字符串校验器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class JsonStringValidator implements ConstraintValidator<JsonString, String> {
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return JSONUtil.isTypeJSON(value);
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,12 @@
|
||||
|
||||
package top.continew.starter.core.validation.constraints;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.PhoneUtil;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
/**
|
||||
* 手机号校验注解校验器
|
||||
* 手机号校验器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.10.0
|
||||
@@ -31,7 +30,7 @@ public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (CharSequenceUtil.isBlank(value)) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return PhoneUtil.isMobile(value);
|
||||
|
||||
@@ -106,7 +106,7 @@ public class MetaUtils {
|
||||
final DatabaseMetaData metaData = conn.getMetaData();
|
||||
try (final ResultSet rs = metaData.getTables(catalog, schema, tableName, Convert
|
||||
.toStrArray(TableType.TABLE))) {
|
||||
if (null != rs) {
|
||||
if (rs != null) {
|
||||
String name;
|
||||
while (rs.next()) {
|
||||
name = rs.getString("TABLE_NAME");
|
||||
|
||||
@@ -143,7 +143,7 @@ public class QueryWrapperHelper {
|
||||
}
|
||||
// 设置了 @QueryIgnore 注解,直接忽略
|
||||
QueryIgnore queryIgnoreAnnotation = AnnotationUtil.getAnnotation(field, QueryIgnore.class);
|
||||
if (null != queryIgnoreAnnotation) {
|
||||
if (queryIgnoreAnnotation != null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
|
||||
|
||||
@@ -84,7 +84,7 @@ public class MybatisPlusAutoConfiguration {
|
||||
}
|
||||
// 分页插件
|
||||
MyBatisPlusExtensionProperties.PaginationProperties paginationProperties = properties.getPagination();
|
||||
if (null != paginationProperties && paginationProperties.isEnabled()) {
|
||||
if (paginationProperties != null && paginationProperties.isEnabled()) {
|
||||
interceptor.addInnerInterceptor(this.paginationInnerInterceptor(paginationProperties));
|
||||
}
|
||||
// 乐观锁插件
|
||||
|
||||
@@ -154,7 +154,7 @@ public class QueryWrapperHelper {
|
||||
}
|
||||
// 设置了 @QueryIgnore 注解,直接忽略
|
||||
QueryIgnore queryIgnoreAnnotation = AnnotationUtil.getAnnotation(field, QueryIgnore.class);
|
||||
if (null != queryIgnoreAnnotation) {
|
||||
if (queryIgnoreAnnotation != null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 建议:数据库表列建议采用下划线连接法命名,程序变量建议采用驼峰法命名
|
||||
|
||||
@@ -12,16 +12,16 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.11.0</revision>
|
||||
<spring-boot.version>3.3.9</spring-boot.version>
|
||||
<revision>2.12.0</revision>
|
||||
<spring-boot.version>3.3.11</spring-boot.version>
|
||||
<spring-cloud.version>2023.0.5</spring-cloud.version>
|
||||
<redisson.version>3.45.0</redisson.version>
|
||||
<jetcache.version>2.7.7</jetcache.version>
|
||||
<cosid.version>2.11.0</cosid.version>
|
||||
<sa-token.version>1.41.0</sa-token.version>
|
||||
<redisson.version>3.46.0</redisson.version>
|
||||
<jetcache.version>2.7.8</jetcache.version>
|
||||
<cosid.version>2.12.3</cosid.version>
|
||||
<sa-token.version>1.42.0</sa-token.version>
|
||||
<just-auth.version>1.16.7</just-auth.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
<mybatis-flex.version>1.10.8</mybatis-flex.version>
|
||||
<mybatis-flex.version>1.10.9</mybatis-flex.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<snail-job.version>1.4.0</snail-job.version>
|
||||
@@ -31,19 +31,21 @@
|
||||
<nashorn.version>15.6</nashorn.version>
|
||||
<easy-excel.version>3.3.4</easy-excel.version>
|
||||
<x-file-storage.version>2.2.1</x-file-storage.version>
|
||||
<aws-s3.version>1.12.782</aws-s3.version>
|
||||
<s3.version>2.30.35</s3.version>
|
||||
<s3-crt.version>0.36.1</s3-crt.version>
|
||||
<aws-s3.version>1.12.783</aws-s3.version>
|
||||
<s3.version>2.31.35</s3.version>
|
||||
<s3-crt.version>0.38.1</s3-crt.version>
|
||||
<thumbnails.version>0.4.20</thumbnails.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
<graceful-response.version>5.0.5-boot3</graceful-response.version>
|
||||
<crane4j.version>2.9.0</crane4j.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<tlog.version>1.5.2</tlog.version>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<ttl.version>2.14.5</ttl.version>
|
||||
<ip2region.version>3.3.6</ip2region.version>
|
||||
<hutool.version>5.8.36</hutool.version>
|
||||
<hutool.version>5.8.37</hutool.version>
|
||||
<snakeyaml.version>2.4</snakeyaml.version>
|
||||
<truelicense.version>1.33</truelicense.version>
|
||||
<zip4j.version>2.11.5</zip4j.version>
|
||||
<!-- 解决部分传递依赖漏洞问题 -->
|
||||
<commons-beanutils.version>1.9.4</commons-beanutils.version>
|
||||
<commons-io.version>2.17.0</commons-io.version>
|
||||
@@ -81,7 +83,7 @@
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JetCache(一个基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作) -->
|
||||
<!-- JetCache(基于 Java 的缓存系统封装,提供统一的 API 和注解来简化缓存的使用。提供了比 SpringCache 更加强大的注解,可以原生的支持 TTL、两级缓存、分布式自动刷新,还提供了 Cache 接口用于手工缓存操作) -->
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
<artifactId>jetcache-bom</artifactId>
|
||||
@@ -93,18 +95,10 @@
|
||||
<!-- CosId(通用、灵活、高性能的分布式 ID 生成器) -->
|
||||
<dependency>
|
||||
<groupId>me.ahoo.cosid</groupId>
|
||||
<artifactId>cosid-spring-boot-starter</artifactId>
|
||||
<version>${cosid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.ahoo.cosid</groupId>
|
||||
<artifactId>cosid-spring-redis</artifactId>
|
||||
<version>${cosid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.ahoo.cosid</groupId>
|
||||
<artifactId>cosid-jdbc</artifactId>
|
||||
<artifactId>cosid-bom</artifactId>
|
||||
<version>${cosid.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token(轻量级 Java 权限认证框架,让鉴权变得简单、优雅) -->
|
||||
@@ -223,14 +217,14 @@
|
||||
<version>${easy-captcha.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JS 引擎(一个纯编译的 JavaScript 引擎) -->
|
||||
<!-- JS 引擎(纯编译的 JavaScript 引擎) -->
|
||||
<dependency>
|
||||
<groupId>org.openjdk.nashorn</groupId>
|
||||
<artifactId>nashorn-core</artifactId>
|
||||
<version>${nashorn.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Easy Excel(一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
|
||||
<!-- Easy Excel(基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
@@ -257,6 +251,12 @@
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${s3.version}</version>
|
||||
</dependency>
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
<version>${s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 使用 AWS 基于 CRT 的 S3 客户端 -->
|
||||
<dependency>
|
||||
@@ -265,13 +265,6 @@
|
||||
<version>${s3-crt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
<version>${s3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Thumbnailator(缩略图生成库) -->
|
||||
<dependency>
|
||||
<groupId>net.coobird</groupId>
|
||||
@@ -279,14 +272,14 @@
|
||||
<version>${thumbnails.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<!-- Graceful Response(Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
<version>${graceful-response.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Crane4j(一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<!-- Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
||||
@@ -316,7 +309,7 @@
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHTTP(一个默认高效的 HTTP 客户端) -->
|
||||
<!-- OkHTTP(默认高效的 HTTP 客户端) -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
@@ -351,6 +344,19 @@
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TrueLicense (开源的证书管理引擎) -->
|
||||
<dependency>
|
||||
<groupId>de.schlichtherle.truelicense</groupId>
|
||||
<artifactId>truelicense-core</artifactId>
|
||||
<version>${truelicense.version}</version>
|
||||
</dependency>
|
||||
<!-- Zip4j (开源的 Java 处理 zip 压缩文件的开发包) -->
|
||||
<dependency>
|
||||
<groupId>net.lingala.zip4j</groupId>
|
||||
<artifactId>zip4j</artifactId>
|
||||
<version>${zip4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 解决部分传递依赖漏洞问题 -->
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>ContiNew Starter 扩展模块 - CRUD(增删改查) - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Crane4j(一个基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<!-- Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) -->
|
||||
<dependency>
|
||||
<groupId>cn.crane4j</groupId>
|
||||
<artifactId>crane4j-spring-boot-starter</artifactId>
|
||||
|
||||
@@ -64,13 +64,13 @@ public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMappi
|
||||
@NonNull Class<?> handlerType,
|
||||
CrudRequestMapping crudRequestMapping) {
|
||||
RequestMappingInfo info = this.buildRequestMappingInfo(method);
|
||||
if (null != info) {
|
||||
if (info != null) {
|
||||
RequestMappingInfo typeInfo = this.buildRequestMappingInfo(handlerType);
|
||||
if (null != typeInfo) {
|
||||
if (typeInfo != null) {
|
||||
info = typeInfo.combine(info);
|
||||
}
|
||||
String prefix = crudRequestMapping.value();
|
||||
if (null != prefix) {
|
||||
if (prefix != null) {
|
||||
RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
|
||||
options.setPatternParser(PathPatternParser.defaultInstance);
|
||||
info = RequestMappingInfo.paths(prefix).options(options).build().combine(info);
|
||||
|
||||
@@ -45,7 +45,7 @@ import java.util.List;
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改参数类型
|
||||
* @param <C> 创建或修改请求参数类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@@ -117,7 +117,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
/**
|
||||
* 创建
|
||||
*
|
||||
* @param req 创建参数
|
||||
* @param req 创建请求参数
|
||||
* @return ID
|
||||
*/
|
||||
@CrudApi(Api.CREATE)
|
||||
@@ -131,7 +131,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
/**
|
||||
* 修改
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @param req 修改请求参数
|
||||
* @param id ID
|
||||
*/
|
||||
@CrudApi(Api.UPDATE)
|
||||
@@ -146,7 +146,7 @@ public abstract class AbstractBaseController<S extends BaseService<L, D, Q, C>,
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param req 删除参数
|
||||
* @param req 删除请求参数
|
||||
*/
|
||||
@CrudApi(Api.DELETE)
|
||||
@Operation(summary = "删除数据", description = "删除数据")
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -35,10 +36,12 @@ public class PageQuery extends SortQuery {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 默认页码:1
|
||||
*/
|
||||
private static final int DEFAULT_PAGE = 1;
|
||||
|
||||
/**
|
||||
* 默认每页条数:10
|
||||
*/
|
||||
@@ -58,6 +61,123 @@ public class PageQuery extends SortQuery {
|
||||
@Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})")
|
||||
private Integer size = DEFAULT_SIZE;
|
||||
|
||||
public PageQuery() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery(1, 10)}
|
||||
* </p>
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页条数
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(Integer page, Integer size) {
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery(1, 10, "createTime,desc", "name,asc")}
|
||||
* </p>
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页条数
|
||||
* @param sort 排序
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(Integer page, Integer size, String... sort) {
|
||||
super(sort);
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery("createTime,desc", "name,asc")}
|
||||
* </p>
|
||||
*
|
||||
* @param sort 排序
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(String... sort) {
|
||||
super(sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery("createTime", Sort.Direction.DESC)}
|
||||
* </p>
|
||||
*
|
||||
* @param field 字段
|
||||
* @param direction 排序方向
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(String field, Sort.Direction direction) {
|
||||
super(field, direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery(Sort.by(Sort.Direction.DESC, "createTime"))}
|
||||
* </p>
|
||||
*
|
||||
* @param sort 排序
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(Sort sort) {
|
||||
super(sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery(1, 10, "createTime", Sort.Direction.DESC)}
|
||||
* </p>
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页条数
|
||||
* @param field 字段
|
||||
* @param direction 排序方向
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(Integer page, Integer size, String field, Sort.Direction direction) {
|
||||
super(field, direction);
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new PageQuery(1, 10, Sort.by(Sort.Direction.DESC, "createTime"))}
|
||||
* </p>
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页条数
|
||||
* @param sort 排序
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public PageQuery(Integer page, Integer size, Sort sort) {
|
||||
super(sort);
|
||||
this.page = page;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Integer getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,58 @@ public class SortQuery implements Serializable {
|
||||
@Schema(description = "排序条件", example = "createTime,desc")
|
||||
private String[] sort;
|
||||
|
||||
public SortQuery() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new SortQuery("createTime,desc", "name,asc")}
|
||||
* </p>
|
||||
*
|
||||
* @param sort 排序条件
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public SortQuery(String... sort) {
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new SortQuery("createTime", Sort.Direction.DESC)}
|
||||
* </p>
|
||||
*
|
||||
* @param field 字段
|
||||
* @param direction 排序方向
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public SortQuery(String field, Sort.Direction direction) {
|
||||
this(field + StringConstants.COMMA + direction.name().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* <p>
|
||||
* 示例:{@code new SortQuery(Sort.by(Sort.Order.desc("createTime")))}
|
||||
* </p>
|
||||
*
|
||||
* @param sort 排序条件
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public SortQuery(Sort sort) {
|
||||
if (sort == null || sort.isUnsorted()) {
|
||||
this.sort = null;
|
||||
return;
|
||||
}
|
||||
this.sort = sort.stream()
|
||||
.map(order -> order.getProperty() + StringConstants.COMMA + order.getDirection().name().toLowerCase())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析排序条件为 Spring 分页排序实体
|
||||
*
|
||||
|
||||
@@ -23,13 +23,13 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页信息
|
||||
* 分页响应参数
|
||||
*
|
||||
* @param <T> 列表数据类型
|
||||
* @author Charles7c
|
||||
* @since 2.5.2
|
||||
*/
|
||||
@Schema(description = "分页信息")
|
||||
@Schema(description = "分页响应参数")
|
||||
public class BasePageResp<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* ID 响应信息
|
||||
* ID 响应参数
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.5.0
|
||||
|
||||
@@ -23,13 +23,13 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 键值对信息
|
||||
* 键值对响应参数
|
||||
*
|
||||
* @param <T>
|
||||
* @author Charles7c
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@Schema(description = "键值对信息")
|
||||
@Schema(description = "键值对响应参数")
|
||||
public class LabelValueResp<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.List;
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改参数类型
|
||||
* @param <C> 创建或修改请求参数类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@@ -90,7 +90,7 @@ public interface BaseService<L, D, Q, C> {
|
||||
/**
|
||||
* 创建
|
||||
*
|
||||
* @param req 创建参数
|
||||
* @param req 创建请求参数
|
||||
* @return 自增 ID
|
||||
*/
|
||||
Long create(C req);
|
||||
@@ -98,7 +98,7 @@ public interface BaseService<L, D, Q, C> {
|
||||
/**
|
||||
* 修改
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @param req 修改请求参数
|
||||
* @param id ID
|
||||
*/
|
||||
void update(C req, Long id);
|
||||
|
||||
@@ -59,7 +59,7 @@ import java.util.Optional;
|
||||
* @param <L> 列表类型
|
||||
* @param <D> 详情类型
|
||||
* @param <Q> 查询条件
|
||||
* @param <C> 创建或修改参数类型
|
||||
* @param <C> 创建或修改请求参数类型
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
@@ -120,7 +120,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
default -> throw new IllegalArgumentException("暂不支持 [%s] 数据权限".formatted(dataScope));
|
||||
}
|
||||
}
|
||||
return null != where ? new AndExpression(where, new ParenthesedExpressionList<>(expression)) : expression;
|
||||
return where != null ? new AndExpression(where, new ParenthesedExpressionList<>(expression)) : expression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +176,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
inExpression.setRightExpression(subSelect);
|
||||
return null != expression ? new OrExpression(expression, inExpression) : inExpression;
|
||||
return expression != null ? new OrExpression(expression, inExpression) : inExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +197,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
equalsTo.setRightExpression(new LongValue(userContext.getDeptId()));
|
||||
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +218,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
EqualsTo equalsTo = new EqualsTo();
|
||||
equalsTo.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.userId()));
|
||||
equalsTo.setRightExpression(new LongValue(userContext.getUserId()));
|
||||
return null != expression ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
return expression != null ? new OrExpression(expression, equalsTo) : equalsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +250,7 @@ public class DefaultDataPermissionHandler implements DataPermissionHandler {
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(this.buildColumn(dataPermission.tableAlias(), dataPermission.deptId()));
|
||||
inExpression.setRightExpression(subSelect);
|
||||
return null != expression ? new OrExpression(expression, inExpression) : inExpression;
|
||||
return expression != null ? new OrExpression(expression, inExpression) : inExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null != tenantId) {
|
||||
if (tenantId != null) {
|
||||
return new LongValue(tenantId);
|
||||
}
|
||||
return null;
|
||||
@@ -55,7 +55,7 @@ public class DefaultTenantLineHandler implements TenantLineHandler {
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (null != tenantId && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
||||
if (tenantId != null && tenantId.equals(tenantProperties.getSuperTenantId())) {
|
||||
return true;
|
||||
}
|
||||
if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<description>ContiNew Starter 文件处理模块 - Excel</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Easy Excel(一个基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
|
||||
<!-- Easy Excel(基于 Java 的、快速、简洁、解决大文件内存溢出的 Excel 处理工具) -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
|
||||
@@ -68,7 +68,7 @@ public class ExcelBigNumberConverter implements Converter<Long> {
|
||||
public WriteCellData<Object> convertToExcelData(Long value,
|
||||
ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
if (null != value) {
|
||||
if (value != null) {
|
||||
String str = Long.toString(value);
|
||||
if (str.length() > MAX_LENGTH) {
|
||||
return new WriteCellData<>(str);
|
||||
|
||||
@@ -89,6 +89,7 @@ public class ExcelUtils {
|
||||
.doWrite(list);
|
||||
} catch (Exception e) {
|
||||
log.error("Export excel occurred an error: {}. fileName: {}.", e.getMessage(), fileName, e);
|
||||
response.reset();
|
||||
throw new BaseException("导出 Excel 出现错误");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ public class IdempotentAspect {
|
||||
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
|
||||
String cacheKey = this.getCacheKey(joinPoint, idempotent);
|
||||
// 如果键已存在,则抛出异常
|
||||
if (!RedisUtils.setIfAbsent(cacheKey, Duration.ofMillis(idempotent.unit().toMillis(idempotent.timeout())))) {
|
||||
if (!RedisUtils.setIfAbsent(cacheKey, cacheKey, Duration.ofMillis(idempotent.unit()
|
||||
.toMillis(idempotent.timeout())))) {
|
||||
throw new IdempotentException(idempotent.message());
|
||||
}
|
||||
// 执行目标方法
|
||||
|
||||
@@ -50,13 +50,13 @@ public class SimpleDeserializersWrapper extends SimpleDeserializers {
|
||||
DeserializationConfig config,
|
||||
BeanDescription beanDesc) throws JsonMappingException {
|
||||
JsonDeserializer<?> deser = super.findEnumDeserializer(type, config, beanDesc);
|
||||
if (null != deser) {
|
||||
if (deser != null) {
|
||||
return deser;
|
||||
}
|
||||
// 重写增强:开始查找指定枚举类型的接口的反序列化器(例如:GenderEnum 枚举类型,则是找它的接口 BaseEnum 的反序列化器)
|
||||
for (Class<?> typeInterface : type.getInterfaces()) {
|
||||
deser = this._classMappings.get(new ClassKey(typeInterface));
|
||||
if (null != deser) {
|
||||
if (deser != null) {
|
||||
return deser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-core</artifactId>
|
||||
<description>ContiNew Starter License 模块 - 核心模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- TrueLicense (一个开源的证书管理引擎)-->
|
||||
<dependency>
|
||||
<groupId>de.schlichtherle.truelicense</groupId>
|
||||
<artifactId>truelicense-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Zip4j (开源的 Java 处理 zip 压缩文件的开发包) -->
|
||||
<dependency>
|
||||
<groupId>net.lingala.zip4j</groupId>
|
||||
<artifactId>zip4j</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.exception;
|
||||
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 自定义证书认证异常
|
||||
*
|
||||
* @author loach
|
||||
* @author echo
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class LicenseException extends BaseException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public LicenseException() {
|
||||
}
|
||||
|
||||
public LicenseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public LicenseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public LicenseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 生成创建者返回参数
|
||||
*
|
||||
* @author echo
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class BuildCreatorResp implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 许可证创建者参数
|
||||
*/
|
||||
private LicenseCreatorParam param;
|
||||
|
||||
/**
|
||||
* 客户端 Zip 文件
|
||||
*/
|
||||
private ZipFile clientZipFile;
|
||||
|
||||
public BuildCreatorResp(LicenseCreatorParam param, ZipFile clientZipFile) {
|
||||
this.param = param;
|
||||
this.clientZipFile = clientZipFile;
|
||||
}
|
||||
|
||||
public BuildCreatorResp() {
|
||||
}
|
||||
|
||||
public LicenseCreatorParam getParam() {
|
||||
return param;
|
||||
}
|
||||
|
||||
public void setParam(LicenseCreatorParam param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
public ZipFile getClientZipFile() {
|
||||
return clientZipFile;
|
||||
}
|
||||
|
||||
public void setClientZipFile(ZipFile clientZipFile) {
|
||||
this.clientZipFile = clientZipFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
/**
|
||||
* config参数
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class ConfigParam {
|
||||
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 公钥别称
|
||||
*/
|
||||
private String publicAlias;
|
||||
|
||||
/**
|
||||
* 访问公钥库的密码
|
||||
*/
|
||||
private String storePass;
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public String getPublicAlias() {
|
||||
return publicAlias;
|
||||
}
|
||||
|
||||
public void setPublicAlias(String publicAlias) {
|
||||
this.publicAlias = publicAlias;
|
||||
}
|
||||
|
||||
public String getStorePass() {
|
||||
return storePass;
|
||||
}
|
||||
|
||||
public void setStorePass(String storePass) {
|
||||
this.storePass = storePass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
import de.schlichtherle.license.AbstractKeyStoreParam;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 自定义密钥存储参数
|
||||
*
|
||||
* @author loach
|
||||
* @author echo
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class CustomKeyStoreParam extends AbstractKeyStoreParam {
|
||||
|
||||
/**
|
||||
* 密钥路径,可为磁盘路径,也可为项目资源文件里的路径,如果为磁盘路径需重写getStream()方法
|
||||
*/
|
||||
private String storePath;
|
||||
|
||||
/**
|
||||
* 公钥或私钥的别名
|
||||
*/
|
||||
private String alias;
|
||||
|
||||
/**
|
||||
* 访问公钥/私钥库的密码
|
||||
*/
|
||||
private String storePass;
|
||||
|
||||
/**
|
||||
* 公钥/私钥的密码
|
||||
*/
|
||||
private String keyPass;
|
||||
|
||||
public CustomKeyStoreParam(Class clazz, String s) {
|
||||
super(clazz, s);
|
||||
}
|
||||
|
||||
public CustomKeyStoreParam(Class clazz, String resource, String alias, String storePass, String keyPass) {
|
||||
super(clazz, resource);
|
||||
this.storePath = resource;
|
||||
this.alias = alias;
|
||||
this.storePass = storePass;
|
||||
this.keyPass = keyPass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStorePwd() {
|
||||
return storePass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyPwd() {
|
||||
return keyPass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return new FileInputStream(storePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 许可证创建者参数
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class LicenseCreatorParam implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 证书主题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 私钥别称
|
||||
*/
|
||||
private String privateAlias;
|
||||
|
||||
/**
|
||||
* 私钥密码
|
||||
*/
|
||||
private String keyPass;
|
||||
|
||||
/**
|
||||
* 访问公钥库的密码
|
||||
*/
|
||||
private String storePass;
|
||||
|
||||
/**
|
||||
* 证书生成路径
|
||||
*/
|
||||
private String licensePath;
|
||||
|
||||
/**
|
||||
* 私钥库存储路径
|
||||
*/
|
||||
private String privateKeysStorePath;
|
||||
|
||||
/**
|
||||
* 证书生效时间
|
||||
*/
|
||||
private Date issuedTime = new Date();
|
||||
|
||||
/**
|
||||
* 证书失效时间
|
||||
*/
|
||||
private Date expiryTime;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private String consumerType = "user";
|
||||
|
||||
/**
|
||||
* 用户数量
|
||||
*/
|
||||
private Integer consumerAmount = 1;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 额外的服务器硬件校验信息
|
||||
*/
|
||||
private LicenseExtraModel licenseExtraModel;
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public String getPrivateAlias() {
|
||||
return privateAlias;
|
||||
}
|
||||
|
||||
public void setPrivateAlias(String privateAlias) {
|
||||
this.privateAlias = privateAlias;
|
||||
}
|
||||
|
||||
public String getKeyPass() {
|
||||
return keyPass;
|
||||
}
|
||||
|
||||
public void setKeyPass(String keyPass) {
|
||||
this.keyPass = keyPass;
|
||||
}
|
||||
|
||||
public String getStorePass() {
|
||||
return storePass;
|
||||
}
|
||||
|
||||
public void setStorePass(String storePass) {
|
||||
this.storePass = storePass;
|
||||
}
|
||||
|
||||
public String getLicensePath() {
|
||||
return licensePath;
|
||||
}
|
||||
|
||||
public void setLicensePath(String licensePath) {
|
||||
this.licensePath = licensePath;
|
||||
}
|
||||
|
||||
public String getPrivateKeysStorePath() {
|
||||
return privateKeysStorePath;
|
||||
}
|
||||
|
||||
public void setPrivateKeysStorePath(String privateKeysStorePath) {
|
||||
this.privateKeysStorePath = privateKeysStorePath;
|
||||
}
|
||||
|
||||
public Date getIssuedTime() {
|
||||
return issuedTime;
|
||||
}
|
||||
|
||||
public void setIssuedTime(Date issuedTime) {
|
||||
this.issuedTime = issuedTime;
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
return expiryTime;
|
||||
}
|
||||
|
||||
public void setExpiryTime(Date expiryTime) {
|
||||
this.expiryTime = expiryTime;
|
||||
}
|
||||
|
||||
public String getConsumerType() {
|
||||
return consumerType;
|
||||
}
|
||||
|
||||
public void setConsumerType(String consumerType) {
|
||||
this.consumerType = consumerType;
|
||||
}
|
||||
|
||||
public Integer getConsumerAmount() {
|
||||
return consumerAmount;
|
||||
}
|
||||
|
||||
public void setConsumerAmount(Integer consumerAmount) {
|
||||
this.consumerAmount = consumerAmount;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public LicenseExtraModel getLicenseExtraModel() {
|
||||
return licenseExtraModel;
|
||||
}
|
||||
|
||||
public void setLicenseExtraModel(LicenseExtraModel licenseExtraModel) {
|
||||
this.licenseExtraModel = licenseExtraModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 为用户生成证书需要的具体参数
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class LicenseCreatorParamVO {
|
||||
|
||||
/**
|
||||
* 有效期截至时间
|
||||
*/
|
||||
private Date expireTime;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
private String customerName;
|
||||
|
||||
/**
|
||||
* 公钥钥库密码库,必须包含数字和字母
|
||||
*/
|
||||
private String storePass;
|
||||
|
||||
/**
|
||||
* 私钥密码,必须包含数字和字母
|
||||
*/
|
||||
private String keyPass;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* license 保存位置
|
||||
*/
|
||||
private String licenseSavePath;
|
||||
|
||||
/**
|
||||
* 额外的服务器硬件校验信息
|
||||
*/
|
||||
private LicenseExtraModel licenseExtraModel;
|
||||
|
||||
public Date getExpireTime() {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
public void setExpireTime(Date expireTime) {
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
|
||||
public String getStorePass() {
|
||||
return storePass;
|
||||
}
|
||||
|
||||
public void setStorePass(String storePass) {
|
||||
this.storePass = storePass;
|
||||
}
|
||||
|
||||
public String getKeyPass() {
|
||||
return keyPass;
|
||||
}
|
||||
|
||||
public void setKeyPass(String keyPass) {
|
||||
this.keyPass = keyPass;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getLicenseSavePath() {
|
||||
return licenseSavePath;
|
||||
}
|
||||
|
||||
public void setLicenseSavePath(String licenseSavePath) {
|
||||
this.licenseSavePath = licenseSavePath;
|
||||
}
|
||||
|
||||
public LicenseExtraModel getLicenseExtraModel() {
|
||||
return licenseExtraModel;
|
||||
}
|
||||
|
||||
public void setLicenseExtraModel(LicenseExtraModel licenseExtraModel) {
|
||||
this.licenseExtraModel = licenseExtraModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 额外的服务器硬件校验信息对象,这里的属性可根据需求自定义
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class LicenseExtraModel {
|
||||
|
||||
/**
|
||||
* 可被允许的IP地址
|
||||
*/
|
||||
private Set<String> ipAddress;
|
||||
|
||||
/**
|
||||
* 可被允许的mac地址
|
||||
*/
|
||||
private Set<String> macAddress;
|
||||
|
||||
/**
|
||||
* 可被允许的CPU序列号
|
||||
*/
|
||||
private String cpuSerial;
|
||||
|
||||
/**
|
||||
* 可被允许的主板序列号
|
||||
*/
|
||||
private String mainBoardSerial;
|
||||
|
||||
public Set<String> getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(Set<String> ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public Set<String> getMacAddress() {
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public void setMacAddress(Set<String> macAddress) {
|
||||
this.macAddress = macAddress;
|
||||
}
|
||||
|
||||
public String getCpuSerial() {
|
||||
return cpuSerial;
|
||||
}
|
||||
|
||||
public void setCpuSerial(String cpuSerial) {
|
||||
this.cpuSerial = cpuSerial;
|
||||
}
|
||||
|
||||
public String getMainBoardSerial() {
|
||||
return mainBoardSerial;
|
||||
}
|
||||
|
||||
public void setMainBoardSerial(String mainBoardSerial) {
|
||||
this.mainBoardSerial = mainBoardSerial;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.util;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
|
||||
/**
|
||||
* 运行命令行工具类
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class ExecCmdUtil {
|
||||
|
||||
private static final String CREATE_3RDSESSION_SHELL_SCRIPT = "head -n 80 /dev/urandom | tr -dc A-Za-z0-9 | head -c 168";
|
||||
|
||||
private ExecCmdUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行cmd命令(shell脚本)
|
||||
*
|
||||
* @param cmd linux sh/windows bat命令
|
||||
* @return String 返回打印信息
|
||||
*/
|
||||
public static String exec(String... cmd) throws IOException {
|
||||
Process process;
|
||||
if (System.getProperty("os.name").contains("Windows")) {
|
||||
if (cmd != null && cmd.length == 1) {
|
||||
process = Runtime.getRuntime().exec(cmd[0]);
|
||||
} else {
|
||||
process = Runtime.getRuntime().exec(cmd);
|
||||
}
|
||||
} else {
|
||||
cmd = ArrayUtil.addAll(new String[] {"/bin/sh", "-c"}, cmd);
|
||||
process = Runtime.getRuntime().exec(cmd);
|
||||
}
|
||||
|
||||
String print = readProcess(process.getInputStream());
|
||||
String err = readProcess(process.getErrorStream());
|
||||
return print + " " + err;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 InputStream 内容为字符串(使用 GBK 编码)。
|
||||
*
|
||||
* @param in 输入流
|
||||
* @return 拼接后的字符串,读取失败返回空字符串
|
||||
*/
|
||||
private static String readProcess(InputStream in) {
|
||||
try (LineNumberReader print = new LineNumberReader(new InputStreamReader(in, "GBK"))) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
String line;
|
||||
while ((line = print.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行linux命令(shell脚本)生成3rd_session随机数
|
||||
*/
|
||||
public static String create3rdSessionToken() throws IOException {
|
||||
return exec(CREATE_3RDSESSION_SHELL_SCRIPT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.license.exception.LicenseException;
|
||||
import top.continew.license.model.LicenseExtraModel;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 服务器信息工具类
|
||||
*
|
||||
* @author Rong.Jia
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class ServerInfoUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ServerInfoUtils.class);
|
||||
|
||||
private static class ServerInfosContainer {
|
||||
private static Set<String> ipAddress = null;
|
||||
private static Set<String> macAddress = null;
|
||||
private static String cpuSerial = null;
|
||||
private static String mainBoardSerial = null;
|
||||
}
|
||||
|
||||
private ServerInfoUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装需要额外校验的License参数
|
||||
*
|
||||
* @return {@link LicenseExtraModel }
|
||||
*/
|
||||
public static LicenseExtraModel getServerInfos() {
|
||||
LicenseExtraModel result = new LicenseExtraModel();
|
||||
try {
|
||||
initServerInfos();
|
||||
result.setIpAddress(ServerInfosContainer.ipAddress);
|
||||
result.setMacAddress(ServerInfosContainer.macAddress);
|
||||
result.setCpuSerial(ServerInfosContainer.cpuSerial);
|
||||
result.setMainBoardSerial(ServerInfosContainer.mainBoardSerial);
|
||||
} catch (Exception e) {
|
||||
log.error("获取服务器硬件信息异常", e);
|
||||
throw new LicenseException(String.format("获取服务器硬件信息异常, %s", e.getMessage()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化服务器硬件信息,并将信息缓存到内存
|
||||
*
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
private static void initServerInfos() throws Exception {
|
||||
if (ServerInfosContainer.ipAddress == null) {
|
||||
ServerInfosContainer.ipAddress = getIpAddress();
|
||||
}
|
||||
if (ServerInfosContainer.macAddress == null) {
|
||||
ServerInfosContainer.macAddress = getMacAddress();
|
||||
}
|
||||
if (ServerInfosContainer.cpuSerial == null) {
|
||||
ServerInfosContainer.cpuSerial = getCpuSerial();
|
||||
}
|
||||
if (ServerInfosContainer.mainBoardSerial == null) {
|
||||
ServerInfosContainer.mainBoardSerial = getMainBoardSerial();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器临时磁盘位置
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
public static String getServerTempPath() {
|
||||
return System.getProperty("user.dir");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取CPU序列号
|
||||
*
|
||||
* @return String CPU 序列号
|
||||
*/
|
||||
public static String getCpuSerial() {
|
||||
return FileUtil.isWindows() ? getWindowCpuSerial() : getLinuxCpuSerial();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主板序列号
|
||||
*
|
||||
* @return String 主板序列号
|
||||
*/
|
||||
public static String getMainBoardSerial() {
|
||||
return FileUtil.isWindows() ? getWindowMainBoardSerial() : getLinuxMainBoardSerial();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取linux cpu 序列号
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
private static String getLinuxCpuSerial() {
|
||||
String result = StrUtil.EMPTY;
|
||||
String cpuIdCmd = "dmidecode";
|
||||
BufferedReader bufferedReader = null;
|
||||
try {
|
||||
// 管道
|
||||
Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", cpuIdCmd});
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
String line = null;
|
||||
int index = -1;
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
// 寻找标示字符串[hwaddr]
|
||||
index = line.toLowerCase().indexOf("uuid");
|
||||
if (index >= 0) {
|
||||
// 取出mac地址并去除2边空格
|
||||
result = line.substring(index + "uuid".length() + 1).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("获取Linux cpu信息错误 {}", e.getMessage());
|
||||
} finally {
|
||||
IoUtil.close(bufferedReader);
|
||||
}
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Window cpu 序列号
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
private static String getWindowCpuSerial() {
|
||||
|
||||
StringBuilder result = new StringBuilder(StrUtil.EMPTY);
|
||||
File file = null;
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
file = File.createTempFile("tmp", ".vbs");
|
||||
file.deleteOnExit();
|
||||
FileWriter fw = new FileWriter(file);
|
||||
String vbs = """
|
||||
Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2")
|
||||
Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor")
|
||||
|
||||
For Each objItem In colItems
|
||||
WScript.Echo objItem.ProcessorId
|
||||
Exit For ' do the first cpu only!
|
||||
Next
|
||||
""";
|
||||
fw.write(vbs);
|
||||
fw.close();
|
||||
|
||||
Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath());
|
||||
input = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取window cpu信息错误, {}", e.getMessage());
|
||||
} finally {
|
||||
IoUtil.close(input);
|
||||
FileUtil.del(file);
|
||||
}
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Linux主板序列号
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
private static String getLinuxMainBoardSerial() {
|
||||
String command = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1";
|
||||
try {
|
||||
Process process = new ProcessBuilder("sh", "-c", command).start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
return reader.lines().findFirst().orElse(StrUtil.EMPTY);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("获取 Linux 主板序列号失败: {}", e.getMessage());
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取window主板序列号
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
private static String getWindowMainBoardSerial() {
|
||||
StringBuilder result = new StringBuilder(StrUtil.EMPTY);
|
||||
File file = null;
|
||||
BufferedReader input = null;
|
||||
try {
|
||||
file = File.createTempFile("realhowto", ".vbs");
|
||||
file.deleteOnExit();
|
||||
FileWriter fw = new FileWriter(file);
|
||||
|
||||
String vbs = """
|
||||
Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2")
|
||||
Set colItems = objWMIService.ExecQuery _
|
||||
("Select * from Win32_BaseBoard")
|
||||
For Each objItem in colItems
|
||||
Wscript.Echo objItem.SerialNumber
|
||||
exit for ' do the first cpu only!
|
||||
Next
|
||||
""";
|
||||
|
||||
fw.write(vbs);
|
||||
fw.close();
|
||||
Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath());
|
||||
input = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取Window主板信息错误 {}", e.getMessage());
|
||||
} finally {
|
||||
IoUtil.close(input);
|
||||
FileUtil.del(file);
|
||||
}
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>获取Mac地址</p>
|
||||
*
|
||||
* @return List<String> Mac地址
|
||||
* @throws Exception 默认异常
|
||||
*/
|
||||
public static Set<String> getMacAddress() throws Exception {
|
||||
// 获取所有网络接口
|
||||
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
||||
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
||||
return inetAddresses.stream()
|
||||
.map(ServerInfoUtils::getMacByInetAddress)
|
||||
.distinct()
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>获取IP地址</p>
|
||||
*
|
||||
* @return List<String> IP地址
|
||||
* @throws Exception 默认异常
|
||||
*/
|
||||
public static Set<String> getIpAddress() throws Exception {
|
||||
// 获取所有网络接口
|
||||
Set<InetAddress> inetAddresses = getLocalAllInetAddress();
|
||||
if (CollectionUtil.isNotEmpty(inetAddresses)) {
|
||||
return inetAddresses.stream()
|
||||
.map(InetAddress::getHostAddress)
|
||||
.distinct()
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>获取某个网络地址对应的Mac地址</p>
|
||||
*
|
||||
* @param inetAddr 网络地址
|
||||
* @return String Mac地址
|
||||
*/
|
||||
private static String getMacByInetAddress(InetAddress inetAddr) {
|
||||
try {
|
||||
byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < mac.length; i++) {
|
||||
if (i != 0) {
|
||||
stringBuilder.append("-");
|
||||
}
|
||||
// 将十六进制byte转化为字符串
|
||||
String temp = Integer.toHexString(mac[i] & 0xff);
|
||||
if (temp.length() == 1) {
|
||||
stringBuilder.append("0").append(temp);
|
||||
} else {
|
||||
stringBuilder.append(temp);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString().toUpperCase();
|
||||
} catch (SocketException e) {
|
||||
log.error("getMacByInetAddress {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>获取当前服务器所有符合条件的网络地址</p>
|
||||
*
|
||||
* @return List<InetAddress> 网络地址列表
|
||||
* @throws Exception 默认异常
|
||||
*/
|
||||
private static Set<InetAddress> getLocalAllInetAddress() throws Exception {
|
||||
Set<InetAddress> result = CollUtil.newHashSet();
|
||||
// 遍历所有的网络接口
|
||||
for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
networkInterfaces.hasMoreElements();) {
|
||||
NetworkInterface ni = networkInterfaces.nextElement();
|
||||
// 在所有的接口下再遍历IP
|
||||
for (Enumeration<InetAddress> addresses = ni.getInetAddresses(); addresses.hasMoreElements();) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
//排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址
|
||||
/*&& !inetAddr.isSiteLocalAddress()*/
|
||||
if (!address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isMulticastAddress()) {
|
||||
result.add(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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>top.continew</groupId>
|
||||
<artifactId>continew-starter-license</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-generator</artifactId>
|
||||
<description>ContiNew Starter License 模块 - 生成器</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- License 模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import top.continew.license.service.LicenseCreateService;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* license 生成模块 自动配置
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(LicenseGenerateProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.LICENSE_GENERATOR, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class LicenseGenerateAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LicenseGenerateAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* license 生成服务接口
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LicenseCreateService licenseCreateService() {
|
||||
return LicenseCreateService.getInstance();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'License-Generator' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* license 生成模块配置属性
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.12.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.LICENSE_GENERATOR)
|
||||
public class LicenseGenerateProperties {
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.manager;
|
||||
|
||||
import de.schlichtherle.license.*;
|
||||
import de.schlichtherle.xml.GenericCertificate;
|
||||
import top.continew.license.exception.LicenseException;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 自定义服务端证书管理类(生成证书)
|
||||
*
|
||||
* @author loach
|
||||
* @author echo
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class ServerLicenseManager extends LicenseManager {
|
||||
|
||||
public ServerLicenseManager(LicenseParam param) {
|
||||
super(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 证书生成参数验证
|
||||
*
|
||||
* @param content 内容
|
||||
*/
|
||||
protected synchronized void validateCreate(final LicenseContent content) {
|
||||
Date now = new Date();
|
||||
Date notBefore = content.getNotBefore();
|
||||
Date notAfter = content.getNotAfter();
|
||||
if (notBefore != null && now.before(notBefore)) {
|
||||
throw new LicenseException("证书尚未生效,无法生成");
|
||||
}
|
||||
if (notAfter != null && now.after(notAfter)) {
|
||||
throw new LicenseException("证书已过期,无法生成");
|
||||
}
|
||||
|
||||
if (notBefore != null && notAfter != null && notBefore.after(notAfter)) {
|
||||
throw new LicenseException("证书生效时间晚于失效时间,无法生成");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写生成证书的方法,增加生成参数验证
|
||||
*
|
||||
* @param content 内容
|
||||
* @param notary 公证人
|
||||
* @return {@link byte[] }
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
@Override
|
||||
protected synchronized byte[] create(LicenseContent content, LicenseNotary notary) throws Exception {
|
||||
initialize(content);
|
||||
validateCreate(content);
|
||||
final GenericCertificate genericCertificate = notary.sign(content);
|
||||
return getPrivacyGuard().cert2key(genericCertificate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.service;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.schlichtherle.license.*;
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.license.exception.LicenseException;
|
||||
import top.continew.license.manager.ServerLicenseManager;
|
||||
import top.continew.license.model.*;
|
||||
import top.continew.license.util.ExecCmdUtil;
|
||||
import top.continew.license.util.ServerInfoUtils;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* 证书生成接口 实现类
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class LicenseCreateService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LicenseCreateService.class);
|
||||
|
||||
private static volatile LicenseCreateService instance;
|
||||
|
||||
private static final X500Principal DEFAULT_HOLDER_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
|
||||
|
||||
private LicenseCreateService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例
|
||||
*
|
||||
* @return {@link LicenseCreateService }
|
||||
*/
|
||||
public static LicenseCreateService getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (LicenseCreateService.class) {
|
||||
if (instance == null) {
|
||||
instance = new LicenseCreateService();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器信息
|
||||
*
|
||||
* @return {@link LicenseExtraModel }
|
||||
*/
|
||||
public LicenseExtraModel getServerInfo() {
|
||||
return ServerInfoUtils.getServerInfos();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个证书
|
||||
*
|
||||
* @param paramVO param vo
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
public void generateLicense(LicenseCreatorParamVO paramVO) throws Exception {
|
||||
BuildCreatorResp buildCreatorResp = buildCreator(paramVO);
|
||||
LicenseCreatorParam param = buildCreatorResp.getParam();
|
||||
ZipFile clientZipFile = buildCreatorResp.getClientZipFile();
|
||||
try {
|
||||
LicenseParam licenseParam = initLicenseParam(param);
|
||||
LicenseManager licenseManager = new ServerLicenseManager(licenseParam);
|
||||
LicenseContent licenseContent = initLicenseContent(param);
|
||||
licenseManager.store(licenseContent, new File(param.getLicensePath()));
|
||||
log.info("{} 证书生成成功 路径: {}", param.getSubject(), param.getLicensePath());
|
||||
clientZipFile.addFile(param.getLicensePath());
|
||||
} catch (Exception e) {
|
||||
throw new LicenseException("生成证书失败:" + param.getSubject(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 License 创建者对象及客户端配置压缩包。
|
||||
*
|
||||
* @param paramVO 创建参数封装对象,包含客户名、密码、描述、扩展信息等。
|
||||
* @return Map 包含 LicenseCreatorParam(creator) 和 生成的客户端 Zip 文件(clientZipFile)
|
||||
* @throws Exception 命令执行或文件操作过程中出现异常
|
||||
*/
|
||||
private BuildCreatorResp buildCreator(LicenseCreatorParamVO paramVO) throws Exception {
|
||||
String customerName = paramVO.getCustomerName();
|
||||
String privateAlias = customerName + "-private-alias";
|
||||
String publicAlias = customerName + "-public-alias";
|
||||
String currentCustomerDir = relativePath(paramVO) + customerName + IdUtil.fastSimpleUUID() + File.separator;
|
||||
File customerDirFile = new File(currentCustomerDir);
|
||||
if (!customerDirFile.exists() && !customerDirFile.mkdirs()) {
|
||||
throw new IOException("Failed to create directory: " + currentCustomerDir);
|
||||
}
|
||||
|
||||
String privateKeystore = currentCustomerDir + "privateKeys.keystore";
|
||||
String publicKeystore = currentCustomerDir + "publicCerts.keystore";
|
||||
String certFilePath = currentCustomerDir + "certfile.cer";
|
||||
String licensePath = currentCustomerDir + "license.lic";
|
||||
|
||||
LicenseCreatorParam param = new LicenseCreatorParam();
|
||||
param.setSubject(customerName);
|
||||
param.setPrivateAlias(privateAlias);
|
||||
param.setKeyPass(paramVO.getKeyPass());
|
||||
param.setStorePass(paramVO.getStorePass());
|
||||
param.setLicensePath(licensePath);
|
||||
param.setPrivateKeysStorePath(privateKeystore);
|
||||
param.setExpiryTime(paramVO.getExpireTime());
|
||||
param.setDescription(paramVO.getDescription());
|
||||
param.setLicenseExtraModel(paramVO.getLicenseExtraModel());
|
||||
|
||||
int validity = getValidity(param.getIssuedTime(), paramVO.getExpireTime());
|
||||
String dname = "\"CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN\"";
|
||||
|
||||
// 生成私钥库
|
||||
String keyAlgOption = checkJavaVersion() ? "-keyalg DSA" : ""; // JDK>=17 要指定 keyalg
|
||||
String genKeyCmd = String
|
||||
.format("keytool -genkeypair %s -keysize 1024 -validity %d -alias %s -keystore %s -storepass %s -keypass %s -dname %s", keyAlgOption, validity, privateAlias, privateKeystore, paramVO
|
||||
.getStorePass(), paramVO.getKeyPass(), dname);
|
||||
|
||||
// 导出证书
|
||||
String exportCertCmd = String
|
||||
.format("keytool -exportcert -alias %s -keystore %s -storepass %s -file \"%s\"", privateAlias, privateKeystore, paramVO
|
||||
.getStorePass(), certFilePath);
|
||||
|
||||
// 导入到公钥库
|
||||
String importCertCmd = String
|
||||
.format("keytool -noprompt -import -alias %s -file \"%s\" -keystore %s -storepass %s", publicAlias, certFilePath, publicKeystore, paramVO
|
||||
.getStorePass());
|
||||
|
||||
// 执行命令
|
||||
ExecCmdUtil.exec(genKeyCmd);
|
||||
ExecCmdUtil.exec(exportCertCmd);
|
||||
ExecCmdUtil.exec(importCertCmd);
|
||||
|
||||
// 生成客户端配置文件
|
||||
ZipFile clientZipFile = generateClientConfig(param, currentCustomerDir, publicAlias);
|
||||
return new BuildCreatorResp(param, clientZipFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验JDK版本
|
||||
*
|
||||
* @return boole T 17 版本 F 非 17 版本
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
private boolean checkJavaVersion() throws Exception {
|
||||
String version = System.getProperty("java.version");
|
||||
int currentVersion = 0;
|
||||
if (version.startsWith("1.")) {
|
||||
currentVersion = Integer.parseInt(version.split("\\.")[1]);
|
||||
} else {
|
||||
currentVersion = Integer.parseInt(version.split("\\.")[0]);
|
||||
}
|
||||
return currentVersion >= 17;
|
||||
}
|
||||
|
||||
private ZipFile generateClientConfig(LicenseCreatorParam param,
|
||||
String currentCustomerDir,
|
||||
String publicAlias) throws Exception {
|
||||
ZipFile clientLicense = new ZipFile(currentCustomerDir + "clientLicense.zip");
|
||||
File config = new File(currentCustomerDir + "clientConfig.json");
|
||||
ConfigParam configParam = new ConfigParam();
|
||||
configParam.setPublicAlias(publicAlias);
|
||||
configParam.setStorePass(param.getStorePass());
|
||||
configParam.setSubject(param.getSubject());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = mapper.writeValueAsString(configParam);
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(config);
|
||||
out.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
out.flush();
|
||||
} catch (Exception e) {
|
||||
throw new LicenseException("密钥文件生成失败", e);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
throw new LicenseException("文件流关闭失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<File> files = new ArrayList<>();
|
||||
files.add(config);
|
||||
files.add(new File(currentCustomerDir + "publicCerts.keystore"));
|
||||
clientLicense.addFiles(files);
|
||||
return clientLicense;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将有效时间转换成天
|
||||
*
|
||||
* @param issuedTime 签发时间
|
||||
* @param expireTime 过期时间
|
||||
* @return int
|
||||
*/
|
||||
private int getValidity(Date issuedTime, Date expireTime) {
|
||||
long issued = issuedTime.getTime();
|
||||
long expire = expireTime.getTime();
|
||||
long differ = expire - issued;
|
||||
long remaining = differ % (24L * 3600L * 1000L);
|
||||
long validity = differ / (24L * 3600L * 1000L);
|
||||
if (remaining > 0) {
|
||||
validity++;
|
||||
}
|
||||
return (int)validity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是 Windows
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean isWindows() {
|
||||
String os = System.getProperty("os.name");
|
||||
return os.toLowerCase().contains("windows");
|
||||
}
|
||||
|
||||
/**
|
||||
* 证书生成路径
|
||||
*
|
||||
* @param paramVO param vo
|
||||
* @return {@link String }
|
||||
*/
|
||||
private String relativePath(LicenseCreatorParamVO paramVO) {
|
||||
if (paramVO.getLicenseSavePath() != null) {
|
||||
return paramVO.getLicenseSavePath();
|
||||
}
|
||||
if (isWindows()) {
|
||||
return "C:/license/";
|
||||
}
|
||||
return "/data/license/";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书生成参数
|
||||
*/
|
||||
private LicenseParam initLicenseParam(LicenseCreatorParam param) {
|
||||
Preferences preferences = Preferences.userNodeForPackage(LicenseCreateService.class);
|
||||
//设置密钥
|
||||
CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
|
||||
KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreateService.class, param
|
||||
.getPrivateKeysStorePath(), param.getPrivateAlias(), param.getStorePass(), param.getKeyPass());
|
||||
return new DefaultLicenseParam(param.getSubject(), preferences, privateStoreParam, cipherParam);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置证书生成内容
|
||||
*
|
||||
* @param param 参数
|
||||
* @return {@link LicenseContent }
|
||||
*/
|
||||
private LicenseContent initLicenseContent(LicenseCreatorParam param) {
|
||||
LicenseContent licenseContent = new LicenseContent();
|
||||
licenseContent.setHolder(DEFAULT_HOLDER_ISSUER);
|
||||
licenseContent.setIssuer(DEFAULT_HOLDER_ISSUER);
|
||||
licenseContent.setSubject(param.getSubject());
|
||||
licenseContent.setIssued(param.getIssuedTime());
|
||||
licenseContent.setNotBefore(param.getIssuedTime());
|
||||
licenseContent.setNotAfter(param.getExpiryTime());
|
||||
licenseContent.setConsumerType(param.getConsumerType());
|
||||
licenseContent.setConsumerAmount(param.getConsumerAmount());
|
||||
licenseContent.setInfo(param.getDescription());
|
||||
|
||||
if (param.getLicenseExtraModel() != null) {
|
||||
licenseContent.setExtra(param.getLicenseExtraModel());
|
||||
}
|
||||
|
||||
return licenseContent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.license.autoconfigure.LicenseGenerateAutoConfiguration
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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>top.continew</groupId>
|
||||
<artifactId>continew-starter-license</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license-verifier</artifactId>
|
||||
<description>ContiNew Starter License 模块 - 校验器</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- License 模块 - 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-license-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.autoconfigure;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
import de.schlichtherle.license.LicenseManager;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import top.continew.license.initializing.LicenseStarterInitializingBean;
|
||||
import top.continew.license.bean.LicenseInstallerBean;
|
||||
import top.continew.license.manager.CustomLicenseManager;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* license 校验模块 自动配置
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(LicenseVerifyProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.LICENSE_VERIFIER, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
|
||||
public class LicenseVerifyAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LicenseVerifyAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 证书安装业务类
|
||||
*
|
||||
* @param properties 属性
|
||||
* @return {@link LicenseInstallerBean }
|
||||
*/
|
||||
@Bean
|
||||
public LicenseInstallerBean licenseInstallerBean(LicenseVerifyProperties properties) {
|
||||
return new LicenseInstallerBean(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动校验 License服务
|
||||
*
|
||||
* @param licenseInstallerBean 许可证安装程序bean
|
||||
* @return {@link LicenseStarterInitializingBean }
|
||||
*/
|
||||
@Bean
|
||||
@DependsOn("licenseInstallerBean")
|
||||
public LicenseStarterInitializingBean licenseStarterInitializingBean(LicenseInstallerBean licenseInstallerBean) {
|
||||
return new LicenseStarterInitializingBean(licenseInstallerBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端证书管理类(证书验证)
|
||||
*
|
||||
* @param properties 属性
|
||||
* @return {@link LicenseManager }
|
||||
*/
|
||||
@Bean
|
||||
public LicenseManager licenseManager(LicenseVerifyProperties properties) {
|
||||
return CustomLicenseManager.getInstance(properties);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'License-Verifier' completed initialization.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* license 校验模块配置属性
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.LICENSE_VERIFIER)
|
||||
public class LicenseVerifyProperties {
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 生成的license文件所在路径
|
||||
*/
|
||||
private String storePath = FileUtil.getTmpDirPath();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getStorePath() {
|
||||
return storePath;
|
||||
}
|
||||
|
||||
public void setStorePath(String storePath) {
|
||||
this.storePath = storePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.bean;
|
||||
|
||||
import de.schlichtherle.license.LicenseContent;
|
||||
import de.schlichtherle.license.LicenseManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.license.autoconfigure.LicenseVerifyProperties;
|
||||
import top.continew.license.exception.LicenseException;
|
||||
import top.continew.license.manager.CustomLicenseManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 证书安装业务类
|
||||
*
|
||||
* @author loach
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class LicenseInstallerBean {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LicenseInstallerBean.class);
|
||||
|
||||
private final LicenseVerifyProperties properties;
|
||||
private LicenseManager licenseManager;
|
||||
|
||||
public LicenseInstallerBean(LicenseVerifyProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装许可证
|
||||
*/
|
||||
public void installLicense() {
|
||||
try {
|
||||
this.licenseManager = CustomLicenseManager.getInstance(properties);
|
||||
licenseManager.uninstall();
|
||||
File licenseFile = Paths.get(properties.getStorePath(), "clientLicense", "license.lic").toFile();
|
||||
LicenseContent licenseContent = licenseManager.install(licenseFile);
|
||||
log.info("证书认证通过,安装成功: {}", licenseContent.getSubject());
|
||||
} catch (Exception e) {
|
||||
throw new LicenseException("证书认证失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载许可证
|
||||
*/
|
||||
public void uninstallLicense() {
|
||||
if (licenseManager != null) {
|
||||
try {
|
||||
licenseManager.uninstall();
|
||||
log.info("证书已卸载");
|
||||
} catch (Exception e) {
|
||||
log.warn("卸载证书失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 即时验证证书合法性
|
||||
*/
|
||||
public void verify() {
|
||||
if (licenseManager != null) {
|
||||
try {
|
||||
licenseManager.verify();
|
||||
log.info("证书验证成功");
|
||||
} catch (Exception e) {
|
||||
throw new LicenseException("证书认证失败", e);
|
||||
}
|
||||
} else {
|
||||
throw new LicenseException("证书认证失败: licenseManager is null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.initializing;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import top.continew.license.bean.LicenseInstallerBean;
|
||||
|
||||
/**
|
||||
* 启动校验 License
|
||||
*
|
||||
* @author loach
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class LicenseStarterInitializingBean implements InitializingBean {
|
||||
private final LicenseInstallerBean licenseInstallerBean;
|
||||
|
||||
public LicenseStarterInitializingBean(LicenseInstallerBean licenseInstallerBean) {
|
||||
this.licenseInstallerBean = licenseInstallerBean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
licenseInstallerBean.installLicense();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.license.manager;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.schlichtherle.license.*;
|
||||
import de.schlichtherle.xml.GenericCertificate;
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.license.autoconfigure.LicenseVerifyProperties;
|
||||
import top.continew.license.bean.LicenseInstallerBean;
|
||||
import top.continew.license.exception.LicenseException;
|
||||
import top.continew.license.model.ConfigParam;
|
||||
import top.continew.license.model.CustomKeyStoreParam;
|
||||
import top.continew.license.model.LicenseExtraModel;
|
||||
import top.continew.license.util.ServerInfoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* 客户端证书管理类(证书验证)
|
||||
*
|
||||
* @author loach
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class CustomLicenseManager extends LicenseManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CustomLicenseManager.class);
|
||||
|
||||
private static volatile CustomLicenseManager INSTANCE;
|
||||
private LicenseExtraModel extraModel;
|
||||
|
||||
private final LicenseVerifyProperties properties;
|
||||
|
||||
private CustomLicenseManager(LicenseVerifyProperties properties) {
|
||||
this.properties = properties;
|
||||
// 初始化服务信息
|
||||
initServerExtraModel();
|
||||
// 解压证书和配置文件等
|
||||
extractZip();
|
||||
// 获取配置文件
|
||||
ConfigParam configParam = getConfigParam();
|
||||
// 安装证书
|
||||
Preferences preferences = Preferences.userNodeForPackage(LicenseInstallerBean.class);
|
||||
CipherParam cipherParam = new DefaultCipherParam(configParam.getStorePass());
|
||||
KeyStoreParam publicKeyStoreParam = new CustomKeyStoreParam(LicenseInstallerBean.class, properties
|
||||
.getStorePath() + File.separator + "clientLicense/publicCerts.keystore", configParam
|
||||
.getPublicAlias(), configParam.getStorePass(), null);
|
||||
LicenseParam licenseParam = new DefaultLicenseParam(configParam
|
||||
.getSubject(), preferences, publicKeyStoreParam, cipherParam);
|
||||
|
||||
super.setLicenseParam(licenseParam);
|
||||
}
|
||||
|
||||
public static CustomLicenseManager getInstance(LicenseVerifyProperties properties) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (CustomLicenseManager.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new CustomLicenseManager(properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private void initServerExtraModel() {
|
||||
this.extraModel = ServerInfoUtils.getServerInfos();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压压缩包
|
||||
*/
|
||||
private void extractZip() {
|
||||
Path zipPath = Paths.get(properties.getStorePath(), "clientLicense.zip");
|
||||
Path outputDir = Paths.get(properties.getStorePath(), "clientLicense");
|
||||
|
||||
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
|
||||
if (!Files.exists(outputDir)) {
|
||||
Files.createDirectories(outputDir);
|
||||
}
|
||||
zipFile.extractAll(outputDir.toAbsolutePath().toString());
|
||||
} catch (IOException e) {
|
||||
log.error("解压 clientLicense.zip 出错: {}", e.getMessage(), e);
|
||||
throw new LicenseException("解压失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩文件中配置的基础参数
|
||||
*
|
||||
* @return {@link ConfigParam }
|
||||
*/
|
||||
private ConfigParam getConfigParam() {
|
||||
Path configPath = Paths.get(properties.getStorePath(), "clientLicense", "clientConfig.json");
|
||||
|
||||
if (!Files.exists(configPath)) {
|
||||
log.warn("配置文件不存在: {}", configPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = Files.newInputStream(configPath)) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readValue(inputStream, ConfigParam.class);
|
||||
} catch (IOException e) {
|
||||
log.error("读取配置文件失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写验证证书方法,添加自定义参数验证
|
||||
*
|
||||
* @param content 内容
|
||||
* @throws LicenseContentException 许可证内容例外
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void validate(LicenseContent content) throws LicenseContentException {
|
||||
// 系统验证基本参数:生效时间、失效时间、公钥别名、公钥密码
|
||||
super.validate(content);
|
||||
// 验证自定义参数
|
||||
Object o = content.getExtra();
|
||||
if (extraModel != null && o instanceof LicenseExtraModel contentExtraModel) {
|
||||
if (!contentExtraModel.getCpuSerial().equals(extraModel.getCpuSerial())) {
|
||||
throw new LicenseException("CPU核数不匹配");
|
||||
}
|
||||
if (!contentExtraModel.getMainBoardSerial().equals(extraModel.getMainBoardSerial())) {
|
||||
throw new LicenseException("主板序列号不匹配");
|
||||
}
|
||||
if (!contentExtraModel.getIpAddress().equals(extraModel.getIpAddress())) {
|
||||
throw new LicenseException("IP地址不匹配");
|
||||
}
|
||||
if (!contentExtraModel.getMacAddress().equals(extraModel.getMacAddress())) {
|
||||
throw new LicenseException("MAC地址不匹配");
|
||||
}
|
||||
} else {
|
||||
throw new LicenseException("证书无效");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写证书安装方法,主要是更改调用本类的验证方法
|
||||
*
|
||||
* @param key 钥匙
|
||||
* @param notary 公证人
|
||||
* @return {@link LicenseContent }
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
@Override
|
||||
protected synchronized LicenseContent install(final byte[] key, LicenseNotary notary) throws Exception {
|
||||
|
||||
final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
|
||||
notary.verify(certificate);
|
||||
final LicenseContent content = (LicenseContent)certificate.getContent();
|
||||
this.validate(content);
|
||||
setLicenseKey(key);
|
||||
setCertificate(certificate);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写验证证书合法的方法,主要是更改调用本类的验证方法
|
||||
*
|
||||
* @param notary 公证人
|
||||
* @return {@link LicenseContent }
|
||||
* @throws Exception 例外
|
||||
*/
|
||||
@Override
|
||||
protected synchronized LicenseContent verify(LicenseNotary notary) throws Exception {
|
||||
GenericCertificate certificate = getCertificate();
|
||||
if (certificate != null) {
|
||||
return (LicenseContent)certificate.getContent();
|
||||
}
|
||||
byte[] licenseKey = getLicenseKey();
|
||||
if (licenseKey == null) {
|
||||
String subject = getLicenseParam().getSubject();
|
||||
throw new NoLicenseInstalledException(subject);
|
||||
}
|
||||
// 使用私钥解密生成证书
|
||||
certificate = getPrivacyGuard().key2cert(licenseKey);
|
||||
// 验证证书签名
|
||||
notary.verify(certificate);
|
||||
// 提取内容并进行业务校验
|
||||
LicenseContent content = (LicenseContent)certificate.getContent();
|
||||
this.validate(content);
|
||||
// 缓存证书
|
||||
setCertificate(certificate);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.license.autoconfigure.LicenseVerifyAutoConfiguration
|
||||
21
continew-starter-license/pom.xml
Normal file
21
continew-starter-license/pom.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-license</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<description>ContiNew Starter License模块</description>
|
||||
|
||||
<modules>
|
||||
<module>continew-starter-license-core</module>
|
||||
<module>continew-starter-license-generator</module>
|
||||
<module>continew-starter-license-verifier</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package top.continew.starter.log.aspect;
|
||||
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -78,7 +77,7 @@ public class LogAspect {
|
||||
// 指定规则不记录
|
||||
Method targetMethod = this.getMethod(joinPoint);
|
||||
Class<?> targetClass = joinPoint.getTarget().getClass();
|
||||
if (!isRequestRecord(targetMethod, targetClass)) {
|
||||
if (!isRecord(targetMethod, targetClass)) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
String errorMsg = null;
|
||||
@@ -108,13 +107,13 @@ public class LogAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
* 是否记录日志
|
||||
*
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
* @return true:需要记录;false:不需要记录
|
||||
*/
|
||||
private boolean isRequestRecord(Method targetMethod, Class<?> targetClass) {
|
||||
private boolean isRecord(Method targetMethod, Class<?> targetClass) {
|
||||
// 非 Web 环境不记录
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null || attributes.getResponse() == null) {
|
||||
@@ -124,13 +123,7 @@ public class LogAspect {
|
||||
if (logProperties.isMatch(attributes.getRequest().getRequestURI())) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
if (null != methodLog && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
return logHandler.isRecord(targetMethod, targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,11 +36,6 @@ import java.net.URISyntaxException;
|
||||
/**
|
||||
* 日志过滤器
|
||||
*
|
||||
* @author Dave Syer(Spring Boot Actuator)
|
||||
* @author Wallace Wadge(Spring Boot Actuator)
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Venil Noronha(Spring Boot Actuator)
|
||||
* @author Madhura Bhave(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @since 1.1.0
|
||||
|
||||
@@ -20,6 +20,7 @@ import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
@@ -49,6 +50,29 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(AbstractLogHandler.class);
|
||||
private final TransmittableThreadLocal<AccessLogContext> logContextThread = new TransmittableThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public boolean isRecord(Method targetMethod, Class<?> targetClass) {
|
||||
// 如果接口被隐藏,不记录日志
|
||||
Operation methodOperation = AnnotationUtil.getAnnotation(targetMethod, Operation.class);
|
||||
if (methodOperation != null && methodOperation.hidden()) {
|
||||
return false;
|
||||
}
|
||||
Hidden methodHidden = AnnotationUtil.getAnnotation(targetMethod, Hidden.class);
|
||||
if (methodHidden != null) {
|
||||
return false;
|
||||
}
|
||||
if (targetClass.getDeclaredAnnotation(Hidden.class) != null) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
if (methodLog != null && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecord.Started start(Instant startTime) {
|
||||
return LogRecord.start(startTime);
|
||||
@@ -89,13 +113,13 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
logRecord.setDescription("请在该接口方法上添加 @top.continew.starter.log.annotation.Log(value) 来指定日志描述");
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
// 例如:@Log("新增部门") -> 新增部门
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.value())) {
|
||||
if (methodLog != null && CharSequenceUtil.isNotBlank(methodLog.value())) {
|
||||
logRecord.setDescription(methodLog.value());
|
||||
return;
|
||||
}
|
||||
// 例如:@Operation(summary="新增部门") -> 新增部门
|
||||
Operation methodOperation = AnnotationUtil.getAnnotation(targetMethod, Operation.class);
|
||||
if (null != methodOperation && CharSequenceUtil.isNotBlank(methodOperation.summary())) {
|
||||
if (methodOperation != null && CharSequenceUtil.isNotBlank(methodOperation.summary())) {
|
||||
logRecord.setDescription(methodOperation.summary());
|
||||
}
|
||||
}
|
||||
@@ -113,18 +137,18 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
// 例如:@Log(module = "部门管理") -> 部门管理
|
||||
// 方法级注解优先级高于类级注解
|
||||
if (null != methodLog && CharSequenceUtil.isNotBlank(methodLog.module())) {
|
||||
if (methodLog != null && CharSequenceUtil.isNotBlank(methodLog.module())) {
|
||||
logRecord.setModule(methodLog.module());
|
||||
return;
|
||||
}
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
if (null != classLog && CharSequenceUtil.isNotBlank(classLog.module())) {
|
||||
if (classLog != null && CharSequenceUtil.isNotBlank(classLog.module())) {
|
||||
logRecord.setModule(classLog.module());
|
||||
return;
|
||||
}
|
||||
// 例如:@Tag(name = "部门管理") -> 部门管理
|
||||
Tag classTag = AnnotationUtil.getAnnotation(targetClass, Tag.class);
|
||||
if (null != classTag && CharSequenceUtil.isNotBlank(classTag.name())) {
|
||||
if (classTag != null && CharSequenceUtil.isNotBlank(classTag.name())) {
|
||||
logRecord.setModule(classTag.name());
|
||||
}
|
||||
}
|
||||
@@ -133,12 +157,12 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
public Set<Include> getIncludes(Set<Include> includes, Method targetMethod, Class<?> targetClass) {
|
||||
Log classLog = AnnotationUtil.getAnnotation(targetClass, Log.class);
|
||||
Set<Include> includeSet = new HashSet<>(includes);
|
||||
if (null != classLog) {
|
||||
if (classLog != null) {
|
||||
this.processInclude(includeSet, classLog);
|
||||
}
|
||||
// 方法级注解优先级高于类级注解
|
||||
Log methodLog = AnnotationUtil.getAnnotation(targetMethod, Log.class);
|
||||
if (null != methodLog) {
|
||||
if (methodLog != null) {
|
||||
this.processInclude(includeSet, methodLog);
|
||||
}
|
||||
return includeSet;
|
||||
|
||||
@@ -33,6 +33,15 @@ import java.util.Set;
|
||||
*/
|
||||
public interface LogHandler {
|
||||
|
||||
/**
|
||||
* 是否记录日志
|
||||
*
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
* @return 是否记录日志
|
||||
*/
|
||||
boolean isRecord(Method targetMethod, Class<?> targetClass);
|
||||
|
||||
/**
|
||||
* 开始日志记录
|
||||
*
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
package top.continew.starter.log.interceptor;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
@@ -26,7 +24,6 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.handler.LogHandler;
|
||||
import top.continew.starter.log.model.AccessLogContext;
|
||||
@@ -48,7 +45,6 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
private final LogProperties logProperties;
|
||||
private final LogHandler logHandler;
|
||||
private final LogDao logDao;
|
||||
private final TransmittableThreadLocal<Instant> timeTtl = new TransmittableThreadLocal<>();
|
||||
private final TransmittableThreadLocal<LogRecord.Started> logTtl = new TransmittableThreadLocal<>();
|
||||
|
||||
public LogInterceptor(LogProperties logProperties, LogHandler logHandler, LogDao logDao) {
|
||||
@@ -64,7 +60,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
Instant startTime = Instant.now();
|
||||
logHandler.accessLogStart(AccessLogContext.builder().startTime(startTime).properties(logProperties).build());
|
||||
// 开始日志记录
|
||||
if (this.isRequestRecord(handler, request)) {
|
||||
if (this.isRecord(handler)) {
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime);
|
||||
logTtl.set(startedLogRecord);
|
||||
}
|
||||
@@ -94,40 +90,20 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
log.error("Logging http log occurred an error: {}.", ex.getMessage(), ex);
|
||||
throw ex;
|
||||
} finally {
|
||||
timeTtl.remove();
|
||||
logTtl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否要记录日志
|
||||
* 是否记录日志
|
||||
*
|
||||
* @param handler 处理器
|
||||
* @return true:需要记录;false:不需要记录
|
||||
*/
|
||||
private boolean isRequestRecord(Object handler, HttpServletRequest request) {
|
||||
private boolean isRecord(Object handler) {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口被隐藏,不记录日志
|
||||
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
|
||||
if (null != methodOperation && methodOperation.hidden()) {
|
||||
return false;
|
||||
}
|
||||
Hidden methodHidden = handlerMethod.getMethodAnnotation(Hidden.class);
|
||||
if (null != methodHidden) {
|
||||
return false;
|
||||
}
|
||||
Class<?> handlerBeanType = handlerMethod.getBeanType();
|
||||
if (null != handlerBeanType.getDeclaredAnnotation(Hidden.class)) {
|
||||
return false;
|
||||
}
|
||||
// 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志
|
||||
Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
|
||||
if (null != methodLog && methodLog.ignore()) {
|
||||
return false;
|
||||
}
|
||||
Log classLog = handlerBeanType.getDeclaredAnnotation(Log.class);
|
||||
return null == classLog || !classLog.ignore();
|
||||
return logHandler.isRecord(handlerMethod.getMethod(), handlerMethod.getBeanType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
protected List<Field> getEncryptFields(Class<?> clazz) {
|
||||
return CLASS_FIELD_CACHE.computeIfAbsent(clazz, key -> Arrays.stream(ReflectUtil.getFields(clazz))
|
||||
.filter(field -> String.class.equals(field.getType()))
|
||||
.filter(field -> null != field.getAnnotation(FieldEncrypt.class))
|
||||
.filter(field -> field.getAnnotation(FieldEncrypt.class) != null)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@@ -144,6 +144,6 @@ public abstract class AbstractMyBatisInterceptor {
|
||||
*/
|
||||
public String getParameterName(Parameter parameter) {
|
||||
Param param = parameter.getAnnotation(Param.class);
|
||||
return null != param ? param.value() : parameter.getName();
|
||||
return param != null ? param.value() : parameter.getName();
|
||||
}
|
||||
}
|
||||
@@ -96,11 +96,11 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
private void encryptMap(Map<String, Object> parameterMap, MappedStatement mappedStatement) {
|
||||
Object parameter;
|
||||
// 别名带有 et(针对 MP 的 updateById、update 等方法)
|
||||
if (parameterMap.containsKey(Constants.ENTITY) && null != (parameter = parameterMap.get(Constants.ENTITY))) {
|
||||
if (parameterMap.containsKey(Constants.ENTITY) && (parameter = parameterMap.get(Constants.ENTITY)) != null) {
|
||||
this.encryptEntity(super.getEncryptFields(parameter), parameter);
|
||||
}
|
||||
// 别名带有 ew(针对 MP 的 UpdateWrapper、LambdaUpdateWrapper 等参数)
|
||||
if (parameterMap.containsKey(Constants.WRAPPER) && null != (parameter = parameterMap.get(Constants.WRAPPER))) {
|
||||
if (parameterMap.containsKey(Constants.WRAPPER) && (parameter = parameterMap.get(Constants.WRAPPER)) != null) {
|
||||
this.encryptUpdateWrapper(parameter, mappedStatement);
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor implem
|
||||
}
|
||||
if (parameterValue instanceof String str) {
|
||||
FieldEncrypt fieldEncrypt = encryptParameterMap.get(parameterName);
|
||||
if (null != fieldEncrypt) {
|
||||
if (fieldEncrypt != null) {
|
||||
parameterMap.put(parameterName, this.doEncrypt(str, fieldEncrypt));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package top.continew.starter.security.password.constant;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 密码编码器相关常量
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.12.0
|
||||
*/
|
||||
public class PasswordEncoderConstants {
|
||||
|
||||
/**
|
||||
* BCrypt 正则表达式
|
||||
*/
|
||||
public static final Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
|
||||
|
||||
private PasswordEncoderConstants() {
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package top.continew.starter.storage.model.req;
|
||||
*
|
||||
* @author echo
|
||||
* @date 2024/11/04 15:13
|
||||
**/
|
||||
*/
|
||||
public class StorageProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<!-- Graceful Response(Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量) -->
|
||||
<dependency>
|
||||
<groupId>com.feiniaojin</groupId>
|
||||
<artifactId>graceful-response</artifactId>
|
||||
|
||||
@@ -37,8 +37,6 @@ public class R<T> implements Response {
|
||||
|
||||
private static final ResponseStatusFactory RESPONSE_STATUS_FACTORY = SpringUtil
|
||||
.getBean(ResponseStatusFactory.class);
|
||||
private static final ResponseStatus DEFAULT_STATUS_SUCCESS = RESPONSE_STATUS_FACTORY.defaultSuccess();
|
||||
private static final ResponseStatus DEFAULT_STATUS_ERROR = RESPONSE_STATUS_FACTORY.defaultError();
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
@@ -144,7 +142,7 @@ public class R<T> implements Response {
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return Objects.equals(DEFAULT_STATUS_SUCCESS.getCode(), status.getCode());
|
||||
return Objects.equals(RESPONSE_STATUS_FACTORY.defaultSuccess().getCode(), status.getCode());
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
@@ -157,7 +155,7 @@ public class R<T> implements Response {
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok() {
|
||||
return new R(DEFAULT_STATUS_SUCCESS);
|
||||
return new R(RESPONSE_STATUS_FACTORY.defaultSuccess());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +165,7 @@ public class R<T> implements Response {
|
||||
* @return R /
|
||||
*/
|
||||
public static R ok(Object data) {
|
||||
return new R(DEFAULT_STATUS_SUCCESS, data);
|
||||
return new R(RESPONSE_STATUS_FACTORY.defaultSuccess(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,7 +187,7 @@ public class R<T> implements Response {
|
||||
* @return R /
|
||||
*/
|
||||
public static R fail() {
|
||||
return new R(DEFAULT_STATUS_ERROR);
|
||||
return new R(RESPONSE_STATUS_FACTORY.defaultError());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,6 @@ package top.continew.starter.web.util;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
@@ -104,11 +103,11 @@ public class FileUploadUtils {
|
||||
public static void download(HttpServletResponse response,
|
||||
InputStream inputStream,
|
||||
String fileName) throws IOException {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
response.setContentLength(bytes.length);
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLUtil.encode(fileName));
|
||||
IoUtil.write(response.getOutputStream(), true, bytes);
|
||||
try (inputStream; var outputStream = response.getOutputStream()) {
|
||||
response.setContentLengthLong(inputStream.transferTo(outputStream));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ public class SpringWebUtils {
|
||||
final ResourceHandlerRegistry resourceHandlerRegistry = new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
|
||||
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
|
||||
// 移除之前注册的映射
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(CharSequenceUtil.removeSuffix(entry
|
||||
.getKey(), StringConstants.SLASH), StringConstants.PATH_PATTERN);
|
||||
oldHandlerMap.remove(pathPattern);
|
||||
// 重新注册映射
|
||||
String resourceLocations = CharSequenceUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
|
||||
|
||||
13
pom.xml
13
pom.xml
@@ -38,18 +38,19 @@
|
||||
<module>continew-starter-json</module>
|
||||
<module>continew-starter-api-doc</module>
|
||||
<module>continew-starter-web</module>
|
||||
<module>continew-starter-auth</module>
|
||||
<module>continew-starter-data</module>
|
||||
<module>continew-starter-cache</module>
|
||||
<module>continew-starter-security</module>
|
||||
<module>continew-starter-ratelimiter</module>
|
||||
<module>continew-starter-idempotent</module>
|
||||
<module>continew-starter-trace</module>
|
||||
<module>continew-starter-log</module>
|
||||
<module>continew-starter-storage</module>
|
||||
<module>continew-starter-file</module>
|
||||
<module>continew-starter-captcha</module>
|
||||
<module>continew-starter-cache</module>
|
||||
<module>continew-starter-data</module>
|
||||
<module>continew-starter-auth</module>
|
||||
<module>continew-starter-messaging</module>
|
||||
<module>continew-starter-log</module>
|
||||
<module>continew-starter-file</module>
|
||||
<module>continew-starter-storage</module>
|
||||
<module>continew-starter-license</module>
|
||||
<module>continew-starter-extension</module>
|
||||
</modules>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user