commit 63c8d8b757bfd4ba96d089f209c48b35d61914d3 Author: xujun Date: Thu Aug 6 15:19:45 2026 +0800 初始化项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df985dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# ---> Project + +# logs +**/logs/ + +# ai +.claude + +# ---> Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# ---> Eclipse +.settings/ +.project +.classpath + +# ---> Idea +.idea/ +*.iml \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd45492 --- /dev/null +++ b/LICENSE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright 2026 xujun + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a89ca5b --- /dev/null +++ b/README.md @@ -0,0 +1,1078 @@ +# xtools-boot 项目设计文档 + +## 一、功能和用途 + +### 1.1 项目概述 + +- **项目名称**:xtools-boot +- **项目版本**:5.0.0 +- **父POM**:org.xujun:xtools-parent-boot:5.0.0 +- **项目定位**:低调大师工具箱,SpringBoot工具模块,适配JDK25。提供企业级应用开发所需的通用功能模块,涵盖缓存、数据库、搜索、消息队列、任务调度、日志、存储、脱敏等核心能力,以Spring + Boot Starter形式封装,支持按需引入。 + +### 1.2 技术特点 + +- 采用最新的 JDK 25 版本,充分利用新特性(虚拟线程、结构化并发等) +- 基于 Spring Boot 4.0.5 构建,支持自动配置和快速开发 +- 集成 Spring Framework 7.0.6 核心框架 +- 使用 MyBatis 4.0.1 + MyBatis-Plus 3.5.16 简化数据访问层开发 +- 集成 Spring Data Elasticsearch 6.0.4 + Elasticsearch Client 9.2.6 实现日志存储和检索 +- 集成 Spring AMQP 4.0.2 + RabbitMQ AMQP Client 5.27.1 实现异步消息处理 +- 使用 Spring Data Redis 4.0.4 + Lettuce 6.8.2.RELEASE 实现分布式缓存 +- 集成 XXL-JOB 3.4.0 实现分布式任务调度 +- 集成 AWS S3 SDK 2.42.34 实现对象存储 +- 集成 Knife4j 4.5.0 自动生成 API 文档 +- 使用 BouncyCastle 1.84 提供加密支持 +- 使用 Druid 1.2.28 数据库连接池 +- 使用 FastJSON2 2.0.60 进行 JSON 序列化 +- 使用 MapStruct 1.6.3 进行对象映射 +- 使用 Lombok 1.18.44 简化代码 +- 使用 Velocity 2.4.1 模板引擎 +- 使用 Caffeine 3.2.3 本地缓存 +- 使用 ip2region 3.3.7 实现IP地址定位 +- 使用 OSHI 6.11.1 实现系统监控 +- 使用 Fesod Sheet 2.0.1-incubating 处理Excel + +### 1.3 核心功能 + +```mermaid +graph TB + subgraph 基础模块 + A1[xtools-boot-api
API定义层] + A2[xtools-boot-core
核心工具层] + end + + subgraph 数据模块 + B1[xtools-boot-db
数据库模块] + B2[xtools-boot-cache
缓存模块] + B3[xtools-boot-elasticsearch
搜索引擎模块] + end + + subgraph 中间件模块 + C1[xtools-boot-mq
消息队列模块] + C2[xtools-boot-task
任务管理模块] + C3[xtools-boot-job
任务调度模块] + C4[xtools-boot-thread
线程管理模块] + end + + subgraph 功能模块 + D1[xtools-boot-log
日志模块] + D2[xtools-boot-web
Web模块] + D3[xtools-boot-knife4j
API文档模块] + D4[xtools-boot-ip
IP定位模块] + D5[xtools-boot-mask
数据脱敏模块] + D6[xtools-boot-storage
存储模块] + end + + B1 --> B1a[xtools-boot-db-mybatis
MyBatis集成] + B1 --> B1b[xtools-boot-db-mybatis-plus
MyBatis-Plus集成] + B2 --> B2a[xtools-boot-cache-redis
Redis缓存] + C1 --> C1a[xtools-boot-mq-base
消息总线] + C1 --> C1b[xtools-boot-mq-rabbit
RabbitMQ实现] + C3 --> C3a[xtools-boot-job-xxl
XXL-JOB集成] + D6 --> D6a[xtools-boot-storage-base
存储基础] + D6 --> D6b[xtools-boot-storage-file
文件存储] + D6 --> D6c[xtools-boot-storage-s3
S3对象存储] + D2 --> D2a[xtools-boot-web-base
Web基础] + D2 --> D2b[xtools-boot-web-filter
Web过滤器] +``` + +```mermaid +mindmap + root((xtools-boot
SpringBoot工具箱)) + 基础模块 + xtools-boot-api + 基础实体BaseEntity + 统一枚举BaseEnum + 异常体系 + 日志链路追踪 + xtools-boot-core + Spring上下文工具 + 树形结构工具 + 枚举工具 + 任务执行接口 + 模块加载追踪 + 数据模块 + 数据库 + MyBatis集成 + MyBatis-Plus集成 + Druid连接池 + 数据库监控 + 缓存 + Redis缓存 + 缓存监控 + 搜索引擎 + Elasticsearch集成 + ES查询工具 + ES集群监控 + 中间件模块 + 消息队列 + 消息总线MqBus + RabbitMQ集成 + 虚拟线程消费 + 错误处理 + 任务管理 + TaskBus任务总线 + 任务状态追踪 + 异步任务执行 + 任务调度 + XXL-JOB集成 + 自动注册执行器 + 线程管理 + 虚拟线程支持 + 线程回调机制 + 功能模块 + 日志 + LogBus日志总线 + 链路追踪LogTrack + 多级别日志 + 主子线程区分 + Web + MVC配置 + 请求过滤器 + 日志链路集成 + API文档 + Knife4j增强UI + OpenAPI 3集成 + IP定位 + ip2region离线查询 + 数据脱敏 + 注解式脱敏 + 多类型敏感数据 + 自定义脱敏策略 + 存储 + 本地文件存储 + AWS S3对象存储 + 统一存储接口 +``` + +### 1.4 功能关系图 + +```mermaid +graph LR + Client[客户端请求] --> Web[xtools-boot-web] + Web --> Filter[请求过滤器] + Filter --> LogTrack[日志链路追踪] + LogTrack --> Log[xtools-boot-log] + Log --> LogBus[LogBus日志总线] + Web --> Controller[业务Controller] + Controller --> Cache[xtools-boot-cache] + Controller --> DB[xtools-boot-db] + Cache --> Redis[Redis] + DB --> MySQL[MySQL] + Controller --> MQ[xtools-boot-mq] + MQ --> RabbitMQ[RabbitMQ] + MQ --> Thread[xtools-boot-thread] + Thread --> VThread[虚拟线程] + Controller --> Task[xtools-boot-task] + Task --> TaskBus[TaskBus任务总线] + Job[xtools-boot-job] --> XXLJob[XXL-JOB] + Controller --> ES[xtools-boot-elasticsearch] + ES --> Elasticsearch[Elasticsearch] + Controller --> Storage[xtools-boot-storage] + Storage --> File[本地文件/S3] + Controller --> Mask[xtools-boot-mask] + Log --> Doc[xtools-boot-knife4j] +``` + +## 二、项目结构设计 + +### 2.1 整体架构 + +```mermaid +graph TD + App[xtools-boot
父模块] --> API[xtools-boot-api
API定义模块] + App --> Core[xtools-boot-core
核心工具模块] + App --> DB[xtools-boot-db
数据库模块] + App --> Cache[xtools-boot-cache
缓存模块] + App --> ES[xtools-boot-elasticsearch
搜索引擎模块] + App --> MQ[xtools-boot-mq
消息队列模块] + App --> Task[xtools-boot-task
任务管理模块] + App --> Job[xtools-boot-job
任务调度模块] + App --> Thread[xtools-boot-thread
线程管理模块] + App --> Log[xtools-boot-log
日志模块] + App --> Web[xtools-boot-web
Web模块] + App --> Knife4j[xtools-boot-knife4j
API文档模块] + App --> IP[xtools-boot-ip
IP定位模块] + App --> Mask[xtools-boot-mask
数据脱敏模块] + App --> Storage[xtools-boot-storage
存储模块] + DB --> DBMybatis[xtools-boot-db-mybatis] + DB --> DBMP[xtools-boot-db-mybatis-plus] + Cache --> CacheRedis[xtools-boot-cache-redis] + MQ --> MQBase[xtools-boot-mq-base] + MQ --> MQRabbit[xtools-boot-mq-rabbit] + Job --> JobXXL[xtools-boot-job-xxl] + Web --> WebBase[xtools-boot-web-base] + Web --> WebFilter[xtools-boot-web-filter] + Storage --> StorageBase[xtools-boot-storage-base] + Storage --> StorageFile[xtools-boot-storage-file] + Storage --> StorageS3[xtools-boot-storage-s3] +``` + +### 2.2 分层架构 + +```mermaid +flowchart TB + subgraph 自动配置层 + A1[AutoConfiguration
自动配置注册] + A2[ImportSelector
导入选择器] + A3[Configuration
配置类] + end + + subgraph 接口定义层 + B1[BaseEnum
统一枚举接口] + B2[BaseEntity
基础实体] + B3[LogBusInterface
日志处理接口] + B4[StorageService
存储服务接口] + B5[JobInterface
任务执行接口] + B6[BaseTaskType
任务类型接口] + B7[MaskCustom
自定义脱敏接口] + end + + subgraph 基础功能层 + C1[SpringContextUtils
Spring上下文工具] + C2[TreeUtils
树形结构工具] + C3[EnumUtils
枚举工具] + C4[RedisUtils
Redis工具] + C5[EsUtils/EsQueryUtils
ES工具] + C6[MqBus
消息总线] + C7[LogBus
日志总线] + C8[TaskBus
任务总线] + end + + subgraph 扩展功能层 + D1[MySqlMonitor
MySQL监控] + D2[RedisMonitor
Redis监控] + D3[ElasticsearchMonitor
ES监控] + D4[DefaultMaskHandle
默认脱敏处理] + D5[BaseMqHandle
消息处理基类] + D6[BaseErrorHandle
错误处理基类] + end + + A1 --> A2 + A2 --> A3 + A3 --> B1 + B1 --> C1 + C1 --> D1 +``` + +### 2.3 模块职责 + +| 模块 | 职责 | 核心类 | +|-----------------------------|---------------------------------------------------------|---------------------------------------------| +| xtools-boot-api | API定义层,提供基础实体、枚举、异常、日志链路等公共定义 | BaseEntity、BaseEnum、BizError、LogTrack | +| xtools-boot-core | 核心工具层,提供Spring上下文、树形结构、枚举等通用工具 | SpringContextUtils、TreeUtils、EnumUtils | +| xtools-boot-db-mybatis | MyBatis集成,提供数据库访问、监控、慢查询检测 | MyBatisConfig、MySqlMonitor | +| xtools-boot-db-mybatis-plus | MyBatis-Plus集成,提供分页插件、查询工具 | MybatisPlusConfig、QueryUtils | +| xtools-boot-cache-redis | Redis缓存,提供缓存操作、监控 | RedisUtils、RedisMonitor | +| xtools-boot-elasticsearch | ES集成,提供搜索、查询、集群监控 | EsUtils、EsQueryUtils、ElasticsearchMonitor | +| xtools-boot-mq-base | 消息总线,提供消息发布、处理、错误处理 | MqBus、BaseMqHandle、BaseErrorHandle | +| xtools-boot-mq-rabbit | RabbitMQ实现,提供消息监听、消费 | BootRabbitMqConfiguration | +| xtools-boot-task | 任务管理,提供任务总线、状态追踪 | TaskBus、TaskInfo、TaskStatus | +| xtools-boot-job-xxl | XXL-JOB集成,提供分布式任务调度 | BootXxlJobConfiguration、InitXxlJob | +| xtools-boot-thread | 线程管理,提供虚拟线程、回调机制 | BootThreadConfiguration | +| xtools-boot-log | 日志模块,提供日志总线、链路追踪 | LogBus、LogTrackHolder、LogBody | +| xtools-boot-web-base | Web基础,提供MVC配置、过滤器、转换器 | BootWebBaseConfiguration、CommonFilter | +| xtools-boot-web-filter | Web过滤器,提供过滤器扫描注册 | BootWebFilterConfiguration | +| xtools-boot-knife4j | API文档,提供Knife4j/OpenAPI3集成 | BootKnife4jConfiguration | +| xtools-boot-ip | IP定位,提供离线IP地址查询 | BootIpConfiguration | +| xtools-boot-mask | 数据脱敏,提供注解式敏感数据处理 | DefaultMaskHandle、MaskType | +| xtools-boot-storage-base | 存储基础,提供统一存储接口 | StorageService | +| xtools-boot-storage-file | 文件存储,提供本地文件系统存储 | StorageServiceFileImpl | +| xtools-boot-storage-s3 | S3存储,提供AWS S3对象存储 | BootStorageS3Configuration | + +### 2.4 包结构设计 + +``` +xtools.boot.{module} +├── Boot{Module}Configuration.java # 配置类(自动配置入口) +├── selector/ # 导入选择器 +│ └── Boot{Module}ImportSelector.java +├── config/ # 配置 +├── interfaces/ # 接口定义 +│ ├── *Interface.java # 功能接口 +│ └── *Type.java # 类型接口 +├── model/ # 数据模型 +│ ├── dto/ # 数据传输对象 +│ └── entity/ # 实体类 +├── enums/ # 枚举类 +├── utils/ # 工具类 +├── handle/ # 处理器 +├── monitor/ # 监控 +├── holder/ # 线程持有者 +├── callback/ # 回调 +├── init/ # 初始化 +├── service/ # 服务 +│ └── impl/ # 服务实现 +└── filter/ # 过滤器 +``` + +### 2.5 模块依赖关系 + +```mermaid +graph LR + API[xtools-boot-api] --> Extend[xtools-extend] + API --> WebCore[xtools-web] + Core[xtools-boot-core] --> API + Core --> Extend + DBMybatis[xtools-boot-db-mybatis] --> Core + DBMybatis --> Log[xtools-boot-log] + DBMybatis --> Thread[xtools-boot-thread] + DBMP[xtools-boot-db-mybatis-plus] --> Core + CacheRedis[xtools-boot-cache-redis] --> Core + ES[xtools-boot-elasticsearch] --> Extend + ES --> Core + ES --> Log + MQBase[xtools-boot-mq-base] --> Core + MQBase --> Log + MQBase --> Thread + MQRabbit[xtools-boot-mq-rabbit] --> Core + MQRabbit --> MQBase + Task[xtools-boot-task] --> Core + JobXXL[xtools-boot-job-xxl] --> Core + Log --> Core + Thread --> Core + IP[xtools-boot-ip] --> Extend + IP --> Core + Knife4j[xtools-boot-knife4j] --> Core + Mask[xtools-boot-mask] --> Core + StorageBase[xtools-boot-storage-base] --> Core + StorageFile[xtools-boot-storage-file] --> Core + StorageFile --> StorageBase + StorageS3[xtools-boot-storage-s3] --> Extend + StorageS3 --> Core + StorageS3 --> StorageBase + WebBase[xtools-boot-web-base] --> WebCore + WebBase --> Core + WebBase --> Log + WebBase --> WebFilter[xtools-boot-web-filter] + WebFilter --> Core +``` + +## 三、项目功能设计 + +### 3.1 自动配置机制 + +```mermaid +flowchart TD + A[应用启动] --> B[Spring Boot扫描
META-INF/spring/*.imports] + B --> C[加载自动配置类
BootModuleConfiguration] + C --> D[执行构造函数
ModuleLoadUtils.loadSuccess] + D --> E["@Import导入
XXXImportSelector"] + E --> F[ImportBeanDefinitionRegistrar
注册Bean定义] + F --> G["@ConditionalOnProperty
条件化创建Bean"] + G --> H[模块就绪] +``` + +**自动配置设计**: + +- 每个模块通过 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 注册自动配置类 +- 配置类通过 `@Import(XXXImportSelector.class)` 导入自定义选择器 +- 选择器实现 `ImportBeanDefinitionRegistrar` 接口,按包名扫描注册Bean +- 使用 `@ConditionalOnProperty` 实现条件化Bean创建 +- 构造函数中调用 `ModuleLoadUtils.loadSuccess()` 记录模块加载状态 + +### 3.2 统一枚举设计 + +```mermaid +flowchart TD + A[BaseEnum接口] --> B[int code
枚举编码] + A --> C[String desc
枚举描述] + A --> D["BaseEnum[] all()
获取所有枚举"] + B --> E[StatusEnum
状态枚举] + B --> F[DeleteEnum
删除枚举] + B --> G[ThreadType
线程类型] + B --> H[FileDataType
文件类型] + B --> I[TaskStatus
任务状态] + B --> J[MaskType
脱敏类型] + B --> K[MySqlMonitorEnums
MySQL监控枚举] + B --> L[ElasticsearchMonitorEnums
ES监控枚举] +``` + +**枚举规范**: + +- 所有业务枚举实现 `BaseEnum` 接口 +- 提供 `code()` 和 `desc()` 方法 +- 提供静态 `valueOf(int code)` 和 `valueOfDesc(String desc)` 方法 +- 使用 `EnumUtils` 工具类进行枚举转换 + +### 3.3 异常处理设计 + +```mermaid +flowchart TD + A[CommonException
基础异常] --> B[BizError
业务异常] + A --> C[BizWarning
业务警告] + A --> D[UnauthorizedError
未授权异常] + A --> E[BizPublicKeyError
公钥异常] + F[BootError
错误码枚举] --> F1[code
错误编码] + F --> F2[type
错误类型] + F --> F3[module
所属模块] + F --> F4[messageTemplate
消息模板] + G[BootErrorModule
错误模块枚举] --> G1[API
API模块] + G --> G2[CORE
核心模块] + G --> G3[LOG
日志模块] + G --> G4[MQ
消息队列模块] +``` + +**异常体系**: + +- `CommonException`:基础异常(来自xtools-core) +- `BizError`:业务逻辑异常,使用错误码和消息模板 +- `BizWarning`:业务警告异常 +- `UnauthorizedError`:认证授权异常 +- `BizPublicKeyError`:公钥相关异常 +- `BootError`:错误码枚举,包含code、type、module、messageTemplate +- `BootErrorModule`:错误模块枚举,用于错误分类 + +### 3.4 日志管理设计 + +```mermaid +flowchart TD + A[业务代码] --> B[LogBus.createLog
创建日志] + B --> C[LogBody
日志体] + C --> C1[title
日志主题] + C --> C2[level
日志级别] + C --> C3[type
日志类型] + C --> C4[logTrack
链路追踪] + C --> C5[runInfo
运行环境] + C --> C6[stackTrace
堆栈信息] + C --> C7[logData
日志数据] + C --> C8[err
异常文本] + B --> D[LogBusInterface实现类
日志处理器] + D --> E[写入Elasticsearch] + D --> F[写入数据库] + D --> G[发送到消息队列] +``` + +```mermaid +flowchart LR + A[HTTP请求] --> B[LogTrackConfig
生成LogTrack] + B --> C[LogTrackHolder
线程绑定] + C --> D[业务处理] + D --> E{是否异步线程?} + E -->|是| F[传递LogTrack到子线程] + E -->|否| G[直接使用] + F --> H[子线程处理] + G --> I[响应返回] + H --> I +``` + +**日志功能**: + +- **LogBus**:日志总线,统一的日志创建和管理入口 +- **LogTrack**:链路追踪信息,包含traceId、spanId、线程类型等 +- **HolderLogTrack**:线程持有者的链路追踪,支持主子线程传递 +- **LogBusInterface**:日志处理接口,支持自定义日志处理器 +- **LogBusType**:日志类型接口,定义日志基础编码 +- **LogBody**:日志体,包含主题、级别、类型、链路、环境、堆栈、数据、异常 +- **LogBusConfig**:日志总线配置,控制日志打印和堆栈包含 +- **RunInfoConfig**:运行环境信息配置 +- **LogLevel**:日志级别(来自xtools-core) + +### 3.5 消息队列设计 + +```mermaid +flowchart TD + A[业务代码] --> B[MqBus.publish
发布消息] + B --> C[MessageDto
消息体] + C --> C1[log
LogTrack链路追踪] + C --> C2[data
消息数据] + B --> D[RabbitMQ Exchange] + D --> E[Queue] + E --> F["@RabbitListener
消息监听器"] + F --> G[BaseMqHandle
消息处理基类] + G --> H{处理是否成功?} + H -->|成功| I[记录处理结果] + H -->|失败| J[BaseErrorHandle
错误处理] + J --> K[重试/记录错误日志] +``` + +**消息队列功能**: + +- **MqBus**:消息总线,统一的消息发布入口 +- **BaseMqHandle**:消息处理基类,定义消息处理模板 +- **BaseErrorHandle**:错误处理基类,定义错误处理策略 +- **InitMq**:自动初始化,启动时扫描并注册消息监听器 +- **MessageDto**:消息体,包含LogTrack链路追踪和消息数据 +- **MqEnums**:消息队列相关枚举 +- **RabbitMqParams**:RabbitMQ参数配置 +- 支持虚拟线程消费消息 +- 消息序列化使用FastJSON2 + +### 3.6 任务管理设计 + +```mermaid +flowchart TD + A[TaskBus] --> B["new TaskBus()
创建任务总线"] + B --> C["taskType(BaseTaskType)
设置任务类型"] + C --> D["taskData(Object)
设置任务数据"] + D --> E["execute()
执行任务"] + E --> F{任务执行} + F --> G[TaskStatus.ING
执行中] + G --> H{执行结果} + H -->|成功| I[TaskStatus.SUCCESS] + H -->|失败| J[TaskStatus.ERROR] +``` + +**任务管理功能**: + +- **TaskBus**:任务总线,提供Builder模式创建和管理任务 +- **TaskInfo**:任务信息,包含任务类型、任务数据、状态等 +- **TaskStatus**:任务状态枚举(ING、SUCCESS、ERROR) +- **BaseTaskType**:任务类型接口,定义任务类型 +- **TaskBusInterface**:任务总线接口 + +### 3.7 任务调度设计 + +```mermaid +flowchart TD + A[应用启动] --> B[BootXxlJobConfiguration
XXL-JOB配置] + B --> C["@ConditionalOnProperty
条件化加载"] + C --> D[InitXxlJob
初始化XXL-JOB] + D --> E[扫描JobHandler] + E --> F[注册到XXL-JOB调度中心] + F --> G[调度中心触发任务] + G --> H[JobInterface实现类
执行任务] +``` + +**任务调度功能**: + +- **BootXxlJobConfiguration**:XXL-JOB自动配置 +- **InitXxlJob**:XXL-JOB初始化,自动扫描和注册任务处理器 +- **JobInterface**:任务执行接口,定义任务执行契约 +- 通过 `@ConditionalOnProperty` 条件化加载 +- 支持分布式任务调度 + +### 3.8 线程管理设计 + +```mermaid +flowchart TD + A[BootThreadConfiguration
线程配置] --> B[虚拟线程支持] + A --> C[VirtualThreadTaskCallback
虚拟线程任务回调] + B --> D[MQ消息消费使用虚拟线程] + B --> E[异步任务使用虚拟线程] + C --> F[任务执行前回调] + C --> G[任务执行后回调] + C --> H[异常处理回调] +``` + +**线程管理功能**: + +- **BootThreadConfiguration**:线程配置,注册虚拟线程相关Bean +- **VirtualThreadTaskCallback**:虚拟线程任务回调接口,提供执行前、执行后、异常处理回调 +- 支持在消息消费和异步任务中使用虚拟线程 + +### 3.9 缓存设计 + +```mermaid +flowchart LR + A[业务代码] --> B[RedisUtils
Redis工具类] + B --> C{Redis操作} + C --> D[RedisMonitor
缓存监控] + C --> E[读写缓存] + C --> F[设置过期时间] + E --> G[Lettuce客户端] + F --> G + G --> H[Redis Server] +``` + +**缓存功能**: + +- **BootCacheRedisConfiguration**:Redis自动配置 +- **RedisUtils**:Redis工具类,封装常用Redis操作 +- **RedisMonitor**:Redis监控,提供缓存统计和健康检查 +- 使用Lettuce作为Redis客户端 + +### 3.10 数据库设计 + +```mermaid +flowchart TD + subgraph xtools-boot-db-mybatis + A1[MyBatisConfig
MyBatis配置] + A2[MySqlMonitor
MySQL监控] + A3[MonitorDatabasesMapper
监控Mapper] + A4[Druid连接池] + end + + subgraph xtools-boot-db-mybatis-plus + B1[MybatisPlusConfig
MP配置] + B2[QueryUtils
查询工具] + end + + A1 --> C[MyBatis 4.0.1] + B1 --> D[MyBatis-Plus 3.5.16] + A4 --> E[Druid 1.2.28] + B2 --> F[分页插件
时间范围过滤] +``` + +**数据库功能**: + +- **MyBatisConfig**:MyBatis核心配置 +- **MybatisPlusConfig**:MyBatis-Plus配置,注册分页插件 +- **QueryUtils**:查询工具类,提供时间范围过滤等通用查询方法 +- **MySqlMonitor**:MySQL监控,检测数据库连接、慢查询等 +- **MonitorDatabasesMapper**:数据库监控Mapper +- **MySqlMonitorEnums**:监控指标枚举 +- 使用Druid连接池管理数据库连接 + +### 3.11 搜索引擎设计 + +```mermaid +flowchart TD + A[业务代码] --> B[EsUtils
ES HTTP工具] + A --> C[EsQueryUtils
ES查询工具] + B --> D[索引操作] + B --> E[文档操作] + B --> F[集群操作] + C --> G[构建查询] + C --> H[聚合查询] + C --> I[分页查询] + J[ElasticsearchMonitor
ES监控] --> K[集群健康] + J --> L[索引状态] + J --> M[节点信息] + J --> N[ElasticsearchMonitorEnums
监控枚举] +``` + +**搜索引擎功能**: + +- **BootElasticsearchConfiguration**:ES自动配置 +- **EsUtils**:Elasticsearch HTTP客户端工具,提供索引、文档、集群操作 +- **EsQueryUtils**:ES查询工具,提供通用查询构建 +- **ElasticsearchMonitor**:ES集群监控 +- **ElasticsearchMonitorEnums**:监控指标枚举 + +### 3.12 数据脱敏设计 + +```mermaid +flowchart TD + A["实体类字段
@Mask注解"] --> B[MaskType
脱敏类型] + B --> B1[CHINESE_NAME
中文姓名] + B --> B2[ID_CARD
身份证号] + B --> B3[MOBILE_PHONE
手机号] + B --> B4[EMAIL
邮箱] + B --> B5[BANK_CARD
银行卡] + B --> B6[PASSWORD
密码] + B --> B7[ADDRESS
地址] + B --> B8[CUSTOM
自定义] + A --> C["配置参数
prefixNoMaskLen
suffixNoMaskLen
maskChar"] + C --> D[DefaultMaskHandle
默认脱敏处理] + D --> E[MaskCustom
自定义脱敏接口] + E --> F[Jackson序列化时
自动脱敏] +``` + +**数据脱敏功能**: + +- **BootMaskConfiguration**:脱敏模块自动配置 +- **MaskType**:脱敏类型枚举,支持8种内置类型 +- **DefaultMaskHandle**:默认脱敏处理器,根据类型自动脱敏 +- **MaskCustom**:自定义脱敏接口,支持自定义脱敏逻辑 +- 基于Jackson序列化,在JSON输出时自动脱敏 +- 支持配置前缀保留长度、后缀保留长度、脱敏字符 + +### 3.13 存储设计 + +```mermaid +flowchart TD + A[StorageService
统一存储接口] --> A1[exists
文件是否存在] + A --> A2[save
保存文件] + A --> A3[get
获取文件] + A --> A4[del
删除文件] + B[StorageServiceFileImpl
文件存储实现] --> C[本地文件系统] + D[BootStorageS3Configuration
S3存储配置] --> E[AWS S3] + F["@ConditionalOnProperty
storage.type=file"] --> B + G["@ConditionalOnProperty
storage.type=s3"] --> D + A2 --> A2a[bucket
桶名称] + A2 --> A2b[fileName
文件名] + A2 --> A2c[inputStream
输入流] + A2 --> A2d[contentLength
内容长度] +``` + +**存储功能**: + +- **StorageService**:统一存储接口,定义exists、save、get、del操作 +- **BootStorageBaseConfiguration**:存储基础配置 +- **StorageServiceFileImpl**:本地文件系统存储实现 +- **BootStorageS3Configuration**:AWS S3对象存储配置 +- **FileStorageConfig**:文件存储配置,支持配置存储路径 +- 通过 `@ConditionalOnProperty` 选择存储实现 + +### 3.14 Web模块设计 + +```mermaid +flowchart TD + A[xtools-boot-web] --> B[xtools-boot-web-base] + A --> C[xtools-boot-web-filter] + B --> B1[BootWebBaseConfiguration
Web基础配置] + B --> B2[CommonFilter
通用过滤器] + B --> B3[MvcConverterConfig
MVC转换器配置] + B --> B4[LogTrackConfig
日志链路配置] + C --> C1[BootWebFilterConfiguration
过滤器配置] + C --> C2[BootWebFilterImportSelector
过滤器导入选择器] + B4 --> D[生成LogTrack] + D --> E[绑定到ThreadLocal] + E --> F[请求处理] + F --> G[清除ThreadLocal] +``` + +**Web模块功能**: + +- **BootWebBaseConfiguration**:Web基础配置,集成xtools-web +- **CommonFilter**:通用请求过滤器,处理请求上下文 +- **MvcConverterConfig**:MVC转换器配置 +- **LogTrackConfig**:日志链路配置,为HTTP请求生成和管理LogTrack +- **BootWebFilterConfiguration**:Web过滤器配置 + +### 3.15 API文档设计 + +- **BootKnife4jConfiguration**:Knife4j自动配置 +- 集成 springdoc-openapi-starter-webmvc-ui 3.0.3 +- 集成 knife4j-openapi3-jakarta-spring-boot-starter 4.5.0 +- 使用 Swagger Annotations 2.2.48 定义API文档 +- 使用 `@Schema` 注解标注实体字段描述 + +### 3.16 IP定位设计 + +- **BootIpConfiguration**:IP定位自动配置 +- 使用 ip2region 3.3.7 离线IP地址库 +- 内置 `ip/ip2region_v4.xdb` 数据文件 +- 支持IPv4地址的地理定位查询 + +## 四、编码规范设计 + +### 4.1 命名规范 + +**类命名**: + +| 类型 | 命名规则 | 示例 | +|------------|-------------------------------------------|-----------------------------------------| +| 配置类 | Boot{Module}Configuration | BootCoreConfiguration | +| 导入选择器 | Boot{Module}ImportSelector | BootCoreImportSelector | +| 工具类 | {功能}Utils | SpringContextUtils | +| 监控类 | {组件}Monitor | MySqlMonitor | +| 枚举类 | {功能}Enum / {功能}Enums | StatusEnum、MySqlMonitorEnums | +| 异常类 | Biz{类型}Error | BizError、BizWarning | +| 接口 | Base{类型} / {功能}Interface / {功能}Type | BaseEnum、LogBusInterface、BaseTaskType | +| DTO类 | {功能}Dto / {功能}Info / {功能}Body | MessageDto、TaskInfo、LogBody | +| 回调类 | {功能}Callback | VirtualThreadTaskCallback | +| 初始化类 | Init{功能} | InitMq、InitXxlJob | +| 处理器 | {功能}Handle / Default{功能}Handle | BaseMqHandle、DefaultMaskHandle | + +**方法命名**: + +| 操作 | 命名规则 | 示例 | +|--------|-------------------------|------------------------------| +| 查询 | get / find / query | getBean、findById、queryList | +| 创建 | create / save / publish | createLog、save、publish | +| 删除 | delete / del / remove | deleteById、del | +| 判断 | is / has / exists | exists、isValid | +| 转换 | to / convert / of | valueOf、convert | +| 初始化 | init | init | +| 加载 | load | loadSuccess | +| 执行 | execute / run | execute | + +**变量命名**: + +| 类型 | 命名规则 | 示例 | +|----------|------------------|---------------------------| +| 普通变量 | camelCase | logTrack、taskInfo | +| 常量 | UPPER_SNAKE_CASE | CP_NUM0、CP_NUM50 | +| Boolean | is/has前缀 | isValid、hasChildren | +| 集合 | 复数形式 | items、list | +| 配置属性 | camelCase | prefixNoMaskLen、maskChar | + +### 4.2 注释规范 + +**类注释格式**: + +```java +/** + *

Title : 类名称

+ *

Description : 类描述

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/17 18:50 + */ +``` + +**方法注释格式**: + +```java +/** + * 方法描述 + * + * @param param 参数说明 + * @return 返回值说明 + */ +``` + +**字段注释格式**: + +```java +/** + * 字段描述 + */ +private String fieldName; +``` + +### 4.3 代码风格 + +- 使用 **Lombok** 简化代码(@Data、@Getter、@RequiredArgsConstructor) +- 使用 **MapStruct** 进行对象转换 +- 使用 **FastJSON2** 进行JSON序列化 +- 使用 **构造器注入**(通过Lombok的@RequiredArgsConstructor) +- 使用 **@Schema** 注解标注API文档 +- 使用 **@ConfigurationProperties** 进行配置绑定 +- 使用 **@ConditionalOnProperty** 进行条件化配置 +- 使用 **@Import + ImportSelector** 实现自动配置 +- 模块化设计,每个功能独立成模块 + +### 4.4 设计规范 + +- **分层原则**:接口定义层 → 基础功能层 → 扩展功能层 +- **单一职责**:每个模块专注单一功能领域 +- **开闭原则**:通过接口(LogBusInterface、StorageService、MaskCustom)支持扩展 +- **依赖倒置**:面向接口编程,通过配置选择实现 +- **模块化**:功能独立封装,按需引入 + +### 4.5 安全规范 + +- 数据脱敏:支持身份证、手机号、邮箱、银行卡等敏感数据自动脱敏 +- 加密支持:集成BouncyCastle加密库 +- SQL安全:使用MyBatis参数化查询,防止SQL注入 +- 数据验证:集成Jakarta Validation(@NotNull、@Valid) +- JWT支持:集成java-jwt 4.5.1 + +## 五、项目依赖设计 + +### 5.1 核心框架依赖 + +| 依赖 | 版本 | 用途 | +|---------------------------|----------|---------------------| +| Spring Boot | 4.0.5 | 应用框架 | +| Spring Framework | 7.0.6 | 核心框架 | +| Spring Security | 7.0.4 | 安全框架 | +| Spring AMQP | 4.0.2 | RabbitMQ集成 | +| Spring Data BOM | 2025.1.4 | Spring Data版本管理 | +| Spring Data Redis | 4.0.4 | Redis集成 | +| Spring Data Elasticsearch | 6.0.4 | Elasticsearch集成 | +| Jakarta Servlet | 6.1.0 | Servlet API | +| Jakarta Validation | 3.1.1 | 参数校验 | + +### 5.2 数据库相关依赖 + +| 依赖 | 版本 | 用途 | +|-----------------------------|--------|--------------| +| MyBatis Spring Boot Starter | 4.0.1 | MyBatis集成 | +| MyBatis-Plus | 3.5.16 | ORM增强工具 | +| Druid | 1.2.28 | 数据库连接池 | + +### 5.3 搜索引擎依赖 + +| 依赖 | 版本 | 用途 | +|----------------------|-------|---------------| +| Elasticsearch Client | 9.2.6 | ES Java客户端 | + +### 5.4 消息队列依赖 + +| 依赖 | 版本 | 用途 | +|----------------------|--------|---------------------| +| RabbitMQ AMQP Client | 5.27.1 | RabbitMQ Java客户端 | + +### 5.5 缓存依赖 + +| 依赖 | 版本 | 用途 | +|----------|---------------|-------------| +| Lettuce | 6.8.2.RELEASE | Redis客户端 | +| Caffeine | 3.2.3 | 本地缓存 | + +### 5.6 工具库依赖 + +| 依赖 | 版本 | 用途 | +|----------------|----------|----------| +| Lombok | 1.18.44 | 代码简化 | +| MapStruct | 1.6.3 | 对象映射 | +| FastJSON2 | 2.0.60 | JSON处理 | +| Velocity | 2.4.1 | 模板引擎 | +| Jackson BOM | 3.1.0 | JSON处理 | +| Commons Lang3 | 3.20.0 | 通用工具 | +| Commons IO | 2.21.0 | IO工具 | +| Commons Text | 1.15.0 | 文本处理 | +| AspectJ Weaver | 1.9.25.1 | AOP支持 | + +### 5.7 安全相关依赖 + +| 依赖 | 版本 | 用途 | +|--------------|-------|---------| +| BouncyCastle | 1.84 | 加密库 | +| Easy Captcha | 1.6.2 | 验证码 | +| java-jwt | 4.5.1 | JWT令牌 | + +### 5.8 文档相关依赖 + +| 依赖 | 版本 | 用途 | +|---------------------|--------|---------------| +| Knife4j OpenAPI3 | 4.5.0 | API文档增强 | +| Springdoc OpenAPI | 3.0.3 | OpenAPI 3集成 | +| Swagger Annotations | 2.2.48 | API文档注解 | + +### 5.9 任务调度依赖 + +| 依赖 | 版本 | 用途 | +|--------------|-------|----------------| +| XXL-JOB Core | 3.4.0 | 分布式任务调度 | + +### 5.10 系统监控依赖 + +| 依赖 | 版本 | 用途 | +|-----------|--------|------------| +| OSHI Core | 6.11.1 | 系统监控 | +| ip2region | 3.3.7 | IP地址定位 | + +### 5.11 存储依赖 + +| 依赖 | 版本 | 用途 | +|------------|---------|------------| +| AWS S3 SDK | 2.42.34 | S3对象存储 | + +### 5.12 办公工具依赖 + +| 依赖 | 版本 | 用途 | +|-------------|------------------|-----------| +| Fesod Sheet | 2.0.1-incubating | Excel处理 | +| PDFBox | 3.0.7 | PDF处理 | + +### 5.13 其他工具依赖 + +| 依赖 | 版本 | 用途 | +|---------------------|--------------|----------------| +| UserAgentUtils | 1.21 | 浏览器标识解析 | +| Pinyin4j | 2.5.1 | 拼音转换 | +| Thumbnailator | 0.4.21 | 图片压缩 | +| mmseg4j-core | 1.10.0 | 中文分词 | +| ZXing | 3.5.4 | 二维码/条形码 | +| Jsoup | 1.22.1 | HTML解析 | +| Hibernate Validator | 9.0.1.Final | 参数校验 | +| Netty | 4.2.12.Final | 网络通信 | + +### 5.14 测试依赖 + +| 依赖 | 版本 | 用途 | +|---------------|-------|----------| +| JUnit Jupiter | 6.0.3 | 单元测试 | + +### 5.15 xtools内部框架依赖 + +| 模块 | 版本 | 用途 | +|---------------------|-------|-------------| +| xtools-core | 5.0.0 | 核心工具库 | +| xtools-web | 5.0.0 | Web工具库 | +| xtools-extend | 5.0.0 | 扩展工具库 | +| xtools-api | 5.0.0 | API定义库 | +| xtools-parent-boot | 5.0.0 | Boot父POM | +| xtools-parent | 5.0.0 | 基础父POM | +| xtools-dependencies | 5.0.0 | 依赖管理POM | + +## 六、技术选型说明 + +### 6.1 JDK 25 + +- **选择原因**:采用最新JDK版本,充分利用Java新特性 +- **关键特性**: + - **虚拟线程(Virtual Threads)**:轻量级线程,大幅提升高并发场景下的吞吐量,项目在消息队列消费和异步任务中使用虚拟线程 + - **结构化并发(Structured Concurrency)**:简化多线程编程模型 + - **作用域值(Scoped Values)**:替代ThreadLocal,更安全地在线程间共享数据 + - **ZGC/Shenandoah GC**:低延迟垃圾回收器,适合微服务场景 + - **Record Classes**:简化不可变数据类的定义 + - **Pattern Matching**:增强的模式匹配,简化类型判断 + - **Sealed Classes**:密封类,增强类型安全 + +### 6.2 Spring Boot 4.0.5 + +- **Spring Framework版本**:7.0.6 +- **Spring Security版本**:7.0.4 +- **主要特性**: + - 全面支持JDK 17+,推荐使用JDK 25 + - 原生支持虚拟线程,通过配置即可启用 + - 支持Jakarta EE 11规范 + - 改进的可观测性(Observability),集成Micrometer + - 优化启动时间和内存占用 + - 支持GraalVM Native Image + - 增强的自动配置机制 + +### 6.3 MyBatis 4.0.1 + MyBatis-Plus 3.5.16 + +- **选择原因**:MyBatis提供灵活的SQL控制,MyBatis-Plus简化CRUD操作 +- **主要特性**: + - MyBatis 4.0.1 提供基础的SQL映射和参数化查询 + - MyBatis-Plus 3.5.16 提供通用Mapper、分页插件、代码生成器 + - 内置分页插件,简化分页查询 + - 条件构造器,简化复杂查询 + - 支持Lambda表达式查询 + +### 6.4 Elasticsearch 9.2.6 + +- **Spring Data Elasticsearch版本**:6.0.4 +- **主要特性**: + - 全文搜索,支持中文分词(mmseg4j-core 1.10.0) + - 高性能分布式搜索和分析引擎 + - 支持复杂的聚合查询 + - 近实时搜索 + - 集群健康监控 + +### 6.5 Redis + +- **Spring Data Redis版本**:4.0.4 +- **Lettuce客户端版本**:6.8.2.RELEASE +- **主要特性**: + - 高性能内存缓存 + - 丰富的数据结构(String、Hash、List、Set、ZSet) + - 支持分布式锁 + - Lettuce基于Netty的异步非阻塞客户端 + - 支持集群模式和哨兵模式 + +### 6.6 RabbitMQ + +- **Spring AMQP版本**:4.0.2 +- **RabbitMQ AMQP Client版本**:5.27.1 +- **主要特性**: + - 可靠的消息传递机制 + - 灵活的路由规则 + - 支持消息确认和重试 + - 高可用集群支持 + - 与Spring Boot深度集成 + +### 6.7 XXL-JOB 3.4.0 + +- **选择原因**:轻量级分布式任务调度平台 +- **主要特性**: + - 可视化任务管理界面 + - 支持多种任务类型(Bean、GLUE等) + - 弹性扩容 + - 失败重试和告警 + - 任务分片广播 + +### 6.8 其他重要依赖版本 + +| 依赖 | 版本 | 用途 | +|--------------|---------|----------------------| +| FastJSON2 | 2.0.60 | 高性能JSON序列化 | +| Lombok | 1.18.44 | 代码简化 | +| MapStruct | 1.6.3 | 编译期对象映射 | +| Velocity | 2.4.1 | 模板引擎(代码生成) | +| Knife4j | 4.5.0 | API文档增强 | +| BouncyCastle | 1.84 | 国密算法支持 | +| OSHI | 6.11.1 | 系统信息采集 | +| ip2region | 3.3.7 | 离线IP定位 | +| Druid | 1.2.28 | 数据库连接池监控 | +| AWS S3 SDK | 2.42.34 | S3对象存储 | +| Caffeine | 3.2.3 | 高性能本地缓存 | +| Jackson | 3.1.0 | JSON处理 | + +--- + +**文档版本**:v1.0 **编写日期**:2026-04-16 **项目版本**:5.0.0 **父POM版本**:xtools-parent-boot:5.0.0 **JDK版本**:25 +**维护团队**:xujun.org diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e65d817 --- /dev/null +++ b/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + xtools-boot + pom + xtools-boot + 低调大师工具箱,SpringBoot工具模块,适配JDK25 + + + + org.xujun + xtools-parent-boot + 5.1.0 + + + + + + xtools-boot-api + xtools-boot-core + + xtools-boot-cache + xtools-boot-db + xtools-boot-elasticsearch + xtools-boot-idempotent + xtools-boot-ip + xtools-boot-jasypt + xtools-boot-job + xtools-boot-knife4j + xtools-boot-log + xtools-boot-mask + xtools-boot-mq + xtools-boot-storage + xtools-boot-task + xtools-boot-thread + xtools-boot-web + + + + + + 25 + + + \ No newline at end of file diff --git a/xtools-boot-api/pom.xml b/xtools-boot-api/pom.xml new file mode 100644 index 0000000..51ff919 --- /dev/null +++ b/xtools-boot-api/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-api + + + + + + jakarta.validation + jakarta.validation-api + + + + io.swagger.core.v3 + swagger-annotations + + + + \ No newline at end of file diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/anntation/IgnoreXss.java b/xtools-boot-api/src/main/java/xtools/boot/api/anntation/IgnoreXss.java new file mode 100644 index 0000000..7b51f8a --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/anntation/IgnoreXss.java @@ -0,0 +1,22 @@ +package xtools.boot.api.anntation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Title : IgnoreXss

+ *

Description : IgnoreXss

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/9 10:07 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IgnoreXss { +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/constant/BootCommonConstant.java b/xtools-boot-api/src/main/java/xtools/boot/api/constant/BootCommonConstant.java new file mode 100644 index 0000000..9adf1c6 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/constant/BootCommonConstant.java @@ -0,0 +1,56 @@ +package xtools.boot.api.constant; + +/** + *

Title : BootCommonConstant

+ *

Description : BootCommonConstant

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/27 15:53 + */ +public interface BootCommonConstant { + + /** + * 认证 + */ + String AUTHORIZATION = "Authorization"; + + /** + * 日志追踪 + */ + String LOG_TRACK = "x-log-track"; + + /** + * 微服务 + */ + String CLOUD = "x-cloud"; + + /** + * 微服务Token + */ + String CLOUD_TOKEN = "x-cloud-token"; + + /** + * 用户ID + */ + String UID = "x-uid"; + + /** + * 认证信息 + */ + String AUTH = "x-auth"; + + /** + * 获取swagger标签 + */ + String GET_SWAGGER_TAG = "get-swagger-tag"; + + /** + * swagger标签 + */ + String SWAGGER_TAG = "swagger-tag"; + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/BaseEnum.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BaseEnum.java new file mode 100644 index 0000000..d3e6c1b --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BaseEnum.java @@ -0,0 +1,39 @@ +package xtools.boot.api.enums; + +/** + *

Title : BaseEnum

+ *

Description : BaseEnum

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 10:31 + */ +public interface BaseEnum { + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + default BaseEnum[] all() { + return null; + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + int code(); + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + String desc(); + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootError.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootError.java new file mode 100644 index 0000000..74a18c5 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootError.java @@ -0,0 +1,122 @@ +package xtools.boot.api.enums; + +import xtools.base.exception.BaseError; +import xtools.base.exception.BaseErrorModule; + +/** + *

Title : BootError

+ *

Description : BootError

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/23 16:53 + */ +public enum BootError implements BaseError { + + /* api begin */ + // 认证异常 + UNAUTHORIZED_ERROR(401, "UNAUTHORIZED_ERROR", BootErrorModule.BOOT_API, "{}"), + // 公钥错误 + BIZ_PUBLIC_KEY_ERROR(9990, "BIZ_PUBLIC_KEY_ERROR", BootErrorModule.BOOT_API, "{}"), + // 业务警告 + BIZ_WARNING(9998, "BIZ_WARNING", BootErrorModule.BOOT_API, "{}"), + // 业务异常 + BIZ_ERROR(9999, "BIZ_ERROR", BootErrorModule.BOOT_API, "{}"), + // 结果码异常 + RESULT_CODE(10000, "RESULT_CODE", BootErrorModule.BOOT_API, "结果码异常,业务错误码必须大于1000,code:{}"), + /* api end */ + + /* core begin */ + // Spring 核心工具异常 + SPRING_BASE(10001, "SPRING_BASE", BootErrorModule.BOOT_CORE, "Spring基本工具异常,操作类型:[{}]"), + // 时间转换异常 + TIME_CONVERT(10002, "TIME", BootErrorModule.BOOT_CORE, "时间转换异常,时间:{}"), + /* core end */ + + /* log begin */ + // 日志Holder异常 + LOG_HOLDER(10100, "LOG", BootErrorModule.BOOT_LOG, "日志 Holder 异常"), + /* log end */ + + /* mq begin */ + MQ(10200, "MQ", BootErrorModule.BOOT_MQ, "MQ异常,错误:{}"), + /* mq end */; + + /** + * 错误码 + **/ + private final int code; + + /** + * 错误类型 + **/ + private final String type; + + /** + * 错误模块 + **/ + private final BaseErrorModule module; + + /** + * 错误消息模板 + **/ + private final String msgTmp; + + /** + * 构造方法 + * + * @param code 错误码 + * @param type 错误类型 + * @param msgTmp 错误消息模板 + */ + BootError(int code, String type, BaseErrorModule module, String msgTmp) { + this.code = code; + this.type = type; + this.module = module; + this.msgTmp = msgTmp; + } + + /** + * 错误码 + * + * @return 错误码 + */ + @Override + public int code() { + return this.code; + } + + /** + * 错误类型 + * + * @return 错误类型 + */ + @Override + public String type() { + return this.type; + } + + /** + * 错误模块 + * + * @return 错误模块 + */ + @Override + public BaseErrorModule module() { + return this.module; + } + + /** + * 错误消息模板 + * + * @return 错误消息模板 + */ + @Override + public String msgTmp() { + return this.msgTmp; + } + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootErrorModule.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootErrorModule.java new file mode 100644 index 0000000..a1d6d8d --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/BootErrorModule.java @@ -0,0 +1,51 @@ +package xtools.boot.api.enums; + +import xtools.base.exception.BaseErrorModule; + +/** + *

Title : BootErrorModule

+ *

Description : BootErrorModule

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/23 16:53 + */ +public enum BootErrorModule implements BaseErrorModule { + // API模块 + BOOT_API("xtools-boot-api"), + // 核心模块 + BOOT_CORE("xtools-boot-core"), + // 日志模块 + BOOT_LOG("xtools-boot-log"), + // MQ模块 + BOOT_MQ("xtools-boot-mq"), + ; + + /** + * 错误模块 + **/ + private final String module; + + /** + * 构造方法 + * + * @param module 错误模块 + */ + BootErrorModule(String module) { + this.module = module; + } + + /** + * 错误模块 + * + * @return 错误模块 + */ + @Override + public String module() { + return this.module; + } + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/DeleteEnum.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/DeleteEnum.java new file mode 100644 index 0000000..4474b99 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/DeleteEnum.java @@ -0,0 +1,105 @@ +package xtools.boot.api.enums; + +import java.util.Objects; + +/** + *

Title : DeleteEnum

+ *

Description : DeleteEnum

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/20 11:08 + */ +public enum DeleteEnum implements BaseEnum { + + // 正常 + NORMAL(0, "正常"), + // 删除 + DELETE(1, "删除"), + ; + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 构造函数 + * + * @param code 编码 + * @param desc 说明 + */ + DeleteEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 判断枚举值类型 + * + * @param code 枚举值 + * @return 枚举值类型 + */ + public static DeleteEnum valueOf(int code) { + for (DeleteEnum type : values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("unknown code, code=" + code); + } + + /** + * 获取枚举值类型 + * + * @param desc 枚举说明 + * @return 枚举值类型 + */ + public static DeleteEnum valueOfDesc(String desc) { + for (DeleteEnum type : values()) { + if (Objects.equals(type.desc, desc)) { + return type; + } + } + throw new IllegalArgumentException("unknown desc, desc=" + desc); + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/FileDataType.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/FileDataType.java new file mode 100644 index 0000000..52a5f68 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/FileDataType.java @@ -0,0 +1,73 @@ +package xtools.boot.api.enums; + +/** + *

Title : FileDataType

+ *

Description : FileDataType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:44 + */ +public enum FileDataType implements BaseEnum { + + // 正常 + OK(0, "正常"), + // 临时 + TEMP(1, "临时"), + // 待删除 + DELETE(2, "待删除"); + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 构造函数 + * + * @param code 编码 + * @param desc 说明 + */ + FileDataType(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } +} \ No newline at end of file diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/ResultType.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/ResultType.java new file mode 100644 index 0000000..5418478 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/ResultType.java @@ -0,0 +1,78 @@ +package xtools.boot.api.enums; + +/** + *

Title : ResultType

+ *

Description : ResultType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/5 14:10 + */ +public enum ResultType { + + + // 请求成功 + OK(true, 200, "ok", "请求成功"), + // 服务器内部错误 + INTERNAL_SERVER_ERROR(false, 500, "internal_server_error", "服务器内部错误"), + + // 请求参数缺失 | 参数格式错误(如:期望数字却传了字符串) | 参数类型不匹配 | JSON/XML格式错误 | 必填参数为空 + BAD_REQUEST(false, 400, "bad_request", "请求参数缺失 | 参数格式错误(如:期望数字却传了字符串) | 参数类型不匹配 | JSON/XML格式错误 | 必填参数为空"), + // 认证失败 + UNAUTHORIZED(false, 401, "unauthorized", "认证失败"), + // 权限不足 + FORBIDDEN(false, 403, "forbidden", "权限不足"), + // 资源不存在 + NOT_FOUND(false, 404, "not_found", "资源不存在"), + // 请求方法不被允许 + METHOD_NOT_ALLOWED(false, 405, "method_not_allowed", "请求方法不被允许"), + // 请求过于频繁 + TOO_MANY_REQUESTS(false, 429, "too_many_requests", "请求过于频繁"), + ; + + /** + * 是否成功 + */ + private final boolean success; + + /** + * 响应码 + */ + private final int code; + + /** + * 响应信息 + */ + private final String msg; + + /** + * 响应描述 + */ + private final String desc; + + ResultType(boolean success, int code, String msg, String desc) { + this.success = success; + this.code = code; + this.msg = msg; + this.desc = desc; + } + + public boolean success() { + return success; + } + + public int code() { + return code; + } + + public String msg() { + return msg; + } + + public String desc() { + return desc; + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/StatusEnum.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/StatusEnum.java new file mode 100644 index 0000000..d179d8d --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/StatusEnum.java @@ -0,0 +1,105 @@ +package xtools.boot.api.enums; + +import java.util.Objects; + +/** + *

Title : StatusEnum

+ *

Description : StatusEnum

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/20 11:08 + */ +public enum StatusEnum implements BaseEnum { + + // 正常 + NORMAL(1, "正常"), + // 停用 + DISABLED(0, "停用"), + ; + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 构造函数 + * + * @param code 编码 + * @param desc 说明 + */ + StatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 判断枚举值类型 + * + * @param code 枚举值 + * @return 枚举值类型 + */ + public static StatusEnum valueOf(int code) { + for (StatusEnum type : values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("unknown code, code=" + code); + } + + /** + * 获取枚举值类型 + * + * @param desc 枚举说明 + * @return 枚举值类型 + */ + public static StatusEnum valueOfDesc(String desc) { + for (StatusEnum type : values()) { + if (Objects.equals(type.desc, desc)) { + return type; + } + } + throw new IllegalArgumentException("unknown desc, desc=" + desc); + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/enums/ThreadType.java b/xtools-boot-api/src/main/java/xtools/boot/api/enums/ThreadType.java new file mode 100644 index 0000000..e8904c4 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/enums/ThreadType.java @@ -0,0 +1,71 @@ +package xtools.boot.api.enums; + +/** + *

Title : ThreadType

+ *

Description : ThreadType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:44 + */ +public enum ThreadType implements BaseEnum { + + // 主线程 + MAIN(0, "主线程"), + // 子线程 + THREAD(1, "子线程"); + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 构造函数 + * + * @param code 编码 + * @param desc 说明 + */ + ThreadType(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } +} \ No newline at end of file diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizError.java b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizError.java new file mode 100644 index 0000000..a52c7a4 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizError.java @@ -0,0 +1,27 @@ +package xtools.boot.api.exection; + +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; + +/** + *

Title : BizError

+ *

Description : BizError

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/29 11:08 + */ +public class BizError extends CommonException { + + /** + * 构造方法 + * + * @param message 错误消息 + */ + public BizError(String message) { + super(BootError.BIZ_ERROR, message); + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizPublicKeyError.java b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizPublicKeyError.java new file mode 100644 index 0000000..43f8c38 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizPublicKeyError.java @@ -0,0 +1,27 @@ +package xtools.boot.api.exection; + +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; + +/** + *

Title : BizPublicKeyError

+ *

Description : BizPublicKeyError

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/29 11:08 + */ +public class BizPublicKeyError extends CommonException { + + /** + * 构造方法 + * + * @param message 错误消息 + */ + public BizPublicKeyError(String message) { + super(BootError.BIZ_PUBLIC_KEY_ERROR, message); + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizWarning.java b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizWarning.java new file mode 100644 index 0000000..2706a7b --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/exection/BizWarning.java @@ -0,0 +1,27 @@ +package xtools.boot.api.exection; + +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; + +/** + *

Title : BizWarning

+ *

Description : BizWarning

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/29 11:08 + */ +public class BizWarning extends CommonException { + + /** + * 构造方法 + * + * @param message 错误消息 + */ + public BizWarning(String message) { + super(BootError.BIZ_WARNING, message); + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/exection/UnauthorizedError.java b/xtools-boot-api/src/main/java/xtools/boot/api/exection/UnauthorizedError.java new file mode 100644 index 0000000..ee002b7 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/exection/UnauthorizedError.java @@ -0,0 +1,25 @@ +package xtools.boot.api.exection; + +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; + +/** + *

Title : UnauthorizedError

+ *

Description : UnauthorizedError

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/29 11:08 + */ +public class UnauthorizedError extends CommonException { + + /** + * 构造方法 + */ + public UnauthorizedError() { + super(BootError.UNAUTHORIZED_ERROR, "认证异常"); + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/EnumInfoDto.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/EnumInfoDto.java new file mode 100644 index 0000000..8281f81 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/EnumInfoDto.java @@ -0,0 +1,34 @@ +package xtools.boot.api.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

Title : EnumInfoDto

+ *

Description : EnumInfoDto

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 16:30 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EnumInfoDto implements Serializable { + + /** + * 枚举值 + */ + private int code; + + /** + * 枚举说明 + */ + private String desc; +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/Result.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/Result.java new file mode 100644 index 0000000..a74eb86 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/Result.java @@ -0,0 +1,142 @@ +package xtools.boot.api.model.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; +import xtools.boot.api.enums.ResultType; +import xtools.boot.api.exection.BizError; + +import java.util.Objects; + +import static xtools.base.config.Constants.CP_COMMA; +import static xtools.base.config.Constants.CP_NUM1000; +import static xtools.base.config.Constants.CP_NUM200; + +/** + *

Title : Result

+ *

Description : Result

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/22 14:07 + */ +@Data +public final class Result { + + @Schema(description = "是否成功") + private boolean success; + + @Schema(description = "响应代码") + private int code; + + @Schema(description = "提示信息") + private Object msg; + + @Schema(description = "响应数据") + private T data; + + /** + * 构造函数 + */ + public Result() { + } + + /** + * 构造函数 + * + * @param success 是否成功 + * @param code 响应代码 + * @param msg 提示信息 + * @param data 响应数据 + */ + public Result(boolean success, int code, Object msg, T data) { + this.success = success; + this.code = code; + this.msg = msg; + this.data = data; + } + + /** + * 构造函数 + * + * @param type 响应类型 + * @param data 数据 + */ + public Result(ResultType type, T data) { + this.success = type.success(); + this.code = type.code(); + this.msg = type.msg() + CP_COMMA + type.desc(); + this.data = data; + } + + /** + * 请求成功 + * + * @return 结果 + */ + public static Result ok() { + return new Result<>(ResultType.OK, null); + } + + /** + * 请求成功 + * + * @param data 数据 + * @param 泛型 + * @return 结果 + */ + public static Result ok(T data) { + return new Result<>(ResultType.OK, data); + } + + /** + * 请求成功,业务失败 + * + * @param code 业务错误码 + * @param msg 业务错误信息 + * @return 响应结果 + */ + public static Result fail(int code, Object msg) { + if (code < CP_NUM1000) { + throw CommonException.create(BootError.RESULT_CODE, code); + } + return new Result<>(true, code, msg, null); + } + + /** + * 请求参数缺失 | 参数格式错误(如:期望数字却传了字符串) | 参数类型不匹配 | JSON/XML格式错误 | 必填参数为空 + * + * @return 响应结果 + */ + public static Result badRequest() { + return new Result<>(ResultType.BAD_REQUEST, null); + } + + /** + * 请求参数缺失 | 参数格式错误(如:期望数字却传了字符串) | 参数类型不匹配 | JSON/XML格式错误 | 必填参数为空 + * + * @param msg 错误信息 + * @return 响应结果 + */ + public static Result badRequest(Object msg) { + ResultType type = ResultType.BAD_REQUEST; + return new Result<>(type.success(), type.code(), msg, null); + } + + /** + * 获取数据 + * + * @return 数据 + */ + public T data() { + if (Objects.equals(success, false) || !Objects.equals(code, CP_NUM200)) { + throw new BizError(Objects.isNull(msg) ? "业务处理失败" : String.valueOf(msg)); + } + return data; + } + +} \ No newline at end of file diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/HolderLogTrack.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/HolderLogTrack.java new file mode 100644 index 0000000..954f83c --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/HolderLogTrack.java @@ -0,0 +1,75 @@ +package xtools.boot.api.model.dto.log; + +import lombok.Data; +import xtools.boot.api.enums.ThreadType; + +import java.util.concurrent.atomic.AtomicInteger; + +import static xtools.base.config.Constants.CP_NUM0; + +/** + *

Title : HolderLogTrack

+ *

Description : HolderLogTrack

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:44 + */ +@Data +public class HolderLogTrack { + + /** + * 日志追踪 ID + */ + private final String traceId; + /** + * 父线日志 ID + */ + private final String parentId; + /** + * 线程类型 + */ + private final ThreadType type; + /** + * 日志获取次数 + */ + private final AtomicInteger count; + /** + * 是否保存日志 + */ + private boolean save; + + /** + * 构造方法 + * + * @param traceId 日志追踪 ID + * @param parentId 父日志 ID + * @param type 线程类型 + */ + public HolderLogTrack(String traceId, String parentId, ThreadType type) { + this.save = true; + this.traceId = traceId; + this.parentId = parentId; + this.type = type; + this.count = new AtomicInteger(CP_NUM0); + } + + /** + * 构造方法 + * + * @param traceId 日志追踪 ID + * @param parentId 父日志 ID + * @param type 线程类型 + * @param count 日志获取次数 + */ + public HolderLogTrack(String traceId, String parentId, ThreadType type, int count) { + this.save = true; + this.traceId = traceId; + this.parentId = parentId; + this.type = type; + this.count = new AtomicInteger(count); + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/LogTrack.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/LogTrack.java new file mode 100644 index 0000000..82e4a46 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/log/LogTrack.java @@ -0,0 +1,25 @@ +package xtools.boot.api.model.dto.log; + +import xtools.boot.api.enums.ThreadType; + +/** + *

Title : LogTrack

+ *

Description : LogTrack

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @param id 日志 ID + * @param time 日志时间 + * @param traceId 日志追踪 ID + * @param parentId 父线日志 ID + * @param type 线程类型 + * @param index 日志获取次数 + * @param save 是否保存日志 + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:44 + */ +public record LogTrack(String id, Long time, String traceId, String parentId, ThreadType type, Integer index, + boolean save) { +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageReq.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageReq.java new file mode 100644 index 0000000..40799ea --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageReq.java @@ -0,0 +1,43 @@ +package xtools.boot.api.model.dto.page; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

Title : PageReq

+ *

Description : PageReq

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/22 14:58 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageReq implements Serializable { + + /** + * 当前页面 + */ + @Schema(description = "当前页面", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer currentPage; + + /** + * 页面数据条数 + */ + @Schema(description = "页面数据条数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer pageSize; + + /** + * 查询条件 + */ + @Schema(description = "查询条件") + private T query; +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageResp.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageResp.java new file mode 100644 index 0000000..b628442 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/page/PageResp.java @@ -0,0 +1,90 @@ +package xtools.boot.api.model.dto.page; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.NoArgsConstructor; +import xtools.core.extend.PagingUtils; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + *

Title : PageResp

+ *

Description : PageResp

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/22 14:58 + */ +@Data +@NoArgsConstructor +public final class PageResp implements Serializable { + + /** + * 当前页面 + */ + @Schema(description = "当前页面") + private Integer currentPage; + + /** + * 页面数据条数 + */ + @Schema(description = "页面数据条数") + private Integer pageSize; + + /** + * 数据条数 + */ + @Schema(description = "数据条数") + private Long total; + + /** + * 总页数 + */ + @Schema(description = "总页数") + private Long pageCount; + + /** + * 结果数据集 + */ + @Schema(description = "结果数据集") + private List data; + + /** + * 构造方法 + * + * @param req 分页请求 + */ + public PageResp(PageReq req) { + this.currentPage = req.getCurrentPage(); + this.pageSize = req.getPageSize(); + } + + /** + * 构造方法 + * + * @param req 分页请求 + */ + public PageResp(PageReq req, Long total, List data) { + this.currentPage = req.getCurrentPage(); + this.pageSize = req.getPageSize(); + this.total = total; + this.data = data; + } + + /** + * 获取总页数 + * + * @return 总页数 + */ + public Long getPageCount() { + if (Objects.isNull(this.pageCount)) { + this.pageCount = PagingUtils.getLastPage(this.total, this.pageSize); + } + return this.pageCount; + } +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/req/IdListReq.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/req/IdListReq.java new file mode 100644 index 0000000..9c2fe98 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/req/IdListReq.java @@ -0,0 +1,34 @@ +package xtools.boot.api.model.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + *

Title : IdListReq

+ *

Description : IdListReq

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/4 11:03 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdListReq implements Serializable { + + /** + * idList + */ + @NotNull(message = "不能为空") + @Schema(description = "Id列表") + private List idList; +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/resp/TreeResp.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/resp/TreeResp.java new file mode 100644 index 0000000..0695b86 --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/dto/resp/TreeResp.java @@ -0,0 +1,47 @@ +package xtools.boot.api.model.dto.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + *

Title : TreeResp

+ *

Description : TreeResp

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/30 09:39 + */ +@Data +public class TreeResp implements Serializable { + + /** + * 父id + */ + @Schema(description = "父id", example = "1") + private Long parentId; + + /** + * 值 + */ + @Schema(description = "值", example = "1") + private Long value; + + /** + * 标签 + */ + @Schema(description = "标签", example = "1") + private String label; + + /** + * 子项 + */ + @Schema(description = "子项") + private List children; + +} diff --git a/xtools-boot-api/src/main/java/xtools/boot/api/model/entity/BaseEntity.java b/xtools-boot-api/src/main/java/xtools/boot/api/model/entity/BaseEntity.java new file mode 100644 index 0000000..f12ce3e --- /dev/null +++ b/xtools-boot-api/src/main/java/xtools/boot/api/model/entity/BaseEntity.java @@ -0,0 +1,28 @@ +package xtools.boot.api.model.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.Instant; + +/** + *

Title : BaseEntity

+ *

Description : BaseEntity

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/17 18:50 + */ +@Data +public class BaseEntity implements Serializable { + + @Schema(description = "创建时间", example = "2026-01-05 10:32:00") + private Instant gmtCreate; + + @Schema(description = "更新时间", example = "2026-01-05 10:32:00") + private Instant gmtModified; +} \ No newline at end of file diff --git a/xtools-boot-cache/pom.xml b/xtools-boot-cache/pom.xml new file mode 100644 index 0000000..a35ff64 --- /dev/null +++ b/xtools-boot-cache/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-cache + + + + xtools-boot-cache-redis + + + \ No newline at end of file diff --git a/xtools-boot-cache/xtools-boot-cache-redis/pom.xml b/xtools-boot-cache/xtools-boot-cache-redis/pom.xml new file mode 100644 index 0000000..c62a679 --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.xujun + xtools-boot-cache + 5.1.0 + + xtools-boot-cache-redis + + + + + + + org.xujun + xtools-boot-core + + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/BootCacheRedisConfiguration.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/BootCacheRedisConfiguration.java new file mode 100644 index 0000000..ef11189 --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/BootCacheRedisConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.cache.redis; + +import org.springframework.context.annotation.Import; +import xtools.boot.cache.redis.selector.BootCacheRedisImportSelector; +import xtools.boot.core.utils.ModuleLoadUtils; + +/** + *

Title : BootCacheRedisConfiguration

+ *

Description : BootCacheRedisConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootCacheRedisImportSelector.class) +public class BootCacheRedisConfiguration { + + /** + * 构造方法 + */ + public BootCacheRedisConfiguration() { + ModuleLoadUtils.loadSuccess(BootCacheRedisConfiguration.class); + } + +} diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/base/RedisService.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/base/RedisService.java new file mode 100644 index 0000000..7ffc71d --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/base/RedisService.java @@ -0,0 +1,475 @@ +package xtools.boot.cache.redis.base; + +import com.alibaba.fastjson2.JSON; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.stereotype.Component; +import xtools.core.ArrUtils; +import xtools.core.CollectionUtils; +import xtools.core.StringUtils; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.TimeUnit; + +import static xtools.base.config.Constants.CP_COLON; +import static xtools.base.config.Constants.CP_NUM1; + +/** + *

Title : RedisService

+ *

Description : RedisService

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Component +public class RedisService { + + /** + * RedisTemplate + **/ + @Resource + private RedisTemplate redisTemplate; + + /** + * StringRedisTemplate + **/ + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 获取组合 Key + * + * @param keys 多个 Key + * @return 组合 Key + */ + public String getKey(String... keys) { + if (ArrUtils.isEmpty(keys)) { + return null; + } + StringJoiner sj = new StringJoiner(CP_COLON); + for (String key : keys) { + sj.add(key); + } + return sj.toString(); + } + + /** + * 整数 值递增一 + * + * @param key Key + * @return 递增后的值 + */ + public Long incr(String key) { + return stringRedisTemplate.opsForValue().increment(key); + } + + /** + * 保存数据 + * + * @param key Key + * @param data 数据 + */ + public void set(String key, Object data) { + this.set(key, data, null, null); + } + + /** + * 保存数据 + * + * @param key Key + * @param data 数据 + * @param timeout 过期时间 + */ + public void set(String key, Object data, Long timeout) { + this.set(key, data, timeout, TimeUnit.SECONDS); + } + + /** + * 保存数据 + * + * @param key Key + * @param data 数据 + * @param timeout 过期时间 + * @param unit 时间单位 + */ + public void set(String key, Object data, Long timeout, TimeUnit unit) { + // 参数校验 + if (StringUtils.isEmpty(key) || data == null) { + return; + } + // 序列化数据 + String serializeData; + if (data instanceof String) { + serializeData = data.toString(); + } else { + serializeData = JSON.toJSONString(data); + } + if (StringUtils.isEmpty(serializeData)) { + return; + } + if (unit == null) { + unit = TimeUnit.SECONDS; + } + // 判断是否有过期时间 + if (timeout == null || timeout <= 0) { + stringRedisTemplate.opsForValue().set(key, serializeData); + } else { + stringRedisTemplate.opsForValue().set(key, serializeData, Duration.of(timeout, unit.toChronoUnit())); + } + } + + /** + * 保存数据(如果key存在则保存失败) + * + * @param key Key + * @param data 数据 + * @return 保存结果 + */ + public Boolean setNx(String key, Object data) { + return this.setNx(key, data, null, null); + } + + /** + * 保存数据(如果key存在则保存失败) + * + * @param key Key + * @param data 数据 + * @param timeout 有效时间 + * @return 保存结果 + */ + public Boolean setNx(String key, Object data, Long timeout) { + return this.setNx(key, data, timeout, TimeUnit.SECONDS); + } + + /** + * 保存数据(如果key存在则保存失败) + * + * @param key Key + * @param data 数据 + * @param timeout 有效时间 + * @param unit 时间单位 + * @return 保存结果 + */ + public Boolean setNx(String key, Object data, Long timeout, TimeUnit unit) { + // 参数校验 + if (StringUtils.isEmpty(key) || data == null) { + return false; + } + // 序列化数据 + String serializeData; + if (data instanceof String) { + serializeData = data.toString(); + } else { + serializeData = JSON.toJSONString(data); + } + if (StringUtils.isEmpty(serializeData)) { + return false; + } + if (unit == null) { + unit = TimeUnit.SECONDS; + } + // 判断是否有过期时间 + if (timeout == null || timeout <= 0) { + return stringRedisTemplate.opsForValue().setIfAbsent(key, serializeData); + } else { + return stringRedisTemplate.opsForValue().setIfAbsent(key, serializeData, Duration.of(timeout, unit.toChronoUnit())); + } + } + + /** + * 获取数据 + * + * @param Class 类型 + * @param key Key + * @param clazz Class + * @return 数据 + */ + public T get(String key, Class clazz) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return null; + } + // 从缓存获取数据 + String data = stringRedisTemplate.opsForValue().get(key); + if (StringUtils.isEmpty(data)) { + return null; + } + return JSON.to(clazz, data); + } + + /** + * 删除 key 对应的数据 + * + * @param key Key + * @return 删除结果 + */ + public Boolean del(String key) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return false; + } + return stringRedisTemplate.delete(key); + } + + /** + * 更改 Key + * + * @param oldKey 旧Key + * @param newKey 新Key + */ + public void rename(String oldKey, String newKey) { + // 参数校验 + if (StringUtils.isEmpty(oldKey) || StringUtils.isEmpty(newKey)) { + return; + } + stringRedisTemplate.rename(oldKey, newKey); + } + + /** + * 设置key的过期时间(秒) + * + * @param key Key + * @param timeout 时间(秒) + * @return 设置结果 + */ + public Boolean expire(String key, long timeout) { + // 参数校验 + if (StringUtils.isEmpty(key) || timeout <= 0) { + return false; + } + return stringRedisTemplate.expire(key, Duration.of(timeout, TimeUnit.SECONDS.toChronoUnit())); + } + + /** + * 获取key的过期时间(秒) + * + * @param key Key + * @return 过期时间(秒) + */ + public Long getExpire(String key) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return null; + } + return stringRedisTemplate.getExpire(key); + } + + /** + * 取消对 Key 过期时间的设置 + * + * @param key Key + * @return true or false + */ + public Boolean persist(String key) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return false; + } + return stringRedisTemplate.persist(key); + } + + /** + * 查找所有匹配给定的模式的键 + * + * @param pattern key的表达式,*表示多个,?表示一个 + * @return 全部符合表达式记录 + */ + public Set getByPattern(String pattern) { + // 参数校验 + if (StringUtils.isEmpty(pattern)) { + return null; + } + return stringRedisTemplate.keys(pattern); + } + + /** + * 尝试获取锁 + * + * @param key 锁的key + * @param value 请求标识(可用UUID) + * @param expireTime 过期时间(秒) + * @return 是否获取成功 + */ + public boolean tryLock(String key, String value, long expireTime) { + Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.of(expireTime, TimeUnit.SECONDS.toChronoUnit())); + return Boolean.TRUE.equals(result); + } + + /** + * 释放锁(使用Lua脚本保证原子性) + * + * @param key 锁的key + * @param value 请求标识 + * @return 是否释放成功 + */ + public boolean releaseLock(String key, String value) { + String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; + + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(script); + redisScript.setResultType(Long.class); + + Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); + return Long.valueOf(CP_NUM1).equals(result); + } + + /** + * 保存 Hash 数据 + * + * @param key Key + * @param map Hash 数据 + * @return 保存结果 + */ + public boolean hashPutAll(String key, Map map) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return false; + } + if (CollectionUtils.isEmpty(map)) { + return false; + } + stringRedisTemplate.opsForHash().putAll(key, map); + return true; + } + + /** + * 保存 Hash 数据 + * + * @param key key + * @param hashKey hashKey + * @param data 数据 + * @return 保存结果 + */ + public boolean hashPut(String key, Object hashKey, Object data) { + // 参数校验 + if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) { + return false; + } + // 序列化数据 + String serializeData; + if (data instanceof String) { + serializeData = data.toString(); + } else { + serializeData = JSON.toJSONString(data); + } + if (StringUtils.isEmpty(serializeData)) { + return false; + } + stringRedisTemplate.opsForHash().put(key, hashKey, serializeData); + return true; + } + + /** + * 保存 Hash 数据 + * + * @param key key + * @param hashKey hashKey + * @param data 数据 + * @param expireTime 过期时间(秒) + * @return 保存结果 + */ + public boolean hashPut(String key, Object hashKey, Object data, long expireTime) { + // 参数校验 + if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) { + return false; + } + // 序列化数据 + String serializeData; + if (data instanceof String) { + serializeData = data.toString(); + } else { + serializeData = JSON.toJSONString(data); + } + if (StringUtils.isEmpty(serializeData)) { + return false; + } + String script = + "redis.call('HSET', KEYS[1], ARGV[1], ARGV[2]) " + + "redis.call('HEXPIRE', KEYS[1], ARGV[3], 'FIELDS', 1, ARGV[1]) " + + "return 1"; + Long result = stringRedisTemplate.execute( + new DefaultRedisScript<>(script, Long.class), + Collections.singletonList(key), + hashKey, serializeData, String.valueOf(expireTime) + ); + return Long.valueOf(CP_NUM1).equals(result); + } + + /** + * 获取 Hash 中所有数据 + * + * @param 结果类型 + * @param key key + * @param clazz 结果类型 + * @return 数据集 + */ + public T hashEntries(String key, Class clazz) { + // 参数校验 + if (StringUtils.isEmpty(key)) { + return null; + } + Map data = stringRedisTemplate.opsForHash().entries(key); + if (CollectionUtils.isEmpty(data)) { + return null; + } + return JSON.to(clazz, data); + } + + /** + * 获取 Hash 中指定 Key 的数据 + * + * @param 结果类型 + * @param key key + * @param hashKey hashKey + * @param clazz 结果类型 + * @return 数据 + */ + public T hashGet(String key, Object hashKey, Class clazz) { + // 参数校验 + if (StringUtils.isEmpty(key) || Objects.isNull(hashKey)) { + return null; + } + Object data = stringRedisTemplate.opsForHash().get(key, hashKey); + return JSON.to(clazz, data); + } + + /** + * 删除 Hash 中指定 Key 的数据 + * + * @param key key + * @param hashKeys hashKey数组 + * @return 删除结果 + */ + public Long hashDelete(String key, Object... hashKeys) { + if (StringUtils.isBlank(key) || ArrUtils.isEmpty(hashKeys)) { + return 0L; + } + return stringRedisTemplate.opsForHash().delete(key, hashKeys); + } + + /** + * 判断 Hash 中是否存在指定 Key 的数据 + * + * @param key key + * @param hashKey hashKey + * @return 存在结果 + */ + public boolean hashExists(String key, Object hashKey) { + if (StringUtils.isBlank(key) || Objects.isNull(hashKey)) { + return false; + } + return stringRedisTemplate.opsForHash().hasKey(key, hashKey); + } + +} diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/enums/BaseCacheEnum.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/enums/BaseCacheEnum.java new file mode 100644 index 0000000..49c2c1b --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/enums/BaseCacheEnum.java @@ -0,0 +1,30 @@ +package xtools.boot.cache.redis.enums; + +/** + *

Title : BaseCacheEnum

+ *

Description : BaseCacheEnum

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/25 14:57 + */ +public interface BaseCacheEnum { + + /** + * 获取key + * + * @return key + */ + String key(); + + /** + * 获取超时时间 + * + * @return 超时时间 + */ + Long expireTime(); + +} diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/monitor/RedisMonitor.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/monitor/RedisMonitor.java new file mode 100644 index 0000000..359f8fe --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/monitor/RedisMonitor.java @@ -0,0 +1,63 @@ +package xtools.boot.cache.redis.monitor; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import jakarta.annotation.Resource; +import org.springframework.data.redis.connection.RedisCommands; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import xtools.core.StringUtils; + +import java.util.Objects; +import java.util.Properties; + +/** + *

Title : RedisMonitor

+ *

Description : RedisMonitor

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Component +public class RedisMonitor { + + @Resource + private RedisTemplate redisTemplate; + + /** + * 状态 + * + * @return 状态数据 + */ + public JSONObject stats() { + RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); + if (Objects.isNull(factory)) { + return null; + } + // 获取redis命令 + RedisCommands commands = factory.getConnection().commands(); + // 获取相对应信息 + Properties info = commands.info(); + Object dbSize = commands.dbSize(); + Properties commandStats = commands.info("commandstats"); + JSONArray cmd = new JSONArray(); + if (Objects.nonNull(commandStats)) { + commandStats.stringPropertyNames().forEach(key -> { + String property = commandStats.getProperty(key); + JSONObject data = JSONObject.of( + "name", StringUtils.removeStart(key, "cmdstat_"), + "value", StringUtils.substringBetween(property, "calls=", ",usec") + ); + cmd.add(data); + }); + } + // 封装数据 + return JSONObject.of("info", info, "dbSize", dbSize, "cmd", cmd); + } + +} diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/selector/BootCacheRedisImportSelector.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/selector/BootCacheRedisImportSelector.java new file mode 100644 index 0000000..9ae309e --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/selector/BootCacheRedisImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.cache.redis.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootCacheRedisImportSelector

+ *

Description : BootCacheRedisImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootCacheRedisImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.cache.redis"); + } + +} \ No newline at end of file diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/utils/RedisUtils.java b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/utils/RedisUtils.java new file mode 100644 index 0000000..ebc9f97 --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/java/xtools/boot/cache/redis/utils/RedisUtils.java @@ -0,0 +1,36 @@ +package xtools.boot.cache.redis.utils; + +import xtools.boot.cache.redis.base.RedisService; +import xtools.boot.core.utils.SpringContextUtils; + +/** + *

Title : RedisUtils

+ *

Description : RedisUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/10 09:02 + */ +public class RedisUtils { + + /** + * Redis 服务 + **/ + private static RedisService redisService; + + /** + * 获取 Redis 服务 + * + * @return Redis 服务 + */ + public static RedisService get() { + if (redisService != null) { + return redisService; + } + return SpringContextUtils.getBean(RedisService.class); + } + +} diff --git a/xtools-boot-cache/xtools-boot-cache-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-cache/xtools-boot-cache-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..83e51b8 --- /dev/null +++ b/xtools-boot-cache/xtools-boot-cache-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.cache.redis.BootCacheRedisConfiguration \ No newline at end of file diff --git a/xtools-boot-core/pom.xml b/xtools-boot-core/pom.xml new file mode 100644 index 0000000..871a440 --- /dev/null +++ b/xtools-boot-core/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-core + + + + + + + org.xujun + xtools-boot-api + + + + + + org.springframework + spring-context + + + + + \ No newline at end of file diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/BootCoreConfiguration.java b/xtools-boot-core/src/main/java/xtools/boot/core/BootCoreConfiguration.java new file mode 100644 index 0000000..1f87468 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/BootCoreConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.core; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.selector.BootCoreImportSelector; +import xtools.boot.core.utils.ModuleLoadUtils; + +/** + *

Title : BootCoreConfiguration

+ *

Description : BootCoreConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootCoreImportSelector.class) +public class BootCoreConfiguration { + + /** + * 构造方法 + */ + public BootCoreConfiguration() { + ModuleLoadUtils.loadSuccess(BootCoreConfiguration.class); + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/holder/CommonHolder.java b/xtools-boot-core/src/main/java/xtools/boot/core/holder/CommonHolder.java new file mode 100644 index 0000000..d45eaa7 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/holder/CommonHolder.java @@ -0,0 +1,96 @@ +package xtools.boot.core.holder; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + *

Title : CommonHolder

+ *

Description : CommonHolder

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/31 21:20 + */ +public class CommonHolder { + + /** + * Mvc通用信息 + */ + private static final ScopedValue> COMMON_INFO = ScopedValue.newInstance(); + + /** + * 获取通用信息 Holder + * + * @return 通用信息 + */ + public static ScopedValue> getScoped() { + return COMMON_INFO; + } + + /** + * 创建通用信息 + * + * @return 通用信息 + */ + public static Map create() { + return new HashMap<>(16); + } + + /** + * 获取通用信息 + * + * @return 通用信息 + */ + public static Map get() { + if (!COMMON_INFO.isBound()) { + return null; + } + return COMMON_INFO.get(); + } + + /** + * 设置通用信息 + * + * @param key Key + * @param value Value + */ + public static void set(String key, Object value) { + Map map = get(); + if (Objects.nonNull(map)) { + map.put(key, value); + } + } + + /** + * 获取通用信息 + * + * @param key Key + * @return Value + */ + public static Object get(String key) { + Map map = get(); + if (Objects.nonNull(map)) { + return map.get(key); + } + return null; + } + + /** + * 获取通用信息 + * + * @param key Key + * @return Value + */ + public static boolean getBoolean(String key) { + Map map = get(); + if (Objects.nonNull(map) && map.get(key) instanceof Boolean value) { + return value; + } + return false; + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/FilterWhitelist.java b/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/FilterWhitelist.java new file mode 100644 index 0000000..deb6250 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/FilterWhitelist.java @@ -0,0 +1,25 @@ +package xtools.boot.core.interfaces; + +import java.util.Set; + +/** + *

Title : FilterWhitelist

+ *

Description : FilterWhitelist

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/8 12:23 + */ +public interface FilterWhitelist { + + /** + * 添加过滤器白名单 + * + * @return 过滤器白名单 + */ + Set add(); + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/JobInterface.java b/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/JobInterface.java new file mode 100644 index 0000000..f13f174 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/interfaces/JobInterface.java @@ -0,0 +1,34 @@ +package xtools.boot.core.interfaces; + +/** + *

Title : JobInterface

+ *

Description : JobInterface

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/12 20:52 + */ +public interface JobInterface { + + /** + * 执行任务 + * + * @throws Exception 任务执行异常 + */ + void execute() throws Exception; + + /** + * 初始化任务 + */ + default void init() { + } + + /** + * 销毁任务 + */ + default void destroy() { + } +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/selector/BootCoreImportSelector.java b/xtools-boot-core/src/main/java/xtools/boot/core/selector/BootCoreImportSelector.java new file mode 100644 index 0000000..995ef8b --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/selector/BootCoreImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.core.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootCoreImportSelector

+ *

Description : BootCoreImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootCoreImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.core"); + } + +} \ No newline at end of file diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/AddrUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/AddrUtils.java new file mode 100644 index 0000000..802aea4 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/AddrUtils.java @@ -0,0 +1,46 @@ +package xtools.boot.core.utils; + +import xtools.core.StringUtils; + +import java.util.List; + +/** + *

Title : AddrUtils

+ *

Description : AddrUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/13 15:09 + */ +public class AddrUtils { + + /** + * 本地IP列表 + */ + private static final List LOCAL_IP_LIST; + + // 初始化 + static { + LOCAL_IP_LIST = List.of( + "127.0.0.1", + "localhost" + ); + } + + /** + * 是否为本地IP + * + * @param ip IP + * @return true or false + */ + public static boolean isLocalIp(String ip) { + if (StringUtils.isEmpty(ip)) { + return true; + } + return LOCAL_IP_LIST.contains(ip); + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/AppUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/AppUtils.java new file mode 100644 index 0000000..a4d5738 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/AppUtils.java @@ -0,0 +1,33 @@ +package xtools.boot.core.utils; + +import xtools.core.extend.TemplateUtils; + +/** + *

Title : AppUtils

+ *

Description : AppUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025/12/23 09:44 + */ +public class AppUtils { + + /** + * 应用启动信息 + */ + private static final String APP_START_INFO = "应用启动成功,总耗时{}毫秒"; + + /** + * 获取启动信息 + * + * @param startTime 启动时间 + * @return 启动信息 + */ + public static String info(long startTime) { + return TemplateUtils.format(APP_START_INFO, startTime); + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/EnumUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/EnumUtils.java new file mode 100644 index 0000000..a31baf7 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/EnumUtils.java @@ -0,0 +1,42 @@ +package xtools.boot.core.utils; + +import xtools.boot.api.enums.BaseEnum; +import xtools.boot.api.model.EnumInfoDto; +import xtools.core.ArrUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + *

Title : EnumUtils

+ *

Description : EnumUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 16:29 + */ +public class EnumUtils implements Serializable { + + /** + * 获取枚举信息 + * + * @param list 枚举值列表 + * @return 枚举的详细信息 + */ + public static List getEnumInfos(BaseEnum... list) { + if (ArrUtils.isEmpty(list)) { + return null; + } + List dataList = new ArrayList<>(); + for (BaseEnum item : list) { + dataList.add(new EnumInfoDto(item.code(), item.desc())); + } + return dataList; + } + + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/JarUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/JarUtils.java new file mode 100644 index 0000000..5574b95 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/JarUtils.java @@ -0,0 +1,53 @@ +package xtools.boot.core.utils; + +import lombok.extern.slf4j.Slf4j; +import xtools.core.CollectionUtils; +import xtools.core.sys.SysBaseInfoUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + *

Title : JarUtils

+ *

Description : JarUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/18 20:57 + */ +@Slf4j +public class JarUtils implements Serializable { + + /** + * 获取所有jar名称 + * + * @return jar名称 + */ + public static List getJarName() { + String end = "jar"; + List mfPath = null; + try { + mfPath = SysBaseInfoUtils.getMfPath(); + } catch (Exception e) { + log.error("获取[MANIFEST.MF]文件路径失败", e); + } + if (CollectionUtils.isEmpty(mfPath)) { + return null; + } + List jarList = new ArrayList<>(); + mfPath.stream().filter(Objects::nonNull).forEach(item -> { + item = item.replaceAll("!/META-INF/MANIFEST.MF", ""); + item = item.substring(item.lastIndexOf("/") + 1); + if (item.endsWith(end)) { + jarList.add(item); + } + }); + return jarList.stream().sorted().toList(); + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/ModuleLoadUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/ModuleLoadUtils.java new file mode 100644 index 0000000..5bbf11a --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/ModuleLoadUtils.java @@ -0,0 +1,32 @@ +package xtools.boot.core.utils; + +import lombok.extern.slf4j.Slf4j; + +/** + *

Title : ModuleLoadUtils

+ *

Description : ModuleLoadUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/5 09:20 + */ +@Slf4j +public class ModuleLoadUtils { + + /** + * 加载成功模版 + */ + private static final String LOAD_TEMPLATE = "模块[{}]加载成功"; + + /** + * 加载成功 + */ + public static void loadSuccess(Class clazz) { + String packageName = clazz.getPackageName(); + log.info(LOAD_TEMPLATE, packageName); + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/PathPatternUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/PathPatternUtils.java new file mode 100644 index 0000000..b7e51ee --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/PathPatternUtils.java @@ -0,0 +1,84 @@ +package xtools.boot.core.utils; + +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import xtools.core.ArrUtils; +import xtools.core.CollectionUtils; + +import java.util.List; +import java.util.Set; + +/** + *

Title : PathPatternUtils

+ *

Description : PathPatternUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/8 09:05 + */ +public class PathPatternUtils { + + /** + * 路径匹配 + * + * @param pathPatterns 路径规则 + * @param path 路径 + * @return 结果 + */ + public static boolean match(String[] pathPatterns, String path) { + if (ArrUtils.isEmpty(pathPatterns)) { + return false; + } + PathMatcher pm = new AntPathMatcher(); + for (String pathPattern : pathPatterns) { + if (pm.match(pathPattern, path)) { + return true; + } + } + return false; + } + + /** + * 路径匹配 + * + * @param pathPatterns 路径规则 + * @param path 路径 + * @return 结果 + */ + public static boolean match(List pathPatterns, String path) { + if (CollectionUtils.isEmpty(pathPatterns)) { + return false; + } + PathMatcher pm = new AntPathMatcher(); + for (String pathPattern : pathPatterns) { + if (pm.match(pathPattern, path)) { + return true; + } + } + return false; + } + + /** + * 路径匹配 + * + * @param pathPatterns 路径规则 + * @param path 路径 + * @return 结果 + */ + public static boolean match(Set pathPatterns, String path) { + if (CollectionUtils.isEmpty(pathPatterns)) { + return false; + } + PathMatcher pm = new AntPathMatcher(); + for (String pathPattern : pathPatterns) { + if (pm.match(pathPattern, path)) { + return true; + } + } + return false; + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/SpringContextUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/SpringContextUtils.java new file mode 100644 index 0000000..ff65290 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/SpringContextUtils.java @@ -0,0 +1,211 @@ +package xtools.boot.core.utils; + +import lombok.Getter; +import org.jspecify.annotations.NonNull; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; + +import java.util.Collection; +import java.util.LinkedList; + +/** + *

Title : SpringContextUtils

+ *

Description : SpringContextUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Order(-1) +@Component +public class SpringContextUtils implements BeanFactoryPostProcessor, ApplicationContextAware { + + /** + * "@PostConstruct"注解标记的类中,由于ApplicationContext还未加载,导致空指针 + * 因此实现BeanFactoryPostProcessor注入ConfigurableListableBeanFactory实现bean的操作 + */ + private static ConfigurableListableBeanFactory beanFactory; + + /** + * Spring 应用上下文环境 + */ + @Getter + private static ApplicationContext applicationContext; + + /** + * 获取 ListableBeanFactory + * + * @return ListableBeanFactory + */ + public static ListableBeanFactory getBeanFactory() { + return null == beanFactory ? applicationContext : beanFactory; + } + + /** + * 获取 ConfigurableListableBeanFactory + * + * @return ConfigurableListableBeanFactory + */ + public static ConfigurableListableBeanFactory getConfigurableBeanFactory() { + ConfigurableListableBeanFactory factory = null; + if (null != beanFactory) { + factory = beanFactory; + } else if (applicationContext instanceof ConfigurableApplicationContext) { + factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); + } + return factory; + } + + /** + * 根据类的类型获取对应的 Bean + * + * @param 类的类型 + * @param requiredType 类型 + * @return Bean + */ + public static T getBean(Class requiredType) { + try { + return getBeanFactory().getBean(requiredType); + } catch (Exception e) { + throw CommonException.create(BootError.SPRING_BASE, e, "根据类的类型获取对应的 Bean"); + } + } + + /** + * 根据类的类型获取对应的 Bean + * + * @param 类的类型 + * @param requiredType 类型 + * @return Bean + */ + public static T getBeanDefNull(Class requiredType) { + try { + return getBeanFactory().getBean(requiredType); + } catch (Exception e) { + return null; + } + } + + /** + * 根据类的类型获取对应的 Bean 列表 + * + * @param 类的类型 + * @param requiredType 类型 + * @return Bean 列表 + */ + public static Collection getBeanList(Class requiredType) { + try { + return new LinkedList<>(getBeanFactory().getBeansOfType(requiredType).values()); + } catch (Exception e) { + throw CommonException.create(BootError.SPRING_BASE, e, "根据类的类型获取对应的 Bean 列表"); + } + } + + /** + * 根据 Bean 名称获取对象 + * + * @param name Bean 名称 + * @return Bean + */ + public static Object getBean(String name) { + try { + return getBeanFactory().getBean(name); + } catch (Exception e) { + throw CommonException.create(BootError.SPRING_BASE, e, "根据 Bean 名称获取对象"); + } + } + + /** + * 根据 Bean 名称获取对象 + * + * @param name Bean 名称 + * @return Bean + */ + public static Object getBeanDefNull(String name) { + try { + return getBeanFactory().getBean(name); + } catch (Exception e) { + return null; + } + } + + /** + * 动态向 Spring 注册 Bean + * + * @param Bean 类型 + * @param beanName 名称 + * @param bean Bean + */ + public static void registerBean(String beanName, T bean) { + final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory(); + factory.autowireBean(bean); + factory.registerSingleton(beanName, bean); + } + + /** + * 注销bean(将Spring中的bean注销,请谨慎使用) + * + * @param beanName bean 名称 + */ + public static void unregisterBean(String beanName) { + final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory(); + if (factory instanceof DefaultSingletonBeanRegistry registry) { + registry.destroySingleton(beanName); + } + } + + /** + * 发布事件 + * + * @param event 待发布的事件 + */ + public static void publishAppEvent(ApplicationEvent event) { + publishEvent(event); + } + + /** + * 发布事件 + * + * @param event 待发布的事件 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } + + /** + * 设置 Spring 应用上下文环境 + * + * @param applicationContext ApplicationContext + */ + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 设置 beanFactory + * + * @param beanFactory ConfigurableListableBeanFactory + */ + @Override + public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException { + SpringContextUtils.beanFactory = beanFactory; + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/TimeUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/TimeUtils.java new file mode 100644 index 0000000..090b5cb --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/TimeUtils.java @@ -0,0 +1,53 @@ +package xtools.boot.core.utils; + +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; +import xtools.core.StringUtils; +import xtools.core.enums.TimePattern; +import xtools.core.extend.CheckUtils; +import xtools.core.time.InstantUtils; + +import java.io.Serializable; +import java.time.Instant; + +/** + *

Title : TimeUtils

+ *

Description : TimeUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 14:20 + */ +public class TimeUtils implements Serializable { + + /** + * 时间分隔符 + */ + private static final String T = "T"; + + /** + * 时间转换 + * + * @param time 时间 + * @return 时间 + */ + public static Instant toInstant(String time) { + if (StringUtils.isBlank(time)) { + return null; + } + if (time.contains(T)) { + return Instant.parse(time); + } + if (CheckUtils.pattern(TimePattern.YMDHMS.regex(), time)) { + return InstantUtils.parse(time, TimePattern.YMDHMS); + } else if (CheckUtils.pattern(TimePattern.YMD.regex(), time)) { + return InstantUtils.parse(time, TimePattern.YMD); + } else { + throw CommonException.create(BootError.TIME_CONVERT, time); + } + } + +} diff --git a/xtools-boot-core/src/main/java/xtools/boot/core/utils/TreeUtils.java b/xtools-boot-core/src/main/java/xtools/boot/core/utils/TreeUtils.java new file mode 100644 index 0000000..8217e03 --- /dev/null +++ b/xtools-boot-core/src/main/java/xtools/boot/core/utils/TreeUtils.java @@ -0,0 +1,38 @@ +package xtools.boot.core.utils; + +import xtools.boot.api.model.dto.resp.TreeResp; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Title : TreeUtils

+ *

Description : TreeUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/30 09:41 + */ +public class TreeUtils { + + /** + * 获取子节点 + * + * @param id 父级 ID + * @param list 数据级 + * @return 子级 + */ + public static List getChild(Long id, List list) { + List childList = new ArrayList<>(); + list.stream().filter(item -> item.getParentId().equals(id)).forEach(item -> { + List child = getChild(item.getValue(), list); + item.setChildren(child); + childList.add(item); + }); + return childList; + } + +} diff --git a/xtools-boot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..49fd071 --- /dev/null +++ b/xtools-boot-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.core.BootCoreConfiguration \ No newline at end of file diff --git a/xtools-boot-db/pom.xml b/xtools-boot-db/pom.xml new file mode 100644 index 0000000..6e48916 --- /dev/null +++ b/xtools-boot-db/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-db + + + + xtools-boot-db-druid + xtools-boot-db-hikari + xtools-boot-db-mybatis + xtools-boot-db-mybatis-plus + + + \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-druid/pom.xml b/xtools-boot-db/xtools-boot-db-druid/pom.xml new file mode 100644 index 0000000..c891338 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + org.xujun + xtools-boot-db + 5.1.0 + + xtools-boot-db-druid + + + + + + + org.xujun + xtools-boot-core + + + + + + com.alibaba + druid-spring-boot-4-starter + + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/BootDbDruidConfiguration.java b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/BootDbDruidConfiguration.java new file mode 100644 index 0000000..010ae43 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/BootDbDruidConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.db.druid; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.db.druid.selector.BootDbDruidImportSelector; + +/** + *

Title : BootDbDruidConfiguration

+ *

Description : BootDbDruidConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootDbDruidImportSelector.class) +public class BootDbDruidConfiguration { + + /** + * 构造方法 + */ + public BootDbDruidConfiguration() { + ModuleLoadUtils.loadSuccess(BootDbDruidConfiguration.class); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidConf.java b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidConf.java new file mode 100644 index 0000000..4dedf58 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidConf.java @@ -0,0 +1,97 @@ +package xtools.boot.db.druid.condig; + +import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import xtools.core.StringUtils; + +import java.io.IOException; + +/** + *

Title : DruidConf

+ *

Description : DruidConf

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025年8月13日 上午8:06:06 + */ +@Component +@Configuration +public class DruidConf { + + /** + * 广告过滤 + * + * @param properties Druid配置信息 + * @return 广告过滤 + */ + @Bean + @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.alibaba.druid.pool.DruidDataSource") + @ConditionalOnProperty(name = "spring.datasource.druid.web-stat-filter.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + String urlPattern = config.getUrlPattern(); + // 提取common.js的配置路径 + String pattern = StringUtils.isEmpty(urlPattern) ? "/druid/**" : urlPattern; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() { + + /** + * 初始化 + * + * @param filterConfig FilterConfig + */ + @Override + public void init(FilterConfig filterConfig) { + } + + /** + * 过滤器 + * + * @param request ServletRequest + * @param response ServletResponse + * @param chain FilterChain + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + /** + * 销毁 + */ + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } + +} diff --git a/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidFilterWhitelist.java b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidFilterWhitelist.java new file mode 100644 index 0000000..e84dcc5 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/condig/DruidFilterWhitelist.java @@ -0,0 +1,33 @@ +package xtools.boot.db.druid.condig; + +import org.springframework.stereotype.Component; +import xtools.boot.core.interfaces.FilterWhitelist; + +import java.util.Set; + +/** + *

Title : DruidFilterWhitelist

+ *

Description : DruidFilterWhitelist

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/8 12:28 + */ +@Component +public class DruidFilterWhitelist implements FilterWhitelist { + + /** + * 添加过滤器白名单 + * + * @return 过滤器白名单 + */ + @Override + public Set add() { + return Set.of( + "/druid/**" + ); + } +} diff --git a/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/selector/BootDbDruidImportSelector.java b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/selector/BootDbDruidImportSelector.java new file mode 100644 index 0000000..6debbff --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/src/main/java/xtools/boot/db/druid/selector/BootDbDruidImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.db.druid.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootDbDruidImportSelector

+ *

Description : BootDbDruidImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootDbDruidImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.db.druid"); + } + +} \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-druid/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-db/xtools-boot-db-druid/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..1570eb6 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-druid/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.db.druid.BootDbDruidConfiguration \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-hikari/pom.xml b/xtools-boot-db/xtools-boot-db-hikari/pom.xml new file mode 100644 index 0000000..6da3ca4 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-hikari/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.xujun + xtools-boot-db + 5.1.0 + + xtools-boot-db-hikari + + + + + + + org.xujun + xtools-boot-core + + + + + + com.zaxxer + HikariCP + + + + \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-hikari/src/main/java/xtools/boot/db/hikari/BootDbHikariConfiguration.java b/xtools-boot-db/xtools-boot-db-hikari/src/main/java/xtools/boot/db/hikari/BootDbHikariConfiguration.java new file mode 100644 index 0000000..5b0bedb --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-hikari/src/main/java/xtools/boot/db/hikari/BootDbHikariConfiguration.java @@ -0,0 +1,25 @@ +package xtools.boot.db.hikari; + +import xtools.boot.core.utils.ModuleLoadUtils; + +/** + *

Title : BootDbHikariConfiguration

+ *

Description : BootDbHikariConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootDbHikariConfiguration { + + /** + * 构造方法 + */ + public BootDbHikariConfiguration() { + ModuleLoadUtils.loadSuccess(BootDbHikariConfiguration.class); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-hikari/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-db/xtools-boot-db-hikari/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..73df311 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-hikari/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.db.hikari.BootDbHikariConfiguration \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/pom.xml b/xtools-boot-db/xtools-boot-db-mybatis-plus/pom.xml new file mode 100644 index 0000000..7cda6ed --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + org.xujun + xtools-boot-db + 5.1.0 + + xtools-boot-db-mybatis-plus + + + + + + + org.xujun + xtools-boot-core + + + + + + + com.baomidou + mybatis-plus-jsqlparser + + + com.baomidou + mybatis-plus-spring-boot4-starter + + + + + + \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/BootDbMybatisPlusConfiguration.java b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/BootDbMybatisPlusConfiguration.java new file mode 100644 index 0000000..020bdb8 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/BootDbMybatisPlusConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.db.mybatisplus; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.db.mybatisplus.selector.BootDbMybatisPlusImportSelector; + +/** + *

Title : BootCacheRedisConfiguration

+ *

Description : BootCacheRedisConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootDbMybatisPlusImportSelector.class) +public class BootDbMybatisPlusConfiguration { + + /** + * 构造方法 + */ + public BootDbMybatisPlusConfiguration() { + ModuleLoadUtils.loadSuccess(BootDbMybatisPlusConfiguration.class); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/config/MybatisPlusConfig.java b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 0000000..c7d62b8 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,35 @@ +package xtools.boot.db.mybatisplus.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + *

Title : MybatisPlusConfig

+ *

Description : MybatisPlusConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/5 10:15 + */ +@Configuration +public class MybatisPlusConfig { + + /** + * 配置 MybatisPlus 拦截器 + * + * @return MybatisPlus 拦截器 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 添加分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/selector/BootDbMybatisPlusImportSelector.java b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/selector/BootDbMybatisPlusImportSelector.java new file mode 100644 index 0000000..69d2405 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/selector/BootDbMybatisPlusImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.db.mybatisplus.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootDbMybatisPlusImportSelector

+ *

Description : BootDbMybatisPlusImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootDbMybatisPlusImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.db.mybatisplus"); + } + +} \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/utils/QueryUtils.java b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/utils/QueryUtils.java new file mode 100644 index 0000000..9f4d93f --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/java/xtools/boot/db/mybatisplus/utils/QueryUtils.java @@ -0,0 +1,63 @@ +package xtools.boot.db.mybatisplus.utils; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import xtools.core.ArrUtils; + +import java.time.Instant; +import java.util.Objects; + +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1; +import static xtools.base.config.Constants.CP_NUM10; +import static xtools.base.config.Constants.CP_NUM2; + +/** + *

Title : QueryUtils

+ *

Description : QueryUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/17 19:16 + */ +public class QueryUtils { + + /** + * 添加时间范围查询 + * + * @param query 查询条件 + * @param timeRange 时间范围 + * @param timeField 时间字段 + * @param 泛型 + */ + public static void addTimeRange(LambdaQueryWrapper query, Instant[] timeRange, SFunction timeField) { + if (ArrUtils.isEmpty(timeRange) || timeRange.length != CP_NUM2) { + return; + } + query.ge(Objects.nonNull(timeRange[CP_NUM0]), timeField, timeRange[CP_NUM0]); + query.le(Objects.nonNull(timeRange[CP_NUM1]), timeField, timeRange[CP_NUM1]); + } + + /** + * 获取分页条件 + * + * @param currentPage 当前页 + * @param pageSize 页面大小 + * @param 泛型 + * @return 分页条件 + */ + public static Page getPage(Integer currentPage, Integer pageSize) { + if (Objects.isNull(currentPage) || currentPage < CP_NUM1) { + currentPage = CP_NUM1; + } + if (Objects.isNull(pageSize) || pageSize < CP_NUM1) { + pageSize = CP_NUM10; + } + return new Page<>(currentPage, pageSize); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f08626c --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis-plus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.db.mybatisplus.BootDbMybatisPlusConfiguration \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis/pom.xml b/xtools-boot-db/xtools-boot-db-mybatis/pom.xml new file mode 100644 index 0000000..a02cd19 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.xujun + xtools-boot-db + 5.1.0 + + xtools-boot-db-mybatis + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-log + + + + org.xujun + xtools-boot-thread + + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + + com.mysql + mysql-connector-j + + + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/BootDbMybatisConfiguration.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/BootDbMybatisConfiguration.java new file mode 100644 index 0000000..769c329 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/BootDbMybatisConfiguration.java @@ -0,0 +1,30 @@ +package xtools.boot.db.mybatis; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.db.mybatis.selector.BootDbMybatisImportSelector; + +/** + *

Title : BootDbMybatisConfiguration

+ *

Description : BootDbMybatisConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@MapperScan({"xtools.boot.db.mybatis.mapper"}) +@Import(BootDbMybatisImportSelector.class) +public class BootDbMybatisConfiguration { + + /** + * 构造方法 + */ + public BootDbMybatisConfiguration() { + ModuleLoadUtils.loadSuccess(BootDbMybatisConfiguration.class); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/config/MyBatisConfig.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/config/MyBatisConfig.java new file mode 100644 index 0000000..b3a0fcd --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/config/MyBatisConfig.java @@ -0,0 +1,32 @@ +package xtools.boot.db.mybatis.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + *

Title : MyBatisConfig

+ *

Description : MyBatisConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/11 14:46 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "mybatis") +public class MyBatisConfig { + + /** + * 是否开启MyBatis监控 + */ + private Boolean monitor = true; + + /** + * 慢查询时间阈值 + */ + private Integer slowTime = 3000; +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/enums/MySqlMonitorEnums.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/enums/MySqlMonitorEnums.java new file mode 100644 index 0000000..2cac5cd --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/enums/MySqlMonitorEnums.java @@ -0,0 +1,116 @@ +package xtools.boot.db.mybatis.enums; + +import lombok.Getter; +import xtools.boot.api.enums.BaseEnum; + +/** + *

Title : MySqlMonitorEnums

+ *

Description : MySqlMonitorEnums

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 10:31 + */ +public enum MySqlMonitorEnums implements BaseEnum { + + // 进程列表 + PROCESSLIST(1, "进程列表", "SHOW FULL PROCESSLIST"), + // 变量 + VARIABLES(2, "变量", "SHOW GLOBAL VARIABLES"), + // 状态 + STATUS(3, "状态", "SHOW STATUS"), + // 表信息 + TABLE(1000, "表信息", "SHOW TABLE STATUS"), + + // 需要权限 +// // 查看正在进行中的事务 +// INNODB_TRX(100, "查看正在进行中的事务", "SELECT * FROM information_schema.INNODB_TRX"), +// // 查看正在锁的事务 +// INNODB_LOCKS(101, "查看正在锁的事务", "SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS"), +// // 查看等待锁的事务 +// INNODB_LOCK_WAITS(102, "查看等待锁的事务", "SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS"), +// // 查询是否锁表 +// LOCK_TABLES(103, "查询是否锁表", "SHOW OPEN TABLES where In_use > 0"), +// // 查看最近死锁的日志 +// INNODB_STATUS(104, "查看最近死锁的日志", "show engine innodb status"), + + ; + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * SQL语句 + */ + @Getter + private final String sql; + + /** + * 初始化方法 + * + * @param code Code + * @param desc 说明 + * @param sql SQL + */ + MySqlMonitorEnums(int code, String desc, String sql) { + this.code = code; + this.desc = desc; + this.sql = sql; + } + + /** + * 判断枚举值类型 + * + * @param code 枚举值 + * @return 枚举值类型 + */ + public static MySqlMonitorEnums valueOf(int code) { + for (MySqlMonitorEnums type : values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("unknown code, code=" + code); + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + +} \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/interceptor/MyBatisInterceptor.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/interceptor/MyBatisInterceptor.java new file mode 100644 index 0000000..7d5f9b9 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/interceptor/MyBatisInterceptor.java @@ -0,0 +1,214 @@ +package xtools.boot.db.mybatis.interceptor; + +import com.alibaba.fastjson2.JSONObject; +import jakarta.annotation.Resource; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.session.ResultHandler; +import org.jspecify.annotations.NonNull; +import org.springframework.stereotype.Component; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.db.mybatis.config.MyBatisConfig; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.thread.utils.VirtualThreadTaskUtils; +import xtools.core.CollectionUtils; +import xtools.core.enums.LogLevel; +import xtools.core.enums.TimePattern; +import xtools.core.time.InstantUtils; + +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static xtools.base.config.Constants.CP_COLON; +import static xtools.base.config.Constants.CP_COMMA; +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1; +import static xtools.base.config.Constants.CP_SPACE; + +/** + *

Title : MyBatisInterceptor

+ *

Description : MyBatisInterceptor

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/11 14:35 + */ +@Component +@Intercepts({ + @Signature(type = StatementHandler.class, method = MyBatisInterceptor.QUERY, args = {Statement.class, ResultHandler.class}), + @Signature(type = StatementHandler.class, method = MyBatisInterceptor.UPDATE, args = {Statement.class}), + @Signature(type = StatementHandler.class, method = MyBatisInterceptor.BATCH, args = {Statement.class}) +}) +public class MyBatisInterceptor implements Interceptor { + + /** + * 查询方法名 + */ + public final static String QUERY = "query"; + + /** + * 更新方法名 + */ + public final static String UPDATE = "update"; + + /** + * 批量方法名 + */ + public final static String BATCH = "batch"; + + @Resource + private MyBatisConfig conf; + + /** + * 拦截操作 + * + * @param invocation 调用参数 + * @return 结果 + */ + @Override + public Object intercept(@NonNull Invocation invocation) throws Throwable { + LogTrack log = LogTrackHolder.getDefNull(); + // 是否启动监控 + if (!conf.getMonitor() || Objects.isNull(log) || !log.save()) { + return invocation.proceed(); + } + // 执行结果 + Object result = null; + // 异常信息 + Exception err = null; + // 开始时间 + long startTime = System.currentTimeMillis(); + try { + result = invocation.proceed(); + return result; + } catch (Exception e) { + err = e; + throw e; + } finally { + // 结束时间 + long endTime = System.currentTimeMillis(); + // 获取当前线程日志信息 + LogTrack logTrack = LogTrackHolder.get(); + // 执行结果信息 + final Object execResult = result; + // 执行异常 + final Exception exception = err; + // 保存日志 + VirtualThreadTaskUtils.simple(() -> saveLog(logTrack, invocation, execResult, exception, startTime, endTime)); + } + } + + /** + * 保存日志信息 + * + * @param logTrack 日志信息 + * @param invocation 调用参数 + * @param execResult 执行结果 + * @param exception 执行异常 + * @param startTime 开始时间 + * @param endTime 结束时间 + */ + private void saveLog(LogTrack logTrack, Invocation invocation, Object execResult, Exception exception, long startTime, long endTime) { + // 获取运行参数 + Object runArg = invocation.getArgs()[CP_NUM0]; + // 读写标识 + String rw = QUERY.equals(invocation.getMethod().getName()) ? "读" : "写"; + // 执行时间 + long execTime = endTime - startTime; + // 是否为慢SQL + boolean slowSql = execTime >= conf.getSlowTime(); + // SQL语句 + String sql; + // SQL参数 + List params = null; + if (invocation.getTarget() instanceof StatementHandler statementHandler) { + BoundSql boundSql = statementHandler.getBoundSql(); + sql = boundSql.getSql(); + params = getBoundSqlParams(boundSql); + } else { + sql = runArg.toString(); + // 获取完整SQL + sql = sql.substring(sql.indexOf(CP_COLON) + CP_NUM1); + } + // 格式化Sql + sql = sql + .replace(CP_COMMA, ", ") + .replaceAll("\t+", CP_SPACE) + .replaceAll("\n+", CP_SPACE) + .replaceAll(" +", CP_SPACE) + .trim(); + // 分装日志信息 + JSONObject logData = JSONObject.of( + "startTime", InstantUtils.format(InstantUtils.from(startTime), TimePattern.ALL), + "execTime", execTime, + "result", execResult, + "slowSql", slowSql, + "sql", sql, + "params", params + ); + // 日志级别 + LogLevel level = LogLevel.INFO; + if (Objects.nonNull(exception)) { + level = LogLevel.ERROR; + } else if (slowSql) { + level = LogLevel.WARN; + } + LogBus.init(level, LogBusBaseType.MYBATIS, logTrack).title("SQL/" + rw).data(logData).error(exception).save(); + } + + /** + * 从BoundSql中获取参数值 + * + * @param boundSql MyBatis的BoundSql + * @return 参数集合 + */ + private List getBoundSqlParams(BoundSql boundSql) { + if (Objects.isNull(boundSql)) { + return null; + } + List parameterMappings = boundSql.getParameterMappings(); + if (CollectionUtils.isEmpty(parameterMappings)) { + return null; + } + Object parameterObject = boundSql.getParameterObject(); + Map additionalParams = boundSql.getAdditionalParameters(); + List params = new ArrayList<>(parameterMappings.size()); + MetaObject metaObject = Objects.isNull(parameterObject) ? null : SystemMetaObject.forObject(parameterObject); + for (ParameterMapping mapping : parameterMappings) { + String property = mapping.getProperty(); + Object value; + // 优先从additionalParameters获取(MyBatis-Plus分页参数等动态参数在这里) + if (additionalParams.containsKey(property)) { + value = additionalParams.get(property); + } else if (Objects.nonNull(metaObject) && metaObject.hasGetter(property)) { + value = metaObject.getValue(property); + } else if (parameterObject instanceof Map map) { + try { + value = map.get(property); + } catch (Exception e) { + value = "[无法解析:" + property + "]"; + } + } else { + value = parameterObject; + } + params.add(value); + } + return params; + } + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/mapper/MonitorDatabasesMapper.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/mapper/MonitorDatabasesMapper.java new file mode 100644 index 0000000..14f33e8 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/mapper/MonitorDatabasesMapper.java @@ -0,0 +1,37 @@ +package xtools.boot.db.mybatis.mapper; + +import com.alibaba.fastjson2.JSONObject; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

Title : MonitorDatabasesMapper

+ *

Description : MonitorDatabasesMapper

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 11:49 + */ +@Mapper +public interface MonitorDatabasesMapper { + + /** + * 监控MySQL + * + * @param sql SQL语句 + * @return 监控数据 + */ + @Select({ + "" + }) + List mysql(@Param("sql") String sql); + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/monitor/MySqlMonitor.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/monitor/MySqlMonitor.java new file mode 100644 index 0000000..a8e6850 --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/monitor/MySqlMonitor.java @@ -0,0 +1,43 @@ +package xtools.boot.db.mybatis.monitor; + +import com.alibaba.fastjson2.JSONObject; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import xtools.boot.api.exection.BizError; +import xtools.boot.db.mybatis.enums.MySqlMonitorEnums; +import xtools.boot.db.mybatis.mapper.MonitorDatabasesMapper; + +import java.util.List; +import java.util.Objects; + +/** + *

Title : MySqlMonitor

+ *

Description : MySqlMonitor

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 16:18 + */ +@Component +@RequiredArgsConstructor +public class MySqlMonitor { + + private final MonitorDatabasesMapper monitorDatabasesMapper; + + /** + * 获取MySQL监控信息 + * + * @param type 枚举 + * @return MySQL监控信息 + */ + public List info(MySqlMonitorEnums type) { + if (Objects.isNull(type)) { + throw new BizError("类型参数不能为空"); + } + return monitorDatabasesMapper.mysql(type.getSql()); + } + +} diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/selector/BootDbMybatisImportSelector.java b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/selector/BootDbMybatisImportSelector.java new file mode 100644 index 0000000..e21428a --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/java/xtools/boot/db/mybatis/selector/BootDbMybatisImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.db.mybatis.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootDbMybatisImportSelector

+ *

Description : BootDbMybatisImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootDbMybatisImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.db.mybatis"); + } + +} \ No newline at end of file diff --git a/xtools-boot-db/xtools-boot-db-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-db/xtools-boot-db-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..e24976e --- /dev/null +++ b/xtools-boot-db/xtools-boot-db-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.db.mybatis.BootDbMybatisConfiguration \ No newline at end of file diff --git a/xtools-boot-elasticsearch/pom.xml b/xtools-boot-elasticsearch/pom.xml new file mode 100644 index 0000000..9a404ba --- /dev/null +++ b/xtools-boot-elasticsearch/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-elasticsearch + + + + + + + org.xujun + xtools-extend + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-log + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/BootElasticsearchConfiguration.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/BootElasticsearchConfiguration.java new file mode 100644 index 0000000..a054d9f --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/BootElasticsearchConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.elasticsearch; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.elasticsearch.selector.BootElasticsearchImportSelector; + +/** + *

Title : BootElasticsearchConfiguration

+ *

Description : BootElasticsearchConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootElasticsearchImportSelector.class) +public class BootElasticsearchConfiguration { + + /** + * 构造方法 + */ + public BootElasticsearchConfiguration() { + ModuleLoadUtils.loadSuccess(BootElasticsearchConfiguration.class); + } + +} diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/enums/ElasticsearchMonitorEnums.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/enums/ElasticsearchMonitorEnums.java new file mode 100644 index 0000000..36d6095 --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/enums/ElasticsearchMonitorEnums.java @@ -0,0 +1,126 @@ +package xtools.boot.elasticsearch.enums; + +import xtools.boot.api.enums.BaseEnum; + +/** + *

Title : ElasticsearchMonitorEnums

+ *

Description : ElasticsearchMonitorEnums

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 10:31 + */ +public enum ElasticsearchMonitorEnums implements BaseEnum { + + // 集群索引信息 + INDICES(1, "集群索引信息", "/_cat/indices?v"), + // 集群健康信息 + HEALTH(2, "集群健康信息", "/_cat/health?v"), + // 集群分片信息 + SHARDS(3, "集群分片信息", "/_cat/shards?v"), + // 集群节点信息 + NODES(4, "集群节点信息", "/_cat/nodes?v"), + // 集群任务 + TASKS(5, "集群任务", "/_cat/tasks?v"), + // 集群段信息 + SEGMENTS(6, "集群段信息", "/_cat/segments?v"), + // 集群文档总数 + COUNT(7, "集群文档总数", "/_cat/count?v"), + // 集群shard的recovery过程 + RECOVERY(8, "集群shard的recovery过程", "/_cat/recovery?v"), + // 集群别名组 + ALIASES(9, "集群别名组", "/_cat/aliases?v"), + // 集群线程池任务 + THREAD_POOL(10, "集群线程池任务", "/_cat/thread_pool?v"), + // 集群插件信息 + PLUGINS(11, "集群插件信息", "/_cat/plugins?v"), + // 单节点的自定义属性 + NODEATTRS(12, "单节点的自定义属性", "/_cat/nodeattrs?v"), + ; + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 命令 + */ + private final String cmd; + + /** + * 初始化方法 + * + * @param code Code + * @param desc 说明 + * @param cmd 命令 + */ + ElasticsearchMonitorEnums(int code, String desc, String cmd) { + this.code = code; + this.desc = desc; + this.cmd = cmd; + } + + /** + * 判断枚举值类型 + * + * @param code 枚举值 + * @return 枚举值类型 + */ + public static ElasticsearchMonitorEnums valueOf(int code) { + for (ElasticsearchMonitorEnums type : values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("unknown code, code=" + code); + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + + /** + * 获取命令 + * + * @return 命令 + */ + public String cmd() { + return cmd; + } + +} \ No newline at end of file diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/monitor/ElasticsearchMonitor.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/monitor/ElasticsearchMonitor.java new file mode 100644 index 0000000..4a269e6 --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/monitor/ElasticsearchMonitor.java @@ -0,0 +1,71 @@ +package xtools.boot.elasticsearch.monitor; + +import com.alibaba.fastjson2.JSONObject; +import org.springframework.stereotype.Component; +import xtools.boot.api.exection.BizError; +import xtools.boot.elasticsearch.enums.ElasticsearchMonitorEnums; +import xtools.boot.elasticsearch.utils.EsUtils; +import xtools.core.ArrUtils; +import xtools.core.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1; + +/** + *

Title : ElasticsearchMonitor

+ *

Description : ElasticsearchMonitor

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 17:58 + */ +@Component +public class ElasticsearchMonitor { + + /** + * 获取Elasticsearch监控信息 + * + * @param type 枚举 + * @return Elasticsearch监控信息 + */ + public JSONObject info(ElasticsearchMonitorEnums type) { + if (Objects.isNull(type)) { + throw new BizError("类型参数不能为空"); + } + // 查询 + String result; + try { + result = EsUtils.exec(type.cmd(), null, String.class, true); + } catch (Exception e) { + throw new BizError("查询异常"); + } + // 处理数据 + result = result.replace("\n", "###").replaceAll("\\s+", " "); + List arr = ArrUtils.toStringList(result, "###"); + if (CollectionUtils.isEmpty(arr)) { + throw new BizError("类型参数不能为空"); + } + // 封装表头 + List titleList = ArrUtils.toStringList(arr.getFirst(), " "); + // 处理主体数据 + List dataList = new ArrayList<>(); + for (int i = CP_NUM1; i < arr.size(); i++) { + String line = arr.get(i); + List data = ArrUtils.toStringList(line, " "); + JSONObject lineData = new JSONObject(); + for (int j = CP_NUM0; j < data.size(); j++) { + lineData.put(titleList.get(j), data.get(j)); + } + dataList.add(lineData); + } + return JSONObject.of("title", titleList, "dataList", dataList); + } + +} diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/selector/BootElasticsearchImportSelector.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/selector/BootElasticsearchImportSelector.java new file mode 100644 index 0000000..b69a500 --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/selector/BootElasticsearchImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.elasticsearch.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootElasticsearchImportSelector

+ *

Description : BootElasticsearchImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootElasticsearchImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.elasticsearch"); + } + +} \ No newline at end of file diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsQueryUtils.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsQueryUtils.java new file mode 100644 index 0000000..0e2eee0 --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsQueryUtils.java @@ -0,0 +1,98 @@ +package xtools.boot.elasticsearch.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import xtools.core.ArrUtils; +import xtools.core.StringUtils; + +import java.time.Instant; +import java.util.Objects; + +import static xtools.base.config.Constants.CP_ASTERISK; +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1; + +/** + *

Title : EsQueryUtils

+ *

Description : EsQueryUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/14 08:58 + */ +public class EsQueryUtils { + + /** + * 设置精确查询 + * + * @param query 查询条件 + * @param filedName 字段名称 + * @param params 参数 + */ + public static void setMatchPhrase(JSONArray query, String filedName, Object params) { + if (params instanceof String txt && StringUtils.isNotBlank(txt)) { + query.add(JSONObject.of("match_phrase", JSONObject.of(filedName, txt))); + } else if (Objects.nonNull(params)) { + query.add(JSONObject.of("match_phrase", JSONObject.of(filedName, params))); + } + } + + /** + * 模糊查询 + * + * @param query 模糊查询 + * @param filedName 字段名称 + * @param params 参数 + */ + public static void setWildcard(JSONArray query, String filedName, String params) { + setWildcard(query, filedName, params, false); + } + + /** + * 模糊查询 + * + * @param query 模糊查询 + * @param filedName 字段名称 + * @param params 参数 + * @param caseInsensitive 是否忽略大小写 + */ + public static void setWildcard(JSONArray query, String filedName, String params, boolean caseInsensitive) { + if (StringUtils.isBlank(params)) { + return; + } + String value = CP_ASTERISK + params + CP_ASTERISK; + if (caseInsensitive) { + query.add(JSONObject.of("wildcard", JSONObject.of(filedName, JSONObject.of("value", value, "case_insensitive", true)))); + } else { + query.add(JSONObject.of("wildcard", JSONObject.of(filedName, value))); + } + } + + /** + * 时间范围查询 + * + * @param query 时间范围查询 + * @param filedName 字段名称 + * @param params 参数 + */ + public static void setTimeRange(JSONArray query, String filedName, Instant[] params) { + if (ArrUtils.isEmpty(params)) { + return; + } + long start = Long.MIN_VALUE; + long end = Long.MAX_VALUE; + Instant startTime = params[CP_NUM0]; + if (Objects.nonNull(startTime)) { + start = startTime.toEpochMilli(); + } + Instant endTime = params[CP_NUM1]; + if (Objects.nonNull(endTime)) { + end = endTime.toEpochMilli(); + } + query.add(JSONObject.of("range", JSONObject.of(filedName, JSONObject.of("gte", start, "lte", end)))); + } + +} diff --git a/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsUtils.java b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsUtils.java new file mode 100644 index 0000000..44a82f7 --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/java/xtools/boot/elasticsearch/utils/EsUtils.java @@ -0,0 +1,161 @@ +package xtools.boot.elasticsearch.utils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import xtools.boot.api.exection.BizError; +import xtools.boot.api.exection.BizWarning; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.core.BytesUtils; +import xtools.core.CollectionUtils; +import xtools.core.StringUtils; +import xtools.core.encrypt.Base64Utils; +import xtools.core.enums.LogLevel; +import xtools.core.extend.HttpUtils; +import xtools.extend.JsonUtils; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + *

Title : EsUtils

+ *

Description : EsUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2025年8月17日 下午6:24:22 + */ +@Component +public class EsUtils { + + /** + * 超时时间 + */ + private static final long TIMEOUT = 60 * 1000 * 5; + + /** + * 查询服务器地址 + */ + private static String host; + + /** + * 授权信息 + */ + private static String auth; + + + @Value("${spring.elasticsearch.uris:#{null}}") + private String uris; + @Value("${spring.elasticsearch.username:#{null}}") + private String username; + @Value("${spring.elasticsearch.password:#{null}}") + private String password; + + /** + * 执行 + * + * @param uri 执行的uri + * @return 执行结果 + */ + public static String exec(String uri) { + return exec(uri, null, String.class, false); + } + + /** + * 执行 + * + * @param uri 执行的uri + * @param req 请求参数 + * @return 执行结果 + */ + public static JSONObject exec(String uri, JSONObject req) { + return exec(uri, req, JSONObject.class, false); + } + + /** + * 执行 + * + * @param 结果类型 + * @param uri 执行的uri + * @param req 请求参数 + * @param clazz 结果类型 + * @param isGet 是否为get请求 + * @return 结果 + */ + public static T exec(String uri, JSONObject req, Class clazz, boolean isGet) { + // 参数校验 + if (StringUtils.isBlank(uri)) { + throw new BizWarning("uri不能为空"); + } + if (StringUtils.isBlank(host)) { + // 初始化 + EsUtils bean = SpringContextUtils.getBean(EsUtils.class); + bean.init(); + } + HttpUtils http = HttpUtils.init(host + uri).addHeader("Content-Type", "application/json").timeOut(TIMEOUT); + if (StringUtils.isNotBlank(auth)) { + http.addHeader("Authorization", "Basic " + auth); + } + http.setLogCallBack(log -> { + byte[] resp = log.resp(); + String respData = BytesUtils.isEmpty(resp) ? null : new String(resp); + JSONObject logData = JSONObject.of( + "req", log.reqParam(), + "resp", JsonUtils.isJson(respData) ? JSONObject.parseObject(respData) : respData, + "execTime", log.execTime() + ); + Throwable error = log.error(); + // 保存日志 + LogBus.init(Objects.isNull(error) ? LogLevel.INFO : LogLevel.ERROR, LogBusBaseType.ELASTICSEARCH) + .data(logData) + .error(error) + .save(); + }); + // 查询结果 + String result; + if (isGet) { + result = http.get(); + } else { + result = http.post(CollectionUtils.isEmpty(req) ? null : req.toString()); + } + if (StringUtils.isBlank(result)) { + throw new BizWarning("ES执行结果为空"); + } + return JSON.to(clazz, result); + } + + /** + * 获取授权信息 + * + * @param username 用户名 + * @param password 密码 + * @return 授权信息 + */ + private static String getAuth(String username, String password) { + if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { + return null; + } + return Base64Utils.encodeToStr((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + } + + /** + * 初始化 + */ + public void init() { + if (StringUtils.isNotBlank(host)) { + return; + } + if (StringUtils.isBlank(uris)) { + throw new BizError("Es工具类初始化失败, spring.elasticsearch.uris 不能为空"); + } + EsUtils.host = uris; + EsUtils.auth = getAuth(username, password); + } + +} diff --git a/xtools-boot-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..7e4b6dd --- /dev/null +++ b/xtools-boot-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.elasticsearch.BootElasticsearchConfiguration \ No newline at end of file diff --git a/xtools-boot-idempotent/pom.xml b/xtools-boot-idempotent/pom.xml new file mode 100644 index 0000000..478a6d9 --- /dev/null +++ b/xtools-boot-idempotent/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-idempotent + pom + + + + xtools-boot-idempotent-base + xtools-boot-idempotent-web + + + diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/pom.xml b/xtools-boot-idempotent/xtools-boot-idempotent-base/pom.xml new file mode 100644 index 0000000..964cb32 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.xujun + xtools-boot-idempotent + 5.1.0 + + xtools-boot-idempotent-base + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-cache-redis + + + + org.xujun + xtools-boot-api + + + + + + + org.springframework.boot + spring-boot-starter-aspectj + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/BootIdempotentBaseConfiguration.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/BootIdempotentBaseConfiguration.java new file mode 100644 index 0000000..58cec31 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/BootIdempotentBaseConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.idempotent.base; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.idempotent.base.selector.BootIdempotentBaseImportSelector; + +/** + *

Title : BootIdempotentBaseConfiguration

+ *

Description : BootIdempotentBaseConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/29 10:00 + */ +@Import(BootIdempotentBaseImportSelector.class) +public class BootIdempotentBaseConfiguration { + + /** + * 构造方法 + */ + public BootIdempotentBaseConfiguration() { + ModuleLoadUtils.loadSuccess(BootIdempotentBaseConfiguration.class); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/annotation/Idempotent.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/annotation/Idempotent.java new file mode 100644 index 0000000..60044cb --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/annotation/Idempotent.java @@ -0,0 +1,61 @@ +package xtools.boot.idempotent.base.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Title : Idempotent

+ *

Description : Idempotent

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Idempotent { + + /** + * Redis Key 前缀 + */ + String prefix() default "idempotent"; + + /** + * 幂等 Key(SpEL 表达式),为空时使用默认策略: 类名+方法名+参数摘要 + * 如: #orderId, #p0, #req.id + * + * @return SpEL 表达式 + */ + String key() default ""; + + /** + * 幂等有效时间(秒) + * + * @return 有效时间 + */ + long expire() default 5; + + /** + * 重复提交提示信息 + * + * @return 提示信息 + */ + String message() default "请勿重复提交"; + + /** + * 是否在方法执行成功后立即删除 Key(放行下一次提交). + * false: Key 保留至 expire 过期,实现时间窗口内的幂等(默认) + * true: 方法成功后立即释放,仅防止并发重复提交 + * + * @return 是否立即删除 Key + */ + boolean delKey() default false; + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/aspect/IdempotentAspect.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/aspect/IdempotentAspect.java new file mode 100644 index 0000000..14d9d62 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/aspect/IdempotentAspect.java @@ -0,0 +1,65 @@ +package xtools.boot.idempotent.base.aspect; + +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import xtools.boot.api.exection.BizWarning; +import xtools.boot.idempotent.base.annotation.Idempotent; +import xtools.boot.idempotent.base.key.IdempotentKeyResolver; +import xtools.boot.idempotent.base.service.IdempotentService; + +/** + *

Title : IdempotentAspect

+ *

Description : IdempotentAspect

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Aspect +@Component +@RequiredArgsConstructor +public class IdempotentAspect { + + /** + * 幂等服务 + */ + private final IdempotentService idempotentService; + + /** + * 幂等 Key 解析器 + */ + private final IdempotentKeyResolver idempotentKeyResolver; + + /** + * 幂等拦截 + * + * @param joinPoint 连接点 + * @param idempotent 幂等注解 + * @return 方法执行结果 + * @throws Throwable 方法执行异常 + */ + @Around("@annotation(idempotent)") + public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { + // 解析 Key + String key = idempotentKeyResolver.resolve(joinPoint, idempotent); + // 尝试获取幂等锁,失败则视为重复提交 + boolean acquired = idempotentService.tryAcquire(key, idempotent.expire()); + if (!acquired) { + throw new BizWarning(idempotent.message()); + } + // 执行业务 + Object result = joinPoint.proceed(); + if (idempotent.delKey()) { + // 成功后立即释放 + idempotentService.release(key); + } + return result; + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/key/IdempotentKeyResolver.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/key/IdempotentKeyResolver.java new file mode 100644 index 0000000..f91a5e8 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/key/IdempotentKeyResolver.java @@ -0,0 +1,115 @@ +package xtools.boot.idempotent.base.key; + +import com.alibaba.fastjson2.JSON; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; +import xtools.boot.idempotent.base.annotation.Idempotent; +import xtools.core.StringUtils; +import xtools.core.encrypt.Md5Utils; + +import java.lang.reflect.Method; +import java.util.Arrays; + +import static xtools.base.config.Constants.CP_COLON; +import static xtools.base.config.Constants.CP_EMPTY; +import static xtools.base.config.Constants.CP_NUM0; + +/** + *

Title : IdempotentKeyResolver

+ *

Description : IdempotentKeyResolver

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Component +public class IdempotentKeyResolver { + + /** + * SpEL 解析器 + */ + private static final ExpressionParser PARSER = new SpelExpressionParser(); + + /** + * 参数名发现器 + */ + private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + /** + * 解析幂等 Key + * + * @param joinPoint 连接点 + * @param idempotent 幂等注解 + * @return Redis Key + */ + public String resolve(JoinPoint joinPoint, Idempotent idempotent) { + // 获取目标方法与参数 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Object[] args = joinPoint.getArgs(); + Object target = joinPoint.getTarget(); + + // Key 片段:配置了 SpEL 则按 SpEL 求值,否则使用默认策略 + String keyPart; + if (StringUtils.isBlank(idempotent.key())) { + keyPart = defaultKey(target.getClass(), method, args); + } else { + keyPart = spelKey(method, args, target, idempotent.key()); + } + return idempotent.prefix() + CP_COLON + keyPart; + } + + /** + * 默认 Key:类名 + 方法名 + 参数摘要 + * + * @param targetClass 目标类 + * @param method 方法 + * @param args 参数 + * @return Key 片段 + */ + private String defaultKey(Class targetClass, Method method, Object[] args) { + String argsDigest = Md5Utils.encryptToString(JSON.toJSONString(Arrays.asList(args))); + return targetClass.getSimpleName() + CP_COLON + method.getName() + CP_COLON + argsDigest; + } + + /** + * SpEL Key:支持 #参数名,#p0,#a0 及 root 对象属性 + * + * @param method 方法 + * @param args 参数 + * @param target 目标对象 + * @param spel SpEL 表达式 + * @return Key 片段 + */ + private String spelKey(Method method, Object[] args, Object target, String spel) { + // 构建求值上下文,root 对象为目标对象 + StandardEvaluationContext context = new StandardEvaluationContext(target); + context.setRootObject(target); + // 绑定参数名与索引别名(#p0,#a0) + String[] paramNames = NAME_DISCOVERER.getParameterNames(method); + if (paramNames != null) { + for (int i = CP_NUM0; i < paramNames.length && i < args.length; i++) { + context.setVariable(paramNames[i], args[i]); + } + } + for (int i = CP_NUM0; i < args.length; i++) { + context.setVariable("p" + i, args[i]); + context.setVariable("a" + i, args[i]); + } + // 解析表达式 + Expression expression = PARSER.parseExpression(spel); + Object value = expression.getValue(context); + return value == null ? CP_EMPTY : value.toString(); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/selector/BootIdempotentBaseImportSelector.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/selector/BootIdempotentBaseImportSelector.java new file mode 100644 index 0000000..1b951b8 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/selector/BootIdempotentBaseImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.idempotent.base.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootIdempotentBaseImportSelector

+ *

Description : BootIdempotentBaseImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/29 10:00 + */ +public class BootIdempotentBaseImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描核心层包 + scanner.scan("xtools.boot.idempotent.base"); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/service/IdempotentService.java b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/service/IdempotentService.java new file mode 100644 index 0000000..76f7b35 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/java/xtools/boot/idempotent/base/service/IdempotentService.java @@ -0,0 +1,58 @@ +package xtools.boot.idempotent.base.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import xtools.boot.cache.redis.base.RedisService; +import xtools.core.StringUtils; +import xtools.core.UuidUtils; + +import java.util.concurrent.TimeUnit; + +/** + *

Title : IdempotentService

+ *

Description : IdempotentService

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Component +@RequiredArgsConstructor +public class IdempotentService { + + private final RedisService redisService; + + /** + * 尝试获取幂等锁 + * + * @param key 锁 Key + * @param expire 有效时间(时间窗口) + * @return true or false + */ + public boolean tryAcquire(String key, long expire) { + // 参数校验 + if (StringUtils.isBlank(key) || expire <= 0) { + return true; + } + Boolean result = redisService.setNx(key, UuidUtils.get(), expire, TimeUnit.SECONDS); + return Boolean.TRUE.equals(result); + } + + /** + * 释放幂等锁 + * + * @param key 锁 Key + * @return true or false + */ + public boolean release(String key) { + if (StringUtils.isBlank(key)) { + return false; + } + Boolean result = redisService.del(key); + return Boolean.TRUE.equals(result); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..9ea5256 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.idempotent.base.BootIdempotentBaseConfiguration diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/pom.xml b/xtools-boot-idempotent/xtools-boot-idempotent-web/pom.xml new file mode 100644 index 0000000..3cbfb2d --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + org.xujun + xtools-boot-idempotent + 5.1.0 + + xtools-boot-idempotent-web + + + + + + + org.xujun + xtools-boot-idempotent-base + + + + + + + org.xujun + xtools-web + + + + + + org.springframework + spring-web + true + + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/BootIdempotentWebConfiguration.java b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/BootIdempotentWebConfiguration.java new file mode 100644 index 0000000..7e3da31 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/BootIdempotentWebConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.idempotent.web; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.idempotent.web.selector.BootIdempotentWebImportSelector; + +/** + *

Title : BootIdempotentWebConfiguration

+ *

Description : BootIdempotentWebConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/29 10:00 + */ +@Import(BootIdempotentWebImportSelector.class) +public class BootIdempotentWebConfiguration { + + /** + * 构造方法 + */ + public BootIdempotentWebConfiguration() { + ModuleLoadUtils.loadSuccess(BootIdempotentWebConfiguration.class); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/annotation/RepeatSubmit.java b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/annotation/RepeatSubmit.java new file mode 100644 index 0000000..8bf53e7 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/annotation/RepeatSubmit.java @@ -0,0 +1,78 @@ +package xtools.boot.idempotent.web.annotation; + +import xtools.boot.idempotent.web.enums.RepeatSubmitKeyType; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Title : RepeatSubmit

+ *

Description : RepeatSubmit

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RepeatSubmit { + + /** + * Redis Key 前缀 + * + * @return 前缀 + */ + String prefix() default "repeat-submit"; + + /** + * 防重复提交时间间隔(秒) + * + * @return 时间间隔 + */ + long interval() default 5; + + /** + * 重复提交提示信息 + * + * @return 提示信息 + */ + String message() default "请勿重复提交"; + + /** + * Key 生成策略 + * + * @return Key 生成策略 + */ + RepeatSubmitKeyType keyType() default RepeatSubmitKeyType.DEFAULT; + + /** + * Token 请求头名称 + * + * @return 请求头名称 + */ + String tokenHeader() default "Bearer Authorization"; + + /** + * 是否将请求参数(查询参数/表单)参与 Key 计算 + * + * @return 是否参与 + */ + boolean includeParams() default true; + + /** + * 是否在方法执行成功后立即删除 Key(放行下一次提交). + * false: Key 保留至 expire 过期,实现时间窗口内的幂等(默认) + * true: 方法成功后立即释放,仅防止并发重复提交 + * + * @return 是否立即删除 Key + */ + boolean delKey() default false; + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/aspect/RepeatSubmitAspect.java b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/aspect/RepeatSubmitAspect.java new file mode 100644 index 0000000..5efd817 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/aspect/RepeatSubmitAspect.java @@ -0,0 +1,148 @@ +package xtools.boot.idempotent.web.aspect; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import xtools.boot.api.exection.BizWarning; +import xtools.boot.idempotent.base.service.IdempotentService; +import xtools.boot.idempotent.web.annotation.RepeatSubmit; +import xtools.core.StringUtils; +import xtools.core.encrypt.Md5Utils; +import xtools.web.HeaderUtils; + +import java.util.StringJoiner; + +import static xtools.base.config.Constants.CP_COLON; +import static xtools.base.config.Constants.CP_EMPTY; + +/** + *

Title : RepeatSubmitAspect

+ *

Description : RepeatSubmitAspect

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class RepeatSubmitAspect { + + /** + * 幂等服务 + */ + private final IdempotentService idempotentService; + + /** + * 防重复提交拦截 + * + * @param joinPoint 连接点 + * @param repeatSubmit 防重复提交注解 + * @return 方法执行结果 + * @throws Throwable 方法执行异常 + */ + @Around("@annotation(repeatSubmit)") + public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return joinPoint.proceed(); + } + // 生成幂等 Key + String key = buildKey(attributes.getRequest(), repeatSubmit); + boolean acquired = idempotentService.tryAcquire(key, repeatSubmit.interval()); + if (!acquired) { + throw new BizWarning(repeatSubmit.message()); + } + // 执行业务 + Object result = joinPoint.proceed(); + if (repeatSubmit.delKey()) { + // 成功后立即释放 + idempotentService.release(key); + } + return result; + } + + /** + * 构建幂等 Key + * + * @param request 请求 + * @param repeatSubmit 注解 + * @return Key + */ + private String buildKey(HttpServletRequest request, RepeatSubmit repeatSubmit) { + StringJoiner joiner = new StringJoiner(CP_COLON); + joiner.add(repeatSubmit.prefix()); + switch (repeatSubmit.keyType()) { + case TOKEN -> joiner.add(token(request, repeatSubmit.tokenHeader())); + case IP -> joiner.add(clientIp(request)); + case URI -> { + } + default -> joiner.add(identity(request, repeatSubmit.tokenHeader())); + } + joiner.add(HeaderUtils.getUri(request)); + if (repeatSubmit.includeParams()) { + String paramsDigest = paramsDigest(request); + if (StringUtils.isNotBlank(paramsDigest)) { + joiner.add(paramsDigest); + } + } + return joiner.toString(); + } + + /** + * 客户端标识:Token 优先,无 Token 则使用 IP + * + * @param request 请求 + * @param headerName Token 头名称 + * @return 客户端标识 + */ + private String identity(HttpServletRequest request, String headerName) { + String token = token(request, headerName); + return StringUtils.isNotBlank(token) ? token : clientIp(request); + } + + /** + * 获取 Token(去除 Bearer 前缀) + * + * @param request 请求 + * @param headerName 头名称 + * @return Token + */ + private String token(HttpServletRequest request, String headerName) { + String header = HeaderUtils.getHeader(request, headerName); + return StringUtils.isBlank(header) ? CP_EMPTY : header.trim(); + } + + /** + * 获取客户端 IP + * + * @param request 请求 + * @return IP + */ + private String clientIp(HttpServletRequest request) { + String ip = HeaderUtils.getIp(request); + return ip == null ? CP_EMPTY : ip; + } + + /** + * 请求参数摘要(MD5) + * + * @param request 请求 + * @return 摘要 + */ + private String paramsDigest(HttpServletRequest request) { + String params = HeaderUtils.getParamsToString(request); + return StringUtils.isBlank(params) ? null : Md5Utils.encryptToString(params); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/enums/RepeatSubmitKeyType.java b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/enums/RepeatSubmitKeyType.java new file mode 100644 index 0000000..640ecfb --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/enums/RepeatSubmitKeyType.java @@ -0,0 +1,36 @@ +package xtools.boot.idempotent.web.enums; + +/** + *

Title : RepeatSubmitKeyType

+ *

Description : RepeatSubmitKeyType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/28 09:30 + */ +public enum RepeatSubmitKeyType { + + /** + * Token + URI + */ + TOKEN, + + /** + * 客户端 IP + URI + */ + IP, + + /** + * URI + */ + URI, + + /** + * 默认:Token(无则 IP) + URI + */ + DEFAULT, + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/selector/BootIdempotentWebImportSelector.java b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/selector/BootIdempotentWebImportSelector.java new file mode 100644 index 0000000..4432be9 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/java/xtools/boot/idempotent/web/selector/BootIdempotentWebImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.idempotent.web.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootIdempotentWebImportSelector

+ *

Description : BootIdempotentWebImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/07/29 10:00 + */ +public class BootIdempotentWebImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描 Controller 包装层包 + scanner.scan("xtools.boot.idempotent.web"); + } + +} diff --git a/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..433aa63 --- /dev/null +++ b/xtools-boot-idempotent/xtools-boot-idempotent-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.idempotent.web.BootIdempotentWebConfiguration diff --git a/xtools-boot-ip/pom.xml b/xtools-boot-ip/pom.xml new file mode 100644 index 0000000..9c4ad54 --- /dev/null +++ b/xtools-boot-ip/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-ip + + + + + + + + org.lionsoul + ip2region + + + org.xujun + xtools-extend + + + + + + + org.xujun + xtools-boot-core + + + + + + org.springframework.boot + spring-boot + + + + \ No newline at end of file diff --git a/xtools-boot-ip/src/main/java/xtools/boot/ip/BootIpConfiguration.java b/xtools-boot-ip/src/main/java/xtools/boot/ip/BootIpConfiguration.java new file mode 100644 index 0000000..980f40b --- /dev/null +++ b/xtools-boot-ip/src/main/java/xtools/boot/ip/BootIpConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.ip; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.ip.selector.BootIpImportSelector; + +/** + *

Title : BootIpConfiguration

+ *

Description : BootIpConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootIpImportSelector.class) +public class BootIpConfiguration { + + /** + * 构造方法 + */ + public BootIpConfiguration() { + ModuleLoadUtils.loadSuccess(BootIpConfiguration.class); + } + +} diff --git a/xtools-boot-ip/src/main/java/xtools/boot/ip/init/InitIp.java b/xtools-boot-ip/src/main/java/xtools/boot/ip/init/InitIp.java new file mode 100644 index 0000000..3e13761 --- /dev/null +++ b/xtools-boot-ip/src/main/java/xtools/boot/ip/init/InitIp.java @@ -0,0 +1,49 @@ +package xtools.boot.ip.init; + +import org.jspecify.annotations.NonNull; +import org.lionsoul.ip2region.xdb.Version; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; +import xtools.base.config.Constants; +import xtools.boot.ip.utils.IpUtils; +import xtools.extend.IpLocalUtils; + +import java.io.IOException; +import java.io.InputStream; + +/** + *

Title : InitIp

+ *

Description : InitIp

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/16 10:21 + */ +@Component +@Order(Constants.CP_NUM50) +public class InitIp implements ApplicationRunner { + + private static final String IP_FILE = "ip/ip2region_v4.xdb"; + + @Override + public void run(@NonNull ApplicationArguments args) throws Exception { + init(); + } + + /** + * 初始化IP库 + */ + private void init() throws IOException { + ClassPathResource classPathResource = new ClassPathResource(IP_FILE); + try (InputStream is = classPathResource.getInputStream()) { + IpLocalUtils.init(is, Version.IPv4); + IpUtils.init(); + } + } +} diff --git a/xtools-boot-ip/src/main/java/xtools/boot/ip/selector/BootIpImportSelector.java b/xtools-boot-ip/src/main/java/xtools/boot/ip/selector/BootIpImportSelector.java new file mode 100644 index 0000000..4fabf66 --- /dev/null +++ b/xtools-boot-ip/src/main/java/xtools/boot/ip/selector/BootIpImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.ip.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootIpImportSelector

+ *

Description : BootIpImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootIpImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.ip"); + } + +} \ No newline at end of file diff --git a/xtools-boot-ip/src/main/java/xtools/boot/ip/utils/IpUtils.java b/xtools-boot-ip/src/main/java/xtools/boot/ip/utils/IpUtils.java new file mode 100644 index 0000000..2c38560 --- /dev/null +++ b/xtools-boot-ip/src/main/java/xtools/boot/ip/utils/IpUtils.java @@ -0,0 +1,95 @@ +package xtools.boot.ip.utils; + +import xtools.boot.api.exection.BizError; +import xtools.core.StringUtils; +import xtools.core.encrypt.Md5Utils; +import xtools.extend.IpLocalUtils; +import xtools.extend.dto.IpAddrDto; + +import java.util.Objects; +import java.util.StringJoiner; + +import static xtools.base.config.Constants.CP_COMMA; + +/** + *

Title : IpUtils

+ *

Description : IpUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/16 10:41 + */ +public class IpUtils { + + /** + * 是否初始化 + */ + private static boolean init = false; + + /** + * 初始化 + */ + public static void init() { + init = true; + } + + /** + * 查询IP对应地址 + * + * @param ip IP + * @return IP对应地址 + */ + public static IpAddrDto search(String ip) { + if (!init) { + throw new BizError("请先初始化IpUtils"); + } + return IpLocalUtils.search(ip); + } + + /** + * 获取IP对应地址 + * + * @param ip IP + * @return IP对应地址 + */ + public static String searchAddr(String ip) { + return searchAddr(search(ip)); + } + + /** + * 获取IP对应地址 + * + * @param addr 地址信息 + * @return IP对应地址 + */ + public static String searchAddr(IpAddrDto addr) { + if (Objects.isNull(addr)) { + return "未知"; + } + StringJoiner joiner = new StringJoiner(CP_COMMA); + if (StringUtils.isNotBlank(addr.getCountry())) { + joiner.add(addr.getCountry()); + } + if (StringUtils.isNotBlank(addr.getProvince())) { + joiner.add(addr.getProvince()); + } + if (StringUtils.isNotBlank(addr.getCity())) { + joiner.add(addr.getCity()); + } + return joiner.toString(); + } + + /** + * 获取IP对应地址的编码 + * + * @param addr 地址信息 + * @return IP对应地址的编码 + */ + public static String getCode(IpAddrDto addr) { + return Md5Utils.encryptToString(searchAddr(addr)); + } + +} diff --git a/xtools-boot-ip/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-ip/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..e7697a4 --- /dev/null +++ b/xtools-boot-ip/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.ip.BootIpConfiguration \ No newline at end of file diff --git a/xtools-boot-ip/src/main/resources/ip/ip2region_v4.xdb b/xtools-boot-ip/src/main/resources/ip/ip2region_v4.xdb new file mode 100644 index 0000000..6b53b1f Binary files /dev/null and b/xtools-boot-ip/src/main/resources/ip/ip2region_v4.xdb differ diff --git a/xtools-boot-jasypt/pom.xml b/xtools-boot-jasypt/pom.xml new file mode 100644 index 0000000..980cd56 --- /dev/null +++ b/xtools-boot-jasypt/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-jasypt + + + + + + + org.xujun + xtools-boot-core + + + + + + + com.github.ulisesbocchio + jasypt-spring-boot-starter + compile + + + + + \ No newline at end of file diff --git a/xtools-boot-jasypt/src/main/java/xtools/boot/jasypt/BootJasyptConfiguration.java b/xtools-boot-jasypt/src/main/java/xtools/boot/jasypt/BootJasyptConfiguration.java new file mode 100644 index 0000000..230e46c --- /dev/null +++ b/xtools-boot-jasypt/src/main/java/xtools/boot/jasypt/BootJasyptConfiguration.java @@ -0,0 +1,25 @@ +package xtools.boot.jasypt; + +import xtools.boot.core.utils.ModuleLoadUtils; + +/** + *

Title : BootJasyptConfiguration

+ *

Description : BootJasyptConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootJasyptConfiguration { + + /** + * 构造方法 + */ + public BootJasyptConfiguration() { + ModuleLoadUtils.loadSuccess(BootJasyptConfiguration.class); + } + +} diff --git a/xtools-boot-jasypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-jasypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..2450a23 --- /dev/null +++ b/xtools-boot-jasypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.jasypt.BootJasyptConfiguration \ No newline at end of file diff --git a/xtools-boot-job/pom.xml b/xtools-boot-job/pom.xml new file mode 100644 index 0000000..5e08639 --- /dev/null +++ b/xtools-boot-job/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-job + + + + xtools-boot-job-xxl + + + \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/README.md b/xtools-boot-job/xtools-boot-job-xxl/README.md new file mode 100644 index 0000000..fc71a1f --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/README.md @@ -0,0 +1,41 @@ +# xxl-job使用 + +## 官网地址 + +[xxl-job官网](https://www.xuxueli.com/xxl-job) + +## 操作说明 + +> 1.先安装xxl-job-admin +> 2.配置application-boot-xxl-job.yaml文件,可以参考application-boot-xxl-job-demo.yaml +> 3.运行项目,即可在admin配置定时任务了 + +## docker命令 + +```sh +# docker命令启动 +docker run -d \ +-e PARAMS=" \ +--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \ +--spring.datasource.username=root \ +--spring.datasource.password=root \ +--xxl.job.accessToken=xtools-app-xxl-job \ +" \ +-p 26000:8080 \ +-v /data1/xxl-job-admin/applogs:/data/applogs \ +--name xxl-job-admin \ +registry.cn-hangzhou.aliyuncs.com/xujun-public/xxl-job-admin:3.3.2 +``` + +## docker-compose + +> 参考docker-compose目录 + +## 访问 + +```sh +# 访问地址 +http://[ip]:[port]/xxl-job-admin +# 账号/密码 +admin/123456 +``` \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/docker-compose/docker-compose.yml b/xtools-boot-job/xtools-boot-job-xxl/docker-compose/docker-compose.yml new file mode 100644 index 0000000..0d91c13 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/docker-compose/docker-compose.yml @@ -0,0 +1,23 @@ +services: + xxl-job-admin: + container_name: xxl-job-admin + image: registry.cn-hangzhou.aliyuncs.com/xujun-public/xxl-job-admin:3.3.2 + pull_policy: always + ports: + - "26000:8080" + volumes: + - "/etc/localtime:/etc/localtime:ro" + - "/data1/xxl-job-admin/applogs:/data/applogs" + env_file: + - env/container.env + restart: always + networks: + - xxl_job_net + +networks: + xxl_job_net: + name: xxl_job_net + driver: bridge + ipam: + config: + - subnet: 172.201.0.0/16 \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/docker-compose/evn/container.env b/xtools-boot-job/xtools-boot-job-xxl/docker-compose/evn/container.env new file mode 100644 index 0000000..1febe18 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/docker-compose/evn/container.env @@ -0,0 +1,2 @@ +# xxl-job-admin配置 +PARAMS=--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=root --xxl.job.accessToken=xtools-app-xxl-job \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/pom.xml b/xtools-boot-job/xtools-boot-job-xxl/pom.xml new file mode 100644 index 0000000..ac8a032 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.xujun + xtools-boot-job + 5.1.0 + + xtools-boot-job-xxl + + + + + + + org.xujun + xtools-boot-core + + + + + + com.xuxueli + xxl-job-core + + + + + org.springframework.boot + spring-boot + + + + \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/BootXxlJobConfiguration.java b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/BootXxlJobConfiguration.java new file mode 100644 index 0000000..97c80d9 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/BootXxlJobConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.job.xxl; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.job.xxl.selector.BootXxlJobImportSelector; + +/** + *

Title : BootXxlJobConfiguration

+ *

Description : BootXxlJobConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootXxlJobImportSelector.class) +public class BootXxlJobConfiguration { + + /** + * 构造方法 + */ + public BootXxlJobConfiguration() { + ModuleLoadUtils.loadSuccess(BootXxlJobConfiguration.class); + } + +} diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/config/XxlJobConfig.java b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/config/XxlJobConfig.java new file mode 100644 index 0000000..28b2c7a --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/config/XxlJobConfig.java @@ -0,0 +1,125 @@ +package xtools.boot.job.xxl.config; + +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import xtools.core.StringUtils; + +/** + *

Title : XxlJobConfig

+ *

Description : XxlJobConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/10 14:06 + */ +@Slf4j +@Configuration +public class XxlJobConfig { + + /** + * 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔.执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册; + */ + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + /** + * 调度中心通讯TOKEN [选填]:非空时启用; + */ + @Value("${xxl.job.admin.accessToken}") + private String accessToken; + + /** + * 调度中心通讯超时时间[选填],单位秒;默认3s; + */ + @Value("${xxl.job.admin.timeout}") + private int timeout; + + /** + * 执行器启用开关 [选填]:默认开启,关闭时不进行执行器初始化; + */ + @Getter + @Value("${xxl.job.executor.enabled:false}") + private Boolean enabled; + + /** + * 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册 + */ + @Value("${xxl.job.executor.appName}") + private String appName; + + /** + * 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址.从而更灵活支持容器类型执行器动态IP和动态映射端口问题. + */ + @Value("${xxl.job.executor.address}") + private String address; + + /** + * 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; + */ + @Value("${xxl.job.executor.ip}") + private String ip; + + /** + * 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口; + */ + @Value("${xxl.job.executor.port}") + private int port; + + /** + * 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; + */ + @Value("${xxl.job.executor.logPath}") + private String logPath; + + /** + * 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能; + */ + @Value("${xxl.job.executor.logRetentionDays}") + private int logRetentionDays; + + /** + * 任务扫描排除路径 [选填] :任务扫描时忽略指定包路径下的Bean;支持配置包路径前缀,多个逗号分隔; + */ + @Value("${xxl.job.executor.excludedPackage}") + private String excludedPackage; + + /** + * xxl-job 执行器 + * + * @return xxl-job 执行器 + */ + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setTimeout(timeout); + xxlJobSpringExecutor.setEnabled(enabled); + xxlJobSpringExecutor.setAppname(appName); + if (StringUtils.isNotBlank(address)) { + xxlJobSpringExecutor.setAddress(address); + } + if (StringUtils.isNotBlank(ip)) { + xxlJobSpringExecutor.setIp(ip); + } + if (port > 0) { + xxlJobSpringExecutor.setPort(port); + } + if (StringUtils.isNotBlank(logPath)) { + xxlJobSpringExecutor.setLogPath(logPath); + } + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + if (StringUtils.isNotBlank(excludedPackage)) { + xxlJobSpringExecutor.setExcludedPackage(excludedPackage); + } + return xxlJobSpringExecutor; + } + +} diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/init/InitXxlJob.java b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/init/InitXxlJob.java new file mode 100644 index 0000000..84d235b --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/init/InitXxlJob.java @@ -0,0 +1,100 @@ +package xtools.boot.job.xxl.init; + +import com.xxl.job.core.executor.XxlJobExecutor; +import com.xxl.job.core.handler.IJobHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import xtools.base.config.Constants; +import xtools.boot.core.interfaces.JobInterface; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.job.xxl.config.XxlJobConfig; +import xtools.core.CollectionUtils; + +import java.util.Collection; +import java.util.Objects; + +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1; + +/** + *

Title : InitIp

+ *

Description : InitIp

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/16 10:21 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@Order(Constants.CP_NUM90) +public class InitXxlJob implements ApplicationRunner { + + private final XxlJobConfig xxlJobConfig; + + @Override + public void run(@NonNull ApplicationArguments args) { + init(); + } + + /** + * 初始化 + */ + private void init() { + Boolean enabled = xxlJobConfig.getEnabled(); + if (Objects.isNull(enabled) || !enabled) { + log.info("xxl-job未启用"); + return; + } + Collection beanList = SpringContextUtils.getBeanList(JobInterface.class); + if (CollectionUtils.isEmpty(beanList)) { + log.info("没有需要执行xxl-job的任务"); + return; + } + beanList.forEach(item -> { + try { + String name = item.getClass().getSimpleName(); + name = Character.toLowerCase(name.charAt(CP_NUM0)) + name.substring(CP_NUM1); + registerXxlJob(name, item); + log.info("注册xxl-job任务成功:{}", name); + } catch (Exception e) { + log.error("注册xxl-job任务异常", e); + } + }); + } + + /** + * 注册任务 + * + * @param name 任务名称 + * @param job 任务 + */ + private void registerXxlJob(String name, JobInterface job) { + IJobHandler handler = new IJobHandler() { + @Override + public void execute() throws Exception { + job.execute(); + } + + @Override + public void init() { + job.init(); + } + + @Override + public void destroy() { + job.destroy(); + } + }; + // 注册到XXL-JOB + XxlJobExecutor.getInstance().registryJobHandler(name, handler); + } +} diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/selector/BootXxlJobImportSelector.java b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/selector/BootXxlJobImportSelector.java new file mode 100644 index 0000000..d8812eb --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/java/xtools/boot/job/xxl/selector/BootXxlJobImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.job.xxl.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootXxlJobImportSelector

+ *

Description : BootXxlJobImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootXxlJobImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.job.xxl"); + } + +} \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..e88d062 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.job.xxl.BootXxlJobConfiguration \ No newline at end of file diff --git a/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/application-boot-xxl-job-demo.yaml b/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/application-boot-xxl-job-demo.yaml new file mode 100644 index 0000000..e704268 --- /dev/null +++ b/xtools-boot-job/xtools-boot-job-xxl/src/main/resources/application-boot-xxl-job-demo.yaml @@ -0,0 +1,26 @@ +xxl: + job: + admin: + # 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔.执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册; + addresses: http://10.1.0.12:8080/xxl-job-admin + # 调度中心通讯TOKEN [选填]:非空时启用; + accessToken: xtools-app-xxl-job + # 调度中心通讯超时时间[选填],单位秒;默认3s; + timeout: 3 + executor: + # 执行器启用开关 [选填]:默认开启,关闭时不进行执行器初始化; + enabled: true + # 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册 + appName: xtools-cloud + # 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址.从而更灵活支持容器类型执行器动态IP和动态映射端口问题. + address: "" + # 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"; + ip: "" + # 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口; + port: 9999 + # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径; + logPath: /Users/xujun/Downloads/xtools + # 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能; + logRetentionDays: 30 + # 任务扫描排除路径 [选填] :任务扫描时忽略指定包路径下的Bean;支持配置包路径前缀,多个逗号分隔; + excludedPackage: "" \ No newline at end of file diff --git a/xtools-boot-knife4j/pom.xml b/xtools-boot-knife4j/pom.xml new file mode 100644 index 0000000..2399c14 --- /dev/null +++ b/xtools-boot-knife4j/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-knife4j + + + + + + + org.xujun + xtools-boot-core + + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + + \ No newline at end of file diff --git a/xtools-boot-knife4j/src/main/java/com/github/xiaoymin/knife4j/spring/extension/Knife4jOpenApiCustomizer.java b/xtools-boot-knife4j/src/main/java/com/github/xiaoymin/knife4j/spring/extension/Knife4jOpenApiCustomizer.java new file mode 100644 index 0000000..cf21a6d --- /dev/null +++ b/xtools-boot-knife4j/src/main/java/com/github/xiaoymin/knife4j/spring/extension/Knife4jOpenApiCustomizer.java @@ -0,0 +1,144 @@ +package com.github.xiaoymin.knife4j.spring.extension; + +import com.github.xiaoymin.knife4j.annotations.ApiSupport; +import com.github.xiaoymin.knife4j.core.conf.ExtensionsConstants; +import com.github.xiaoymin.knife4j.core.conf.GlobalConstants; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties; +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jSetting; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.models.OpenAPI; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static xtools.base.config.Constants.CP_NUM16; + +/** + *

Title : Knife4jOpenApiCustomizer

+ *

Description : Knife4jOpenApiCustomizer

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 1.0.0 + * @date : 2026/6/12 08:52 + */ +@Slf4j +@AllArgsConstructor +public class Knife4jOpenApiCustomizer implements GlobalOpenApiCustomizer { + + final Knife4jProperties knife4jProperties; + final SpringDocConfigProperties properties; + + @Override + public void customise(OpenAPI openApi) { + log.debug("Knife4j OpenApiCustomizer"); + if (knife4jProperties.isEnable()) { + Knife4jSetting setting = knife4jProperties.getSetting(); + OpenApiExtensionResolver openApiExtensionResolver = new OpenApiExtensionResolver(setting, knife4jProperties.getDocuments()); + // 解析初始化 + openApiExtensionResolver.start(); + Map objectMap = new HashMap<>(CP_NUM16); + objectMap.put(GlobalConstants.EXTENSION_OPEN_SETTING_NAME, setting); + objectMap.put(GlobalConstants.EXTENSION_OPEN_MARKDOWN_NAME, openApiExtensionResolver.getMarkdownFiles()); + openApi.addExtension(GlobalConstants.EXTENSION_OPEN_API_NAME, objectMap); + addOrderExtension(openApi); + } + } + + /** + * 往OpenAPI内tags字段添加x-order属性 + * + * @param openApi openApi + */ + private void addOrderExtension(OpenAPI openApi) { + if (CollectionUtils.isEmpty(properties.getGroupConfigs())) { + return; + } + // 获取包扫描路径 + Set packagesToScan = + properties.getGroupConfigs().stream() + .map(SpringDocConfigProperties.GroupConfig::getPackagesToScan) + .filter(toScan -> !CollectionUtils.isEmpty(toScan)) + .flatMap(List::stream) + .collect(Collectors.toSet()); + if (CollectionUtils.isEmpty(packagesToScan)) { + return; + } + // 扫描包下被ApiSupport注解的RestController Class + Set> classes = + packagesToScan.stream() + .map(this::scanPackageByAnnotation) + .flatMap(Set::stream) + .filter(clazz -> clazz.isAnnotationPresent(ApiSupport.class)) + .collect(Collectors.toSet()); + if (!CollectionUtils.isEmpty(classes)) { + // ApiSupport oder值存入tagSortMap + Map tagOrderMap = new HashMap<>(CP_NUM16); + classes.forEach(clazz -> { + Tag tag = getTag(clazz); + if (Objects.nonNull(tag)) { + ApiSupport apiSupport = clazz.getAnnotation(ApiSupport.class); + tagOrderMap.putIfAbsent(tag.name(), apiSupport.order()); + } + }); + // 往openApi tags字段添加x-order增强属性 + if (openApi.getTags() != null) { + openApi.getTags().forEach(tag -> { + if (tagOrderMap.containsKey(tag.getName())) { + tag.addExtension(ExtensionsConstants.EXTENSION_ORDER, tagOrderMap.get(tag.getName())); + } + }); + } + } + } + + private Tag getTag(Class clazz) { + // 从类上获取 + Tag tag = clazz.getAnnotation(Tag.class); + if (Objects.isNull(tag)) { + // 从接口上获取 + Class[] interfaces = clazz.getInterfaces(); + if (ArrayUtils.isNotEmpty(interfaces)) { + for (Class interfaceClazz : interfaces) { + Tag anno = interfaceClazz.getAnnotation(Tag.class); + if (Objects.nonNull(anno)) { + tag = anno; + break; + } + } + } + } + return tag; + } + + private Set> scanPackageByAnnotation(String packageName) { + ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); + Set> classes = new HashSet<>(); + for (BeanDefinition beanDefinition : scanner.findCandidateComponents(packageName)) { + try { + Class clazz = Class.forName(beanDefinition.getBeanClassName()); + classes.add(clazz); + } catch (ClassNotFoundException ignore) { + } + } + return classes; + } +} diff --git a/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/BootKnife4jConfiguration.java b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/BootKnife4jConfiguration.java new file mode 100644 index 0000000..c58cdeb --- /dev/null +++ b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/BootKnife4jConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.knife4j; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.knife4j.selector.BootKnife4jImportSelector; + +/** + *

Title : BootKnife4jConfiguration

+ *

Description : BootKnife4jConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootKnife4jImportSelector.class) +public class BootKnife4jConfiguration { + + /** + * 构造方法 + */ + public BootKnife4jConfiguration() { + ModuleLoadUtils.loadSuccess(BootKnife4jConfiguration.class); + } + +} diff --git a/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/config/Knife4jFilterWhitelist.java b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/config/Knife4jFilterWhitelist.java new file mode 100644 index 0000000..2a1945e --- /dev/null +++ b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/config/Knife4jFilterWhitelist.java @@ -0,0 +1,35 @@ +package xtools.boot.knife4j.config; + +import org.springframework.stereotype.Component; +import xtools.boot.core.interfaces.FilterWhitelist; + +import java.util.Set; + +/** + *

Title : Knife4jFilterWhitelist

+ *

Description : Knife4jFilterWhitelist

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/8 12:28 + */ +@Component +public class Knife4jFilterWhitelist implements FilterWhitelist { + + /** + * 添加过滤器白名单 + * + * @return 过滤器白名单 + */ + @Override + public Set add() { + return Set.of( + "/doc.html", + "/webjars/**", + "/v3/api-docs/**" + ); + } +} diff --git a/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/selector/BootKnife4jImportSelector.java b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/selector/BootKnife4jImportSelector.java new file mode 100644 index 0000000..eb125f5 --- /dev/null +++ b/xtools-boot-knife4j/src/main/java/xtools/boot/knife4j/selector/BootKnife4jImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.knife4j.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootKnife4jImportSelector

+ *

Description : BootKnife4jImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootKnife4jImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.knife4j"); + } + +} \ No newline at end of file diff --git a/xtools-boot-knife4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-knife4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8eb9826 --- /dev/null +++ b/xtools-boot-knife4j/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.knife4j.BootKnife4jConfiguration \ No newline at end of file diff --git a/xtools-boot-log/pom.xml b/xtools-boot-log/pom.xml new file mode 100644 index 0000000..d32ccc4 --- /dev/null +++ b/xtools-boot-log/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-log + + + + + + + org.xujun + xtools-boot-core + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + org.springframework.boot + spring-boot + + + + \ No newline at end of file diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/BootLogConfiguration.java b/xtools-boot-log/src/main/java/xtools/boot/log/BootLogConfiguration.java new file mode 100644 index 0000000..547ddf8 --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/BootLogConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.log; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.log.selector.BootLogImportSelector; + +/** + *

Title : BootLogConfiguration

+ *

Description : BootLogConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootLogImportSelector.class) +public class BootLogConfiguration { + + /** + * 构造方法 + */ + public BootLogConfiguration() { + ModuleLoadUtils.loadSuccess(BootLogConfiguration.class); + } + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/LogBus.java b/xtools-boot-log/src/main/java/xtools/boot/log/LogBus.java new file mode 100644 index 0000000..13a510f --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/LogBus.java @@ -0,0 +1,395 @@ +package xtools.boot.log; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import lombok.extern.slf4j.Slf4j; +import xtools.base.exception.CommonException; +import xtools.base.exception.ExceptionUtils; +import xtools.boot.api.enums.ThreadType; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.config.LogBusConfig; +import xtools.boot.log.config.RunInfoConfig; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.log.interfaces.LogBusInterface; +import xtools.boot.log.interfaces.LogBusType; +import xtools.boot.log.model.dto.LogBody; +import xtools.boot.log.model.dto.RunInfo; +import xtools.core.ArrUtils; +import xtools.core.CollectionUtils; +import xtools.core.StringUtils; +import xtools.core.enums.LogLevel; +import xtools.core.enums.TimePattern; +import xtools.core.sys.SysBaseInfoUtils; +import xtools.core.time.InstantUtils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static xtools.base.config.Constants.CP_NEGATIVE1; +import static xtools.base.config.Constants.CP_NEWLINE; +import static xtools.base.config.Constants.CP_NUM0; + +/** + *

Title : LogBus

+ *

Description : LogBus

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 09:33 + */ +@Slf4j +public class LogBus { + + /** + * 运行信息 + */ + private static RunInfo runInfo; + + /** + * 日志体 + */ + private final LogBody logBody; + + /** + * 异常信息 + */ + private Throwable throwable; + + /** + * 是否在控制台打印 + */ + private Boolean print; + + /** + * 构造方法 + * + * @param level 日志级别 + * @param type 日志类型 + * @param logTrack 日志信息 + */ + private LogBus(LogLevel level, LogBusType type, LogTrack logTrack) { + // 参数处理 + if (Objects.isNull(level)) { + level = LogLevel.INFO; + } + if (Objects.isNull(logTrack)) { + logTrack = LogTrackHolder.get(); + } + // 构建日志信息 + logBody = new LogBody(); + logBody.setLevel(level); + logBody.setType(type.desc()); + logBody.setLogTrack(logTrack); + logBody.setRunInfo(getRunInfo()); + } + + /** + * 初始化 + * + * @param type 日志类型 + * @return 当前对象 + */ + public static LogBus init(LogBusType type) { + return new LogBus(null, type, null); + } + + /** + * 初始化 + * + * @param level 日志级别 + * @param type 日志类型 + * @return 当前对象 + */ + public static LogBus init(LogLevel level, LogBusType type) { + return new LogBus(level, type, null); + } + + /** + * 初始化 + * + * @param level 日志级别 + * @param type 日志类型 + * @param logTrack 日志信息 + * @return 当前对象 + */ + public static LogBus init(LogLevel level, LogBusType type, LogTrack logTrack) { + return new LogBus(level, type, logTrack); + } + + /** + * 格式化日志 + * + * @param logBody 日志信息 + * @return 日志信息 + */ + private static StringBuilder fmtLog(LogBody logBody) { + // 日志标题 + String title = logBody.getTitle(); + if (StringUtils.isBlank(title)) { + title = "日志信息"; + } + LogTrack logTrack = logBody.getLogTrack(); + // 日志时间 + String time = InstantUtils.format(InstantUtils.from(logTrack.time()), TimePattern.ALL); + // 线程类型 + ThreadType threadType = logTrack.type(); + // 堆栈信息 + JSONArray stackTraceInfo = logBody.getStackTrace(); + // 日志数据 + JSONObject data = logBody.getLogData(); + // 构建打印日志 + StringBuilder sb = new StringBuilder(); + sb.append(CP_NEWLINE); + sb.append("┏━━━ ").append(title).append(" Start ━━━").append(CP_NEWLINE); + sb.append("┠ ").append("日志ID: ").append(logTrack.id()).append(CP_NEWLINE); + sb.append("┠ ").append("日志时间: ").append(time).append(CP_NEWLINE); + sb.append("┠ ").append("追踪ID: ").append(logTrack.traceId()).append(CP_NEWLINE); + sb.append("┠ ").append("线程类型: ").append(threadType.desc()).append(CP_NEWLINE); + if (!ThreadType.MAIN.equals(threadType)) { + sb.append("┠ ").append("父线程ID: ").append(logTrack.parentId()).append(CP_NEWLINE); + } + sb.append("┠ ").append("日志级别: ").append(logBody.getLevel().level()).append(CP_NEWLINE); + sb.append("┠ ").append("日志类型: ").append(logBody.getType()).append(CP_NEWLINE); + sb.append("┠ ").append("运行环境: ").append(CP_NEWLINE).append(JSONObject.toJSONString(logBody.getRunInfo(), JSONWriter.Feature.PrettyFormat)).append(CP_NEWLINE); + if (CollectionUtils.isNotEmpty(stackTraceInfo)) { + sb.append("┠ ").append("堆栈信息: ").append(CP_NEWLINE).append(stackTraceInfo.toJSONString(JSONWriter.Feature.PrettyFormat)).append(CP_NEWLINE); + } + if (CollectionUtils.isNotEmpty(data)) { + sb.append("┠ ").append("日志数据: ").append(CP_NEWLINE).append(data.toJSONString(JSONWriter.Feature.PrettyFormat)).append(CP_NEWLINE); + } + sb.append("┗━━━ ").append(title).append(" End ━━━").append(CP_NEWLINE); + return sb; + } + + /** + * 格式化堆栈信息 + * + * @param stackTraceInfo 堆栈信息 + * @param includeStackTrace 包含的堆栈信息(开始的包名) + * @return 堆栈信息 + */ + private static JSONArray fmtStackTrace(StackTraceElement[] stackTraceInfo, List includeStackTrace) { + // 获取当前线程的调用栈 + if (ArrUtils.isEmpty(stackTraceInfo)) { + return null; + } + JSONArray result = new JSONArray(); + if (CollectionUtils.isEmpty(includeStackTrace)) { + // 获取所有堆栈数据 + Arrays.stream(stackTraceInfo).toList().forEach((item) -> result.add(item.toString())); + return result; + } + + // 获取关于本工程堆栈信息 + for (StackTraceElement item : stackTraceInfo) { + String className = item.getClassName(); + + boolean flag = false; + for (String include : includeStackTrace) { + if (className.startsWith(include)) { + flag = true; + break; + } + } + if (flag) { + result.add(item.toString()); + } + } + return result; + } + + /** + * 日志标题 + * + * @param title 标题 + * @return 当前对象 + */ + public LogBus title(String title) { + if (StringUtils.isNotBlank(title)) { + logBody.setTitle(title); + } + return this; + } + + /** + * 是否打印 + * + * @param print 是否打印 + * @return 当前对象 + */ + public LogBus print(boolean print) { + this.print = print; + return this; + } + + /** + * 设置日志信息 + * + * @param log 日志信息 + * @return 当前对象 + */ + public LogBus data(final Object log) { + if (Objects.isNull(log)) { + return this; + } + if (log instanceof JSONObject jsonLog) { + logBody.setLogData(jsonLog); + } else { + JSONObject logData; + try { + logData = JSONObject.from(log); + } catch (Exception e) { + logData = JSONObject.of("data", log); + } + logBody.setLogData(logData); + } + return this; + } + + /** + * 设置异常信息 + * + * @param throwable 异常信息 + * @return 当前对象 + */ + public LogBus error(Throwable throwable) { + this.throwable = throwable; + return this; + } + + /** + * 保存日志 + */ + public void save() { + // 获取堆栈信息 + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + try { + // 创建子线程,并异步提交 + CompletableFuture.runAsync(() -> { + try { + doSave(logBody, throwable, stackTrace); + } catch (Exception e) { + log.error("保存日志异常", e); + } + }, service); + } finally { + // 关闭线程池 + service.shutdown(); + } + } + + /** + * 获取运行信息 + * + * @return 运行信息 + */ + private RunInfo getRunInfo() { + if (runInfo != null) { + return runInfo; + } + RunInfoConfig sysRunInfo = SpringContextUtils.getBean(RunInfoConfig.class); + RunInfo info = new RunInfo(); + info.setLocalIp(SysBaseInfoUtils.getLocalIp()); + info.setAppName(sysRunInfo.getName()); + info.setPort(sysRunInfo.getPort()); + runInfo = info; + return runInfo; + } + + /** + * 执行日志发送 + * + * @param logBody 日志内容 + * @param throwable 异常 + * @param stackTrace 堆栈信息 + */ + private void doSave(LogBody logBody, Throwable throwable, StackTraceElement[] stackTrace) { + // 获取配置文件 + LogBusConfig config = SpringContextUtils.getBean(LogBusConfig.class); + + // 计算日志级 + boolean geWarn = logBody.getLevel().code() > LogLevel.INFO.code(); + // 处理堆栈信息 + logBody.setStackTrace(fmtStackTrace(stackTrace, geWarn ? null : config.getIncludeStackTrace())); + + // 是否打印日志 + if (Objects.nonNull(print)) { + if (print) { + printLog(logBody); + } + } else { + if (geWarn || config.getPrint()) { + printLog(logBody); + } + } + // 异常日志不做忽略 + if (!geWarn) { + // 不保存日志 + if (!logBody.getLogTrack().save()) { + return; + } + } + // 处理日志内容超长 + JSONObject data = logBody.getLogData(); + if (CollectionUtils.isNotEmpty(data)) { + Integer logDataItemMax = config.getLogDataMaxLength(); + if (!Objects.equals(logDataItemMax, CP_NEGATIVE1)) { + String dataStr = data.toString(); + if (dataStr.length() > logDataItemMax) { + JSONObject newLogData = JSONObject.of( + "msg", "日志内容超长", + "log", dataStr.substring(CP_NUM0, logDataItemMax) + "..." + ); + logBody.setLogData(newLogData); + } + } + } + // 处理异常信息 + if (Objects.nonNull(throwable)) { + if (throwable instanceof CommonException commonException) { + String message = commonException.getMessage(); + Throwable cause = commonException.getCause(); + String errMsg = ExceptionUtils.getErrMsg(Objects.nonNull(cause) ? cause : throwable); + logBody.setErr(message + CP_NEWLINE + errMsg); + } else { + logBody.setErr(ExceptionUtils.getErrMsg(throwable)); + } + } + + // 保存日志信息 + Collection beanList = SpringContextUtils.getBeanList(LogBusInterface.class); + if (CollectionUtils.isNotEmpty(beanList)) { + beanList.forEach(item -> item.save(logBody)); + } + } + + /** + * 打印日志信息 + * + * @param logBody 日志信息 + */ + private void printLog(LogBody logBody) { + // 格式化日志 + StringBuilder logContext = fmtLog(logBody); + // 异常数据 + if (Objects.isNull(throwable)) { + logContext.append(CP_NEWLINE); + log.info(logContext.toString()); + } else { + logContext.append("━━━━ ↓↓↓ exception ↓↓↓ ━━━━").append(CP_NEWLINE); + log.error(logContext.toString(), throwable); + } + } + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/config/LogBusConfig.java b/xtools-boot-log/src/main/java/xtools/boot/log/config/LogBusConfig.java new file mode 100644 index 0000000..77ffb5a --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/config/LogBusConfig.java @@ -0,0 +1,40 @@ +package xtools.boot.log.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Title : LogBusConfig

+ *

Description : LogBusConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 15:07 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "sys.log.bus") +public class LogBusConfig { + + /** + * 是否打印日志 + */ + private Boolean print = false; + + /** + * 包含的堆栈信息(开始的包名) + */ + private List includeStackTrace = new ArrayList<>(); + + /** + * 日志项最大长度 + */ + private Integer logDataMaxLength = -1; +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/config/RunInfoConfig.java b/xtools-boot-log/src/main/java/xtools/boot/log/config/RunInfoConfig.java new file mode 100644 index 0000000..9b0263d --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/config/RunInfoConfig.java @@ -0,0 +1,33 @@ +package xtools.boot.log.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + *

Title : RunInfoConfig

+ *

Description : RunInfoConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 14:17 + */ +@Getter +@Component +public class RunInfoConfig { + + /** + * 应用名称 + */ + @Value("${spring.application.name:xtools-boot}") + private String name; + + /** + * 应用端口 + */ + @Value("${server.port:8080}") + private int port; +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/enums/LogBusBaseType.java b/xtools-boot-log/src/main/java/xtools/boot/log/enums/LogBusBaseType.java new file mode 100644 index 0000000..ca10a1e --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/enums/LogBusBaseType.java @@ -0,0 +1,105 @@ +package xtools.boot.log.enums; + +import xtools.boot.api.enums.BaseEnum; +import xtools.boot.log.interfaces.LogBusType; + +/** + *

Title : LogBusBaseType

+ *

Description : LogBusBaseType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 11:26 + */ +public enum LogBusBaseType implements LogBusType { + + // 其他 + OTHER(0, "其他"), + // THREAD + VIRTUAL_THREAD(1, "虚拟线程"), + // Redis + REDIS(10, "Redis"), + // MyBatis + MYBATIS(20, "MyBatis"), + // MQ + MQ(30, "MQ"), + // Elasticsearch + ELASTICSEARCH(40, "Elasticsearch"), + // Sentinel + SENTINEL(50, "Sentinel"), + // Storage + STORAGE(60, "Storage"), + // 操作日志 + OPT_LOG(70, "OptLog"), + // Http + HTTP(80, "Http工具"), + HTTP_REQUEST(81, "Http请求"), + HTTP_RESPONSE(82, "Http响应"), + // Cloud + CLOUD_REQUEST(85, "Cloud请求"), + CLOUD_RESPONSE(86, "Cloud响应"), + // Controller + CONTROLLER(90, "Controller"), + // Task + TASK(91, "Task"), + // Job + JOB(92, "Job"), + // Risk + RISK(93, "Risk"), + ; + + /** + * 编码 + **/ + private final int code; + + /** + * 说明 + **/ + private final String desc; + + /** + * 初始化方法 + * + * @param code Code + * @param desc 说明 + */ + LogBusBaseType(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/holder/LogTrackHolder.java b/xtools-boot-log/src/main/java/xtools/boot/log/holder/LogTrackHolder.java new file mode 100644 index 0000000..a444c5f --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/holder/LogTrackHolder.java @@ -0,0 +1,202 @@ +package xtools.boot.log.holder; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; +import xtools.boot.api.enums.ThreadType; +import xtools.boot.api.model.dto.log.HolderLogTrack; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.core.UuidUtils; +import xtools.core.encrypt.Base64Utils; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Objects; + +/** + *

Title : LogTrackHolder

+ *

Description : LogTrackHolder

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:36 + */ +public class LogTrackHolder { + + /** + * 日志追踪信息 + */ + private static final ScopedValue LOG_TRACK_INFO = ScopedValue.newInstance(); + + /** + * 获取日志追踪保持信息 + * + * @return 日志追踪保持信息 + */ + public static ScopedValue getScoped() { + return LOG_TRACK_INFO; + } + + + /** + * 新建主线程日志追踪信息 + * + * @return 日志追踪信息 + */ + public static HolderLogTrack newMain() { + ThreadType main = ThreadType.MAIN; + return new HolderLogTrack(UuidUtils.get(), main.name(), main); + } + + /** + * 新建线程日志追踪信息 + * + * @param mainLogTrack 主线程日志追踪信息 + * @return 日志追踪信息 + */ + public static HolderLogTrack newThread(LogTrack mainLogTrack) { + HolderLogTrack holderLogTrack = new HolderLogTrack(mainLogTrack.traceId(), mainLogTrack.id(), ThreadType.THREAD); + holderLogTrack.setSave(mainLogTrack.save()); + return holderLogTrack; + } + + /** + * 新建 Holder 日志追踪信息 + * + * @param logTrack 日志追踪信息 + * @return 日志追踪信息 + */ + public static HolderLogTrack newHolderLogTrack(LogTrack logTrack) { + HolderLogTrack holderLogTrack = new HolderLogTrack(logTrack.traceId(), logTrack.parentId(), logTrack.type(), logTrack.index()); + holderLogTrack.setSave(logTrack.save()); + return holderLogTrack; + } + + /** + * 新建 Holder 日志追踪信息 + * + * @param base64 日志追踪信息 + * @return 日志追踪信息 + */ + public static HolderLogTrack newByBase64(String base64) { + String log = Base64Utils.decodeToStr(base64.getBytes(StandardCharsets.UTF_8)); + LogTrack logTrack = JSONObject.parseObject(log, LogTrack.class); + return newThread(logTrack); + } + + /** + * 获取日志追踪信息 + * + * @return 日志追踪信息 + */ + public static LogTrack get() { + Instant now = Instant.now(); + return packageLogTrack(now, getHolder()); + } + + /** + * 获取日志追踪信息 + * + * @return 日志追踪信息 + */ + public static LogTrack getDefNull() { + Instant now = Instant.now(); + HolderLogTrack holder = getHolderDefNull(); + if (Objects.isNull(holder)) { + return null; + } + return packageLogTrack(now, holder); + } + + /** + * 获取新建Main日志追踪信息 + * + * @return 新建Main日志追踪信息 + */ + public static LogTrack getNewMain() { + Instant now = Instant.now(); + return packageLogTrack(now, newMain()); + } + + /** + * 包装日志追踪信息 + * + * @param now 时间 + * @param holder 日志追踪信息 + * @return 日志追踪信息 + */ + private static LogTrack packageLogTrack(Instant now, HolderLogTrack holder) { + holder.getCount().getAndIncrement(); + return new LogTrack( + UuidUtils.get(), + now.toEpochMilli(), + holder.getTraceId(), + holder.getParentId(), + holder.getType(), + holder.getCount().get(), + holder.isSave() + ); + } + + /** + * 获取日志追踪信息 + * + * @return 日志追踪信息 + */ + public static String getBase64() { + LogTrack logTrack = getDefNull(); + return getBase64(logTrack); + } + + /** + * 获取日志追踪信息 + * + * @param logTrack 日志追踪信息 + * @return 日志追踪信息 + */ + public static String getBase64(LogTrack logTrack) { + if (Objects.isNull(logTrack)) { + return null; + } + String logTxt = JSONObject.toJSONString(logTrack, JSONWriter.Feature.IgnoreEmpty); + return Base64Utils.encodeToStr(logTxt.getBytes(StandardCharsets.UTF_8)); + } + + /** + * 是否保存日志 + * + * @param save true 保存日志, false 不保存日志 + */ + public static void saveLog(boolean save) { + getHolder().setSave(save); + } + + /** + * 获取 Holder 日志追踪信息 + * + * @return Holder 日志追踪信息 + */ + private static HolderLogTrack getHolder() { + if (!LOG_TRACK_INFO.isBound()) { + throw CommonException.create(BootError.LOG_HOLDER); + } + return LOG_TRACK_INFO.get(); + } + + /** + * 获取 Holder 日志追踪信息 + * + * @return Holder 日志追踪信息 + */ + private static HolderLogTrack getHolderDefNull() { + if (!LOG_TRACK_INFO.isBound()) { + return null; + } + return LOG_TRACK_INFO.get(); + } + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusInterface.java b/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusInterface.java new file mode 100644 index 0000000..8d4adb7 --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusInterface.java @@ -0,0 +1,25 @@ +package xtools.boot.log.interfaces; + +import xtools.boot.log.model.dto.LogBody; + +/** + *

Title : LogBusInterface

+ *

Description : LogBusInterface

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 16:56 + */ +public interface LogBusInterface { + + /** + * 保存日志 + * + * @param logBody 日志 + */ + void save(LogBody logBody); + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusType.java b/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusType.java new file mode 100644 index 0000000..dfe6c05 --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/interfaces/LogBusType.java @@ -0,0 +1,23 @@ +package xtools.boot.log.interfaces; + +import xtools.boot.api.enums.BaseEnum; + +/** + *

Title : LogBusType

+ *

Description : LogBusType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 10:31 + */ +public interface LogBusType extends BaseEnum { + + /** + * 基础编码 + */ + int BASE_CODE = 100; + +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/LogBody.java b/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/LogBody.java new file mode 100644 index 0000000..46ff08c --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/LogBody.java @@ -0,0 +1,64 @@ +package xtools.boot.log.model.dto; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.core.enums.LogLevel; + +import java.io.Serializable; + +/** + *

Title : LogBody

+ *

Description : LogBody

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 09:37 + */ +@Data +public class LogBody implements Serializable { + + /** + * 日志主题 + */ + private String title; + + /** + * 日志级别 + */ + private LogLevel level; + + /** + * 日志类型 + */ + private String type; + + /** + * 日志链信息 + */ + private LogTrack logTrack; + + /** + * 运行环境信息 + */ + private RunInfo runInfo; + + /** + * 堆栈信息 + */ + private JSONArray stackTrace; + + /** + * 日志数据 + */ + private JSONObject logData; + + /** + * 异常信息(文本) + */ + private String err; +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/RunInfo.java b/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/RunInfo.java new file mode 100644 index 0000000..588bc13 --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/model/dto/RunInfo.java @@ -0,0 +1,35 @@ +package xtools.boot.log.model.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + *

Title : RunEvInfo

+ *

Description : RunEvInfo

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 09:38 + */ +@Data +public class RunInfo implements Serializable { + + /** + * 服务名称 + */ + private String appName; + + /** + * 本机IP + */ + private String localIp; + + /** + * 运行端口 + */ + private int port; +} diff --git a/xtools-boot-log/src/main/java/xtools/boot/log/selector/BootLogImportSelector.java b/xtools-boot-log/src/main/java/xtools/boot/log/selector/BootLogImportSelector.java new file mode 100644 index 0000000..8bdcad7 --- /dev/null +++ b/xtools-boot-log/src/main/java/xtools/boot/log/selector/BootLogImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.log.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootLogImportSelector

+ *

Description : BootLogImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootLogImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.log"); + } + +} \ No newline at end of file diff --git a/xtools-boot-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..04ed11f --- /dev/null +++ b/xtools-boot-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.log.BootLogConfiguration \ No newline at end of file diff --git a/xtools-boot-mask/pom.xml b/xtools-boot-mask/pom.xml new file mode 100644 index 0000000..1c3f504 --- /dev/null +++ b/xtools-boot-mask/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-mask + + + + + + + org.xujun + xtools-boot-core + + + + + + tools.jackson.core + jackson-databind + + + + \ No newline at end of file diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/BootMaskConfiguration.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/BootMaskConfiguration.java new file mode 100644 index 0000000..0c4f888 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/BootMaskConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.mask; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.mask.selector.BootMaskImportSelector; + +/** + *

Title : BootMaskConfiguration

+ *

Description : BootMaskConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootMaskImportSelector.class) +public class BootMaskConfiguration { + + /** + * 构造方法 + */ + public BootMaskConfiguration() { + ModuleLoadUtils.loadSuccess(BootMaskConfiguration.class); + } + +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/MaskCustom.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/MaskCustom.java new file mode 100644 index 0000000..7e172b5 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/MaskCustom.java @@ -0,0 +1,27 @@ +package xtools.boot.mask; + +import xtools.boot.mask.anntation.Mask; + +/** + *

Title : MaskCustom

+ *

Description : MaskCustom

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/9 10:08 + */ +public interface MaskCustom { + + /** + * 脱敏处理 + * + * @param value 待处理的字符串 + * @param mask 脱敏注解 + * @return 脱敏后的字符串 + */ + String mask(String value, Mask mask); + +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/anntation/Mask.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/anntation/Mask.java new file mode 100644 index 0000000..f32646f --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/anntation/Mask.java @@ -0,0 +1,74 @@ +package xtools.boot.mask.anntation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import tools.jackson.databind.annotation.JsonSerialize; +import xtools.boot.mask.MaskCustom; +import xtools.boot.mask.enums.MaskType; +import xtools.boot.mask.handle.DefaultMaskHandle; +import xtools.boot.mask.serializer.MaskSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Title : Mask

+ *

Description : Mask

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/9 10:07 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = MaskSerializer.class) +public @interface Mask { + + /** + * 脱敏类型 + * + * @return 脱敏类型 + */ + MaskType value(); + + /** + * 是否总是脱敏(无法忽略脱敏) + * + * @return 是否总是脱敏 + */ + boolean always() default false; + + /** + * 前缀不脱敏长度 + * + * @return 前缀不脱敏长度 + */ + int prefixNoMaskLen() default 0; + + /** + * 后缀不脱敏长度 + * + * @return 后缀不脱敏长度 + */ + int suffixNoMaskLen() default 0; + + /** + * 脱敏替代字符 + * + * @return 脱敏替代字符 + */ + String maskChar() default "*"; + + /** + * 自定义脱敏接口 + * + * @return 自定义脱敏接口 + */ + Class custom() default DefaultMaskHandle.class; + +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/enums/MaskType.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/enums/MaskType.java new file mode 100644 index 0000000..8d4bda4 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/enums/MaskType.java @@ -0,0 +1,33 @@ +package xtools.boot.mask.enums; + +/** + *

Title : MaskType

+ *

Description : MaskType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/9 10:07 + */ +public enum MaskType { + + // 中文名 + CHINESE_NAME, + // 身份证号 + ID_CARD, + // 手机号 + MOBILE_PHONE, + // 邮箱 + EMAIL, + // 银行卡 + BANK_CARD, + // 密码 + PASSWORD, + // 地址 + ADDRESS, + // 自定义 + CUSTOM + +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/handle/DefaultMaskHandle.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/handle/DefaultMaskHandle.java new file mode 100644 index 0000000..945f9a9 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/handle/DefaultMaskHandle.java @@ -0,0 +1,43 @@ +package xtools.boot.mask.handle; + +import xtools.boot.mask.MaskCustom; +import xtools.boot.mask.anntation.Mask; + +/** + *

Title : DefaultMaskHandle

+ *

Description : DefaultMaskHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/4 15:19 + */ +public class DefaultMaskHandle implements MaskCustom { + + /** + * 脱敏处理 + * + * @param value 待处理的字符串 + * @param mask 脱敏注解 + * @return 脱敏后的字符串 + */ + @Override + public String mask(String value, Mask mask) { + final int length = value.length(); + int prefixNoMaskLen = mask.prefixNoMaskLen(); + int suffixNoMaskLen = mask.suffixNoMaskLen(); + if (prefixNoMaskLen > 0 && length <= prefixNoMaskLen) { + return value; + } + if (suffixNoMaskLen > 0 && length <= suffixNoMaskLen) { + return value; + } + int maskLength = length - prefixNoMaskLen - suffixNoMaskLen; + if (maskLength <= 0) { + return value; + } + return value.substring(0, prefixNoMaskLen) + mask.maskChar().repeat(maskLength) + value.substring(length - suffixNoMaskLen); + } +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/selector/BootMaskImportSelector.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/selector/BootMaskImportSelector.java new file mode 100644 index 0000000..c699546 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/selector/BootMaskImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.mask.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootMaskImportSelector

+ *

Description : BootMaskImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootMaskImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.mask"); + } + +} \ No newline at end of file diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/serializer/MaskSerializer.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/serializer/MaskSerializer.java new file mode 100644 index 0000000..e00d256 --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/serializer/MaskSerializer.java @@ -0,0 +1,110 @@ +package xtools.boot.mask.serializer; + +import lombok.extern.slf4j.Slf4j; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.TokenStreamContext; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import xtools.boot.mask.MaskCustom; +import xtools.boot.mask.anntation.Mask; +import xtools.boot.mask.utils.MaskIgnoreUtils; +import xtools.core.StringUtils; +import xtools.core.extend.MaskUtils; + +import java.lang.reflect.Field; +import java.util.Objects; + +/** + *

Title : MaskSerializer

+ *

Description : MaskSerializer

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/9 10:08 + */ +@Slf4j +public class MaskSerializer extends ValueSerializer { + + /** + * 序列化字符串 + * + * @param value 待处理的字符串 + * @param gen JsonGenerator + * @param context SerializationContext + * @throws JacksonException JacksonException + */ + @Override + public void serialize(String value, JsonGenerator gen, SerializationContext context) throws JacksonException { + if (StringUtils.isBlank(value)) { + gen.writeString(value); + return; + } + value = value.trim(); + TokenStreamContext streamContext = gen.streamWriteContext(); + if (Objects.isNull(streamContext)) { + gen.writeString(value); + return; + } + try { + Field field = streamContext.currentValue().getClass().getDeclaredField(streamContext.currentName()); + // 获取注解 + Mask mask = field.getAnnotation(Mask.class); + if (mask == null) { + gen.writeString(value); + return; + } + // 判断是否总是脱敏 + if (!mask.always()) { + // 判断是否忽略脱敏 + if (MaskIgnoreUtils.isIgnore()) { + gen.writeString(value); + return; + } + } + gen.writeString(mask(value, mask)); + } catch (Exception e) { + log.error("脱敏处理异常", e); + gen.writeString(value); + } + } + + /** + * 脱敏处理 + * + * @param value 待处理的字符串 + * @param mask 脱敏注解 + * @return 脱敏后的字符串 + * @throws Exception 异常 + */ + private String mask(String value, Mask mask) throws Exception { + String maskString = mask.maskChar(); + return switch (mask.value()) { + case PASSWORD -> MaskUtils.password(value, maskString); + case MOBILE_PHONE -> MaskUtils.mobilePhone(value, maskString); + case EMAIL -> MaskUtils.email(value, maskString); + case CHINESE_NAME -> MaskUtils.chineseName(value, maskString); + case ADDRESS -> MaskUtils.address(value, maskString); + case ID_CARD -> MaskUtils.idCard(value, maskString); + case BANK_CARD -> MaskUtils.bankCard(value, maskString); + case CUSTOM -> customMask(value, mask); + }; + } + + /** + * 自定义脱敏处理 + * + * @param value 待处理的字符串 + * @param mask 脱敏注解 + * @return 脱敏后的字符串 + * @throws Exception 异常 + */ + private String customMask(String value, Mask mask) throws Exception { + Class custom = mask.custom(); + return custom.getDeclaredConstructor().newInstance().mask(value, mask); + } + +} diff --git a/xtools-boot-mask/src/main/java/xtools/boot/mask/utils/MaskIgnoreUtils.java b/xtools-boot-mask/src/main/java/xtools/boot/mask/utils/MaskIgnoreUtils.java new file mode 100644 index 0000000..4a1fa3e --- /dev/null +++ b/xtools-boot-mask/src/main/java/xtools/boot/mask/utils/MaskIgnoreUtils.java @@ -0,0 +1,39 @@ +package xtools.boot.mask.utils; + +import xtools.boot.core.holder.CommonHolder; + +/** + *

Title : MaskUtils

+ *

Description : MaskUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/9 16:38 + */ +public class MaskIgnoreUtils { + + /** + * 忽略脱敏 + */ + private static final String IGNORE = "mask-ignore"; + + /** + * 忽略脱敏 + */ + public static void ignore() { + CommonHolder.set(IGNORE, true); + } + + /** + * 是否忽略脱敏 + * + * @return true 忽略, false 不忽略 + */ + public static boolean isIgnore() { + return CommonHolder.getBoolean(IGNORE); + } + +} diff --git a/xtools-boot-mask/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-mask/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..63891a7 --- /dev/null +++ b/xtools-boot-mask/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.mask.BootMaskConfiguration \ No newline at end of file diff --git a/xtools-boot-mq/pom.xml b/xtools-boot-mq/pom.xml new file mode 100644 index 0000000..07aad12 --- /dev/null +++ b/xtools-boot-mq/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-mq + + + + xtools-boot-mq-base + xtools-boot-mq-kafka + xtools-boot-mq-rabbit + + + \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-base/pom.xml b/xtools-boot-mq/xtools-boot-mq-base/pom.xml new file mode 100644 index 0000000..2e5aae8 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + org.xujun + xtools-boot-mq + 5.1.0 + + xtools-boot-mq-base + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-log + + + + org.xujun + xtools-boot-thread + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/BootMqBaseConfiguration.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/BootMqBaseConfiguration.java new file mode 100644 index 0000000..c1d13e0 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/BootMqBaseConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.mq.base; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.mq.base.selector.BootMqBaseImportSelector; + +/** + *

Title : BootMqBaseConfiguration

+ *

Description : BootMqBaseConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootMqBaseImportSelector.class) +public class BootMqBaseConfiguration { + + /** + * 构造方法 + */ + public BootMqBaseConfiguration() { + ModuleLoadUtils.loadSuccess(BootMqBaseConfiguration.class); + } + +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/enums/MqEnums.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/enums/MqEnums.java new file mode 100644 index 0000000..58b4bb0 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/enums/MqEnums.java @@ -0,0 +1,30 @@ +package xtools.boot.mq.base.enums; + +/** + *

Title : MqEnums

+ *

Description : MqEnums

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/5 16:46 + */ +public interface MqEnums { + + /** + * 获取队列名称 + * + * @return 队列名称 + */ + String queue(); + + /** + * 是否保存日志 + * + * @return 是否保存日志 + */ + boolean saveLog(); + +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseErrorHandle.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseErrorHandle.java new file mode 100644 index 0000000..713a7c5 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseErrorHandle.java @@ -0,0 +1,24 @@ +package xtools.boot.mq.base.handle; + +/** + *

Title : BaseErrorHandle

+ *

Description : BaseErrorHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/12 10:46 + */ +public interface BaseErrorHandle { + + /** + * 消息处理错误异常(需要消息队列重推消息,抛出异常即可) + * + * @param message 消息内容 + * @param exception 异常信息 + */ + void messageHandle(String message, Exception exception); + +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMessageHandle.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMessageHandle.java new file mode 100644 index 0000000..e534504 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMessageHandle.java @@ -0,0 +1,133 @@ +package xtools.boot.mq.base.handle; + +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.mq.base.enums.MqEnums; +import xtools.boot.mq.base.model.dto.MessageDto; +import xtools.boot.mq.base.utils.MqMessageUtils; +import xtools.core.enums.LogLevel; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + *

Title : BaseMessageHandle

+ *

Description : BaseMessageHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/11 17:03 + */ +@Slf4j +public abstract class BaseMessageHandle { + + /** + * 消息泛型类 + */ + private final Class clazz; + + /** + * 构造方法 + * + * @param clazz 泛型类 + */ + public BaseMessageHandle(Class clazz) { + this.clazz = clazz; + } + + /** + * 获取队列 + * + * @return 队列 + */ + public abstract MqEnums queue(); + + /** + * 获取监听方法 + * + * @return 监听方法 + */ + public String listenerMethod() { + return null; + } + + /** + * 获取参数 + * + * @return 参数 + */ + public Object params() { + return null; + } + + /** + * 自定义处理消息 + * + * @param message 消息内容 + * @throws Exception 异常信息 + */ + public abstract void handleMessage(T message) throws Exception; + + /** + * 基础消息处理 + * + * @param message 消息内容 + */ + public void baseHandleMessage(String message) { + // 解包消息 + MessageDto msg = MqMessageUtils.from(message, clazz); + final T data = msg.getData(); + if (Objects.isNull(data)) { + log.warn("消息内容为空"); + return; + } + AtomicReference err = new AtomicReference<>(); + LogTrack logTrack = msg.getLog(); + if (Objects.isNull(logTrack)) { + try { + handleMessage(data); + } catch (Exception e) { + err.set(e); + } + } else { + ScopedValue.where(LogTrackHolder.getScoped(), LogTrackHolder.newHolderLogTrack(logTrack)).run(() -> { + long startTime = System.currentTimeMillis(); + try { + if (logTrack.save()) { + LogBus.init(LogBusBaseType.MQ).title("MQ接收").data(msg).save(); + } + handleMessage(data); + } catch (Exception e) { + err.set(e); + } finally { + if (logTrack.save()) { + long endTime = System.currentTimeMillis(); + LogLevel level = Objects.isNull(err.get()) ? LogLevel.INFO : LogLevel.ERROR; + LogBus.init(level, LogBusBaseType.MQ) + .title("MQ处理完成") + .error(err.get()) + .data(JSONObject.of("execTime", endTime - startTime)) + .save(); + } + } + }); + } + Exception exception = err.get(); + if (Objects.isNull(exception)) { + return; + } + log.error("消息处理异常", exception); + BaseErrorHandle errorHandle = SpringContextUtils.getBeanDefNull(BaseErrorHandle.class); + if (Objects.nonNull(errorHandle)) { + errorHandle.messageHandle(message, exception); + } + } +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMqHandle.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMqHandle.java new file mode 100644 index 0000000..06ff94d --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/handle/BaseMqHandle.java @@ -0,0 +1,41 @@ +package xtools.boot.mq.base.handle; + +/** + *

Title : BaseMqHandle

+ *

Description : BaseMqHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/12 09:23 + */ +public interface BaseMqHandle { + + /** + * 初始化消息队列 + * + * @param queueName 消息队列名称 + */ + void initQueue(String queueName); + + /** + * 添加消息队列监听 + * + * @param queueName 消息队列 + * @param delegate 监听类 + * @param listenerMethod 监听方法 + * @param params 参数 + */ + void addListen(String queueName, Object delegate, String listenerMethod, Object params); + + /** + * 推送消息 + * + * @param routingKey 路由 key + * @param data 消息数据 + */ + void push(String routingKey, Object data); + +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/init/InitMq.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/init/InitMq.java new file mode 100644 index 0000000..bd9e86c --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/init/InitMq.java @@ -0,0 +1,80 @@ +package xtools.boot.mq.base.init; + +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import xtools.base.config.Constants; +import xtools.base.exception.CommonException; +import xtools.boot.api.enums.BootError; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.mq.base.handle.BaseMessageHandle; +import xtools.boot.mq.base.handle.BaseMqHandle; +import xtools.core.CollectionUtils; +import xtools.core.enums.LogLevel; + +import java.util.Collection; +import java.util.Objects; + + +/** + *

Title : InitMq

+ *

Description : InitMq

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/2 14:13 + */ +@Slf4j +@Component +@Order(Constants.CP_NUM50) +public class InitMq implements ApplicationRunner { + + @Override + public void run(@NonNull ApplicationArguments args) { + ScopedValue.where(LogTrackHolder.getScoped(), LogTrackHolder.newMain()).run(() -> { + try { + init(); + } catch (Exception e) { + LogBus.init(LogLevel.ERROR, LogBusBaseType.MQ).title("初始化异常").error(e).save(); + } + }); + + } + + /** + * 初始化 + */ + @SuppressWarnings("rawtypes") + private void init() { + BaseMqHandle mqHandle = SpringContextUtils.getBeanDefNull(BaseMqHandle.class); + if (Objects.isNull(mqHandle)) { + throw CommonException.create(BootError.MQ, "没有找到MQ处理适配器,请添加对应Jar包"); + } + + Collection beanList = SpringContextUtils.getBeanList(BaseMessageHandle.class); + if (CollectionUtils.isEmpty(beanList)) { + log.info("没有找到MQ的消息处理适配器"); + return; + } + beanList.forEach(item -> { + // 获取队列名称 + String queueName = item.queue().queue(); + try { + mqHandle.initQueue(queueName); + mqHandle.addListen(queueName, item, item.listenerMethod(), item.params()); + log.info("消息队列监听[{}]初始化成功", queueName); + } catch (Exception e) { + LogBus.init(LogLevel.ERROR, LogBusBaseType.MQ).title("初始化" + queueName + "异常").error(e).save(); + } + }); + } +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/model/dto/MessageDto.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/model/dto/MessageDto.java new file mode 100644 index 0000000..7ca688f --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/model/dto/MessageDto.java @@ -0,0 +1,31 @@ +package xtools.boot.mq.base.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import xtools.boot.api.model.dto.log.LogTrack; + +/** + *

Title : MessageDto

+ *

Description : MessageDto

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/11 20:06 + */ +@Data +@AllArgsConstructor +public class MessageDto { + + /** + * 日志追踪信息 + */ + private LogTrack log; + + /** + * 消息内容 + */ + private T data; +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/selector/BootMqBaseImportSelector.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/selector/BootMqBaseImportSelector.java new file mode 100644 index 0000000..60b3b73 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/selector/BootMqBaseImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.mq.base.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootMqBaseImportSelector

+ *

Description : BootMqBaseImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootMqBaseImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.mq.base"); + } + +} \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqBus.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqBus.java new file mode 100644 index 0000000..0723fa2 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqBus.java @@ -0,0 +1,64 @@ +package xtools.boot.mq.base.utils; + +import com.alibaba.fastjson2.JSONObject; +import xtools.boot.api.exection.BizError; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.mq.base.enums.MqEnums; +import xtools.boot.mq.base.handle.BaseMqHandle; +import xtools.boot.thread.utils.VirtualThreadTaskUtils; +import xtools.core.enums.LogLevel; + +import java.util.Objects; + +/** + *

Title : MqBus

+ *

Description : MqBus

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/12 10:00 + */ +public class MqBus { + + /** + * 推送到消息队列 + * + * @param mq 队列 + * @param data 数据 + */ + public static void push(MqEnums mq, Object data) { + BaseMqHandle mqHandle = SpringContextUtils.getBeanDefNull(BaseMqHandle.class); + if (Objects.isNull(mqHandle)) { + throw new BizError("没有找到MQ处理适配器,请添加对应Jar包"); + } + // 是否保存日志 + boolean saveLog = mq.saveLog(); + LogTrack logTrack; + if (saveLog) { + logTrack = LogTrackHolder.getDefNull(); + if (Objects.isNull(logTrack)) { + saveLog = false; + } else { + saveLog = logTrack.save(); + } + } + if (saveLog) { + VirtualThreadTaskUtils.execute(() -> { + LogTrack track = LogTrackHolder.get(); + JSONObject message = MqMessageUtils.to(data, track); + LogBus.init(LogLevel.INFO, LogBusBaseType.MQ, track).title("MQ推送").data(message).save(); + mqHandle.push(mq.queue(), message.toString()); + }); + } else { + VirtualThreadTaskUtils.simple(() -> mqHandle.push(mq.queue(), MqMessageUtils.to(data, null).toString())); + } + } + +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqMessageUtils.java b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqMessageUtils.java new file mode 100644 index 0000000..3ae3e40 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/java/xtools/boot/mq/base/utils/MqMessageUtils.java @@ -0,0 +1,53 @@ +package xtools.boot.mq.base.utils; + +import com.alibaba.fastjson2.JSONObject; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.mq.base.model.dto.MessageDto; + +import java.io.Serializable; + +/** + *

Title : MqMessageUtils

+ *

Description : MqMessageUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/11 19:56 + */ +public class MqMessageUtils implements Serializable { + + + /** + * 消息包数据 Key + */ + private static final String LOG = "log"; + private static final String DATA = "data"; + + /** + * 构建消息包 + * + * @param data 消息内容 + * @param logTrack 日志追踪信息 + * @return java.lang.String + **/ + public static JSONObject to(Object data, LogTrack logTrack) { + return JSONObject.of(LOG, logTrack, DATA, data); + } + + /** + * 解析消息包 + * + * @param message 消息包 + * @param clazz 消息内容类型 + * @return MessageDto + **/ + public static MessageDto from(String message, Class clazz) { + JSONObject msg = JSONObject.parseObject(message); + LogTrack log = msg.getObject(LOG, LogTrack.class); + T data = msg.getObject(DATA, clazz); + return new MessageDto<>(log, data); + } +} diff --git a/xtools-boot-mq/xtools-boot-mq-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-mq/xtools-boot-mq-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..73b1ae0 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.mq.base.BootMqBaseConfiguration \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/pom.xml b/xtools-boot-mq/xtools-boot-mq-kafka/pom.xml new file mode 100644 index 0000000..60de28c --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.xujun + xtools-boot-mq + 5.1.0 + + xtools-boot-mq-kafka + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-mq-base + + + + + + org.springframework.boot + spring-boot-starter-kafka + + + + \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/BootKafkaConfiguration.java b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/BootKafkaConfiguration.java new file mode 100644 index 0000000..34fd581 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/BootKafkaConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.mq.kafka; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.mq.kafka.selector.BootKafkaImportSelector; + +/** + *

Title : BootKafkaConfiguration

+ *

Description : BootKafkaConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootKafkaImportSelector.class) +public class BootKafkaConfiguration { + + /** + * 构造方法 + */ + public BootKafkaConfiguration() { + ModuleLoadUtils.loadSuccess(BootKafkaConfiguration.class); + } + +} diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/config/BootKafkaConfig.java b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/config/BootKafkaConfig.java new file mode 100644 index 0000000..fcc4b91 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/config/BootKafkaConfig.java @@ -0,0 +1,38 @@ +package xtools.boot.mq.kafka.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + *

Title : BootKafkaConfig

+ *

Description : BootKafkaConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 1.0.0 + * @date : 2026/7/10 16:01 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "xtools.kafka") +public class BootKafkaConfig { + + /** + * 分区数 + */ + private int partitions = 1; + + /** + * 副本数 + */ + private int replicaCount = 1; + + /** + * 消息保留时长 + */ + private String retentionMs = "604800000"; + +} diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/handle/KafkaHandle.java b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/handle/KafkaHandle.java new file mode 100644 index 0000000..c92e2c8 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/handle/KafkaHandle.java @@ -0,0 +1,107 @@ +package xtools.boot.mq.kafka.handle; + +import lombok.RequiredArgsConstructor; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.common.config.TopicConfig; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.config.TopicBuilder; +import org.springframework.kafka.core.KafkaAdmin; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.MessageListener; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import xtools.boot.api.exection.BizError; +import xtools.boot.mq.base.handle.BaseMqHandle; +import xtools.boot.mq.kafka.config.BootKafkaConfig; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + *

Title : KafkaHandle

+ *

Description : KafkaHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 1.0.0 + * @date : 2026/7/10 14:56 + */ +@Component +@RequiredArgsConstructor +public class KafkaHandle implements BaseMqHandle { + + /** + * 默认监听方法 + */ + private final static String DEFAULT_LISTENER_METHOD = "baseHandleMessage"; + + private final BootKafkaConfig bootKafkaConfig; + + private final ConcurrentKafkaListenerContainerFactory containerFactory; + + private final KafkaAdmin kafkaAdmin; + + private final KafkaTemplate kafkaTemplate; + + /** + * 初始化消息队列 + * + * @param queueName 消息队列名称 + */ + @Override + public void initQueue(String queueName) { + NewTopic topic = TopicBuilder + // topic名称 + .name(queueName) + // 分区数 + .partitions(bootKafkaConfig.getPartitions()) + // 副本数 + .replicas(bootKafkaConfig.getReplicaCount()) + // 消息保留时长 + .config(TopicConfig.RETENTION_MS_CONFIG, bootKafkaConfig.getRetentionMs()) + .build(); + kafkaAdmin.createOrModifyTopics(topic); + } + + /** + * 添加消息队列监听 + * + * @param queueName 消息队列 + * @param delegate 监听类 + * @param listenerMethod 监听方法 + * @param params 参数 + */ + @Override + public void addListen(String queueName, Object delegate, String listenerMethod, Object params) { + if (Objects.isNull(listenerMethod)) { + listenerMethod = DEFAULT_LISTENER_METHOD; + } + // 获取方法 + Method method = ReflectionUtils.findMethod(delegate.getClass(), listenerMethod, String.class); + if (Objects.isNull(method)) { + throw new BizError("在" + delegate.getClass().getName() + "上找不到[" + listenerMethod + "]的方法"); + } + ConcurrentMessageListenerContainer container = containerFactory.createContainer(queueName); + container.getContainerProperties().setMessageListener((MessageListener) record -> { + Object value = record.value(); + String message = value instanceof byte[] bytes ? new String(bytes, StandardCharsets.UTF_8) : String.valueOf(value); + ReflectionUtils.invokeMethod(method, delegate, message); + }); + container.start(); + } + + /** + * 推送消息 + * + * @param routingKey 路由 key + * @param data 消息数据 + */ + @Override + public void push(String routingKey, Object data) { + kafkaTemplate.send(routingKey, data); + } +} diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/selector/BootKafkaImportSelector.java b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/selector/BootKafkaImportSelector.java new file mode 100644 index 0000000..77aa2c1 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/java/xtools/boot/mq/kafka/selector/BootKafkaImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.mq.kafka.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootKafkaImportSelector

+ *

Description : BootKafkaImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootKafkaImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.mq.kafka"); + } + +} \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..c30598b --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.mq.kafka.BootKafkaConfiguration \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/pom.xml b/xtools-boot-mq/xtools-boot-mq-rabbit/pom.xml new file mode 100644 index 0000000..ae1859b --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.xujun + xtools-boot-mq + 5.1.0 + + xtools-boot-mq-rabbit + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-mq-base + + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/BootRabbitMqConfiguration.java b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/BootRabbitMqConfiguration.java new file mode 100644 index 0000000..df45a8b --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/BootRabbitMqConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.mq.rabbit; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.mq.rabbit.selector.BootRabbitMqImportSelector; + +/** + *

Title : BootRabbitMqConfiguration

+ *

Description : BootRabbitMqConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootRabbitMqImportSelector.class) +public class BootRabbitMqConfiguration { + + /** + * 构造方法 + */ + public BootRabbitMqConfiguration() { + ModuleLoadUtils.loadSuccess(BootRabbitMqConfiguration.class); + } + +} diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/handle/RabbitMqHandle.java b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/handle/RabbitMqHandle.java new file mode 100644 index 0000000..5f99f22 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/handle/RabbitMqHandle.java @@ -0,0 +1,89 @@ +package xtools.boot.mq.rabbit.handle; + +import lombok.RequiredArgsConstructor; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.stereotype.Component; +import xtools.boot.mq.base.handle.BaseMqHandle; +import xtools.boot.mq.rabbit.params.RabbitMqParams; +import xtools.core.StringUtils; + +/** + *

Title : RabbitMqHandle

+ *

Description : RabbitMqHandle

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/4 14:14 + */ +@Component +@RequiredArgsConstructor +public class RabbitMqHandle implements BaseMqHandle { + + /** + * 默认监听方法 + */ + private final static String DEFAULT_LISTENER_METHOD = "baseHandleMessage"; + + private final ConnectionFactory connectionFactory; + + private final RabbitTemplate rabbitTemplate; + + /** + * 初始化消息队列 + * + * @param queueName 消息队列名称 + */ + @Override + public void initQueue(String queueName) { + Queue queue = new Queue(queueName, true); + new RabbitAdmin(connectionFactory).declareQueue(queue); + } + + /** + * 添加消息队列监听 + * + * @param queueName 消息队列 + * @param delegate 监听类 + * @param listenerMethod 监听方法 + * @param params 参数 + */ + @Override + public void addListen(String queueName, Object delegate, String listenerMethod, Object params) { + if (StringUtils.isBlank(listenerMethod)) { + listenerMethod = DEFAULT_LISTENER_METHOD; + } + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.setQueueNames(queueName); + // 设置参数 + if (params instanceof RabbitMqParams rabbitMqParams) { + // 并发参数 + String concurrency = rabbitMqParams.getConcurrency(); + if (StringUtils.isNotBlank(concurrency)) { + container.setConcurrency(concurrency); + } + } + container.setMessageListener(new MessageListenerAdapter(delegate, listenerMethod)); + container.start(); + } + + /** + * 推送消息 + * + * @param routingKey 路由 key + * @param data 消息数据 + */ + @Override + public void push(String routingKey, Object data) { + rabbitTemplate.convertAndSend(routingKey, data); + } + +} diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/params/RabbitMqParams.java b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/params/RabbitMqParams.java new file mode 100644 index 0000000..29e71cc --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/params/RabbitMqParams.java @@ -0,0 +1,30 @@ +package xtools.boot.mq.rabbit.params; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

Title : RabbitMqParam

+ *

Description : RabbitMqParam

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/12 08:05 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RabbitMqParams implements Serializable { + + /** + * 并发 + */ + private String concurrency; + +} diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/selector/BootRabbitMqImportSelector.java b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/selector/BootRabbitMqImportSelector.java new file mode 100644 index 0000000..97f5b3c --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/java/xtools/boot/mq/rabbit/selector/BootRabbitMqImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.mq.rabbit.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootRabbitMqImportSelector

+ *

Description : BootRabbitMqImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootRabbitMqImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.mq.rabbit"); + } + +} \ No newline at end of file diff --git a/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..b5073d4 --- /dev/null +++ b/xtools-boot-mq/xtools-boot-mq-rabbit/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.mq.rabbit.BootRabbitMqConfiguration \ No newline at end of file diff --git a/xtools-boot-storage/pom.xml b/xtools-boot-storage/pom.xml new file mode 100644 index 0000000..31720c0 --- /dev/null +++ b/xtools-boot-storage/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-storage + + + + xtools-boot-storage-file + xtools-boot-storage-s3 + xtools-boot-storage-base + + + \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-base/pom.xml b/xtools-boot-storage/xtools-boot-storage-base/pom.xml new file mode 100644 index 0000000..f009d9a --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.xujun + xtools-boot-storage + 5.1.0 + + xtools-boot-storage-base + + + + + + + org.xujun + xtools-boot-core + + + + + + org.springframework.boot + spring-boot + + + + \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/BootStorageBaseConfiguration.java b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/BootStorageBaseConfiguration.java new file mode 100644 index 0000000..46eb230 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/BootStorageBaseConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.storage.base; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.storage.base.selector.BootStorageBaseImportSelector; + +/** + *

Title : BootStorageBaseConfiguration

+ *

Description : BootStorageBaseConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootStorageBaseImportSelector.class) +public class BootStorageBaseConfiguration { + + /** + * 构造方法 + */ + public BootStorageBaseConfiguration() { + ModuleLoadUtils.loadSuccess(BootStorageBaseConfiguration.class); + } + +} diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/config/StorageConfig.java b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/config/StorageConfig.java new file mode 100644 index 0000000..2d4fd90 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/config/StorageConfig.java @@ -0,0 +1,77 @@ +package xtools.boot.storage.base.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + *

Title : StorageConfig

+ *

Description : StorageConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:40 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "storage") +public class StorageConfig { + + /** + * 存储类型[file|s3] + */ + private String type = "file"; + + /** + * 文件类型 + */ + private FileType file; + + /** + * S3类型 + */ + private S3Type s3; + + /** + * 文件类型 + */ + @Data + public static class FileType { + + /** + * 文件存储路径 + */ + private String path; + } + + /** + * S3类型 + */ + @Data + public static class S3Type { + + /** + * 服务类型[rustfs] + */ + private String server = "default"; + + /** + * 地址 + */ + private String host; + + /** + * 密钥ID + */ + private String accessKeyId; + + /** + * 密钥 + */ + private String secretAccessKey; + } + +} diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/selector/BootStorageBaseImportSelector.java b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/selector/BootStorageBaseImportSelector.java new file mode 100644 index 0000000..b6b3dca --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/selector/BootStorageBaseImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.storage.base.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootStorageBaseImportSelector

+ *

Description : BootStorageBaseImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootStorageBaseImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.storage.base"); + } + +} \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/service/StorageService.java b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/service/StorageService.java new file mode 100644 index 0000000..d376420 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/java/xtools/boot/storage/base/service/StorageService.java @@ -0,0 +1,72 @@ +package xtools.boot.storage.base.service; + +import jakarta.validation.constraints.NotNull; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + *

Title : StorageService

+ *

Description : StorageService

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:20 + */ +public interface StorageService { + + /** + * 是否存在文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @return true or false + */ + boolean exists( + @NotNull String bucket, + @NotNull String fileName + ); + + /** + * 保存文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param inputStream 输入流 + * @param contentLength 文件长度 + */ + void save( + @NotNull String bucket, + @NotNull String fileName, + @NotNull InputStream inputStream, + long contentLength + ); + + /** + * 获取文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param outputStream 输出流 + */ + void get( + @NotNull String bucket, + @NotNull String fileName, + @NotNull OutputStream outputStream + ); + + /** + * 删除文件 + * + * @param bucket 桶 + * @param fileName 文件名 + */ + void del( + @NotNull String bucket, + @NotNull String fileName + ); + +} diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..4a4342f --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.storage.base.BootStorageBaseConfiguration \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/application-boot-storage-demo.yaml b/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/application-boot-storage-demo.yaml new file mode 100644 index 0000000..10ee6db --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-base/src/main/resources/application-boot-storage-demo.yaml @@ -0,0 +1,18 @@ +# 存储配置 +storage: + # 存储类型[file|s3](并且需要引入对应的jar包) + type: ${STORAGE_TYPE:file} + # 文件类型 + file: + # 文件存储路径 + path: ${STORAGE_FILE_PATH:/data/xtools/file} + # S3类型 + s3: + # 服务类型(可为空)[rustfs] + server: ${STORAGE_S3_SERVER:rustfs} + # 地址 + host: ${STORAGE_S3_HOST:http://127.0.0.1:9000} + # 密钥ID + accessKeyId: ${STORAGE_S3_ACCESS_KEY_ID:admin} + # 密钥 + secretAccessKey: ${STORAGE_S3_SECRET_ACCESS_KEY:123456} \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-file/pom.xml b/xtools-boot-storage/xtools-boot-storage-file/pom.xml new file mode 100644 index 0000000..cb370bb --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.xujun + xtools-boot-storage + 5.1.0 + + xtools-boot-storage-file + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-storage-base + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + jakarta.annotation + jakarta.annotation-api + + + + \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/BootStorageFileConfiguration.java b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/BootStorageFileConfiguration.java new file mode 100644 index 0000000..6fd3df6 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/BootStorageFileConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.storage.file; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.storage.file.selector.BootStorageFileImportSelector; + +/** + *

Title : BootStorageFileConfiguration

+ *

Description : BootStorageFileConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootStorageFileImportSelector.class) +public class BootStorageFileConfiguration { + + /** + * 构造方法 + */ + public BootStorageFileConfiguration() { + ModuleLoadUtils.loadSuccess(BootStorageFileConfiguration.class); + } + +} diff --git a/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/config/FileStorageConfig.java b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/config/FileStorageConfig.java new file mode 100644 index 0000000..2b71a05 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/config/FileStorageConfig.java @@ -0,0 +1,33 @@ +package xtools.boot.storage.file.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import xtools.boot.storage.base.service.StorageService; +import xtools.boot.storage.file.service.impl.StorageServiceFileImpl; + +/** + *

Title : FileStorageConfig

+ *

Description : FileStorageConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:53 + */ +@Configuration +public class FileStorageConfig { + + /** + * 创建文件存储服务 + * + * @return StorageService + */ + @Bean + @ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "file") + public StorageService fileStorage() { + return new StorageServiceFileImpl(); + } +} diff --git a/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/selector/BootStorageFileImportSelector.java b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/selector/BootStorageFileImportSelector.java new file mode 100644 index 0000000..f84bde9 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/selector/BootStorageFileImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.storage.file.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootStorageFileImportSelector

+ *

Description : BootStorageFileImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootStorageFileImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.storage.file"); + } + +} \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/service/impl/StorageServiceFileImpl.java b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/service/impl/StorageServiceFileImpl.java new file mode 100644 index 0000000..df1d740 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/src/main/java/xtools/boot/storage/file/service/impl/StorageServiceFileImpl.java @@ -0,0 +1,163 @@ +package xtools.boot.storage.file.service.impl; + +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; +import lombok.extern.slf4j.Slf4j; +import xtools.boot.api.exection.BizError; +import xtools.boot.storage.base.config.StorageConfig; +import xtools.boot.storage.base.service.StorageService; +import xtools.core.FileUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import static xtools.base.config.Constants.CP_NEGATIVE1; +import static xtools.base.config.Constants.CP_NUM0; +import static xtools.base.config.Constants.CP_NUM1024; +import static xtools.base.config.Constants.CP_SLASH; + +/** + *

Title : StorageServiceS3Impl

+ *

Description : StorageServiceS3Impl

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:35 + */ +@Slf4j +public class StorageServiceFileImpl implements StorageService { + + @Resource + private StorageConfig storageConfig; + + /** + * 是否存在文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @return true or false + */ + @Override + public boolean exists( + @NotNull String bucket, + @NotNull String fileName + ) { + initBucket(bucket); + File file = new File(getFilePath(bucket, fileName)); + return file.exists(); + } + + /** + * 保存文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param inputStream 输入流 + * @param contentLength 文件长度 + */ + @Override + public void save( + @NotNull String bucket, + @NotNull String fileName, + @NotNull InputStream inputStream, + long contentLength + ) { + initBucket(bucket); + File file = new File(getFilePath(bucket, fileName)); + try (OutputStream outputStream = new FileOutputStream(file)) { + byte[] bytes = new byte[CP_NUM1024]; + int len; + while ((len = inputStream.read(bytes)) != CP_NEGATIVE1) { + outputStream.write(bytes, CP_NUM0, len); + } + } catch (Exception e) { + log.error("保存文件失败", e); + throw new BizError("保存文件失败"); + } + } + + /** + * 获取文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param outputStream 输出流 + */ + @Override + public void get( + @NotNull String bucket, + @NotNull String fileName, + @NotNull OutputStream outputStream + ) { + initBucket(bucket); + try (InputStream inputStream = new FileInputStream(getFilePath(bucket, fileName))) { + byte[] bytes = new byte[CP_NUM1024]; + int len; + while ((len = inputStream.read(bytes)) != CP_NEGATIVE1) { + outputStream.write(bytes, CP_NUM0, len); + } + } catch (Exception e) { + log.error("获取文件失败", e); + throw new BizError("获取文件失败"); + } + } + + /** + * 删除文件 + * + * @param bucket 桶 + * @param fileName 文件名 + */ + @Override + public void del( + @NotNull String bucket, + @NotNull String fileName + ) { + initBucket(bucket); + FileUtils.del(getFilePath(bucket, fileName)); + } + + /** + * 初始化桶 + * + * @param bucket 桶 + */ + private void initBucket(String bucket) { + String path = getPath(bucket); + File file = new File(path); + if (file.exists()) { + return; + } + if (file.mkdirs()) { + return; + } + throw new BizError("创建目录失败" + path); + } + + /** + * 获取路径 + * + * @param bucket 桶 + * @return 路径 + */ + private String getPath(String bucket) { + return storageConfig.getFile().getPath() + CP_SLASH + bucket; + } + + /** + * 获取文件路径 + * + * @param bucket 桶 + * @param fileName 文件名 + * @return 文件路径 + */ + private String getFilePath(String bucket, String fileName) { + return getPath(bucket) + CP_SLASH + fileName; + } +} diff --git a/xtools-boot-storage/xtools-boot-storage-file/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-storage/xtools-boot-storage-file/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8a14b28 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-file/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.storage.file.BootStorageFileConfiguration \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-s3/pom.xml b/xtools-boot-storage/xtools-boot-storage-s3/pom.xml new file mode 100644 index 0000000..c3edf2c --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.xujun + xtools-boot-storage + 5.1.0 + + xtools-boot-storage-s3 + + + + + + + + software.amazon.awssdk + s3 + + + org.xujun + xtools-extend + + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-storage-base + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + jakarta.annotation + jakarta.annotation-api + + + + \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/BootStorageS3Configuration.java b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/BootStorageS3Configuration.java new file mode 100644 index 0000000..0928f76 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/BootStorageS3Configuration.java @@ -0,0 +1,28 @@ +package xtools.boot.storage.s3; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.storage.s3.selector.BootStorageS3ImportSelector; + +/** + *

Title : BootStorageS3Configuration

+ *

Description : BootStorageS3Configuration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootStorageS3ImportSelector.class) +public class BootStorageS3Configuration { + + /** + * 构造方法 + */ + public BootStorageS3Configuration() { + ModuleLoadUtils.loadSuccess(BootStorageS3Configuration.class); + } + +} diff --git a/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/config/S3StorageConfig.java b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/config/S3StorageConfig.java new file mode 100644 index 0000000..a94fd0d --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/config/S3StorageConfig.java @@ -0,0 +1,75 @@ +package xtools.boot.storage.s3.config; + +import jakarta.annotation.Resource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import xtools.boot.storage.base.config.StorageConfig; +import xtools.boot.storage.base.service.StorageService; +import xtools.boot.storage.s3.service.impl.StorageServiceS3Impl; +import xtools.extend.S3Utils; + +/** + *

Title : S3StorageConfig

+ *

Description : S3StorageConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:53 + */ +@Configuration +public class S3StorageConfig { + + @Resource + private StorageConfig storageConfig; + + /** + * 创建S3默认客户端 + * + * @return S3Client + */ + @Bean + @ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "s3") + @ConditionalOnProperty(prefix = "storage.s3", name = "server", havingValue = "default") + public S3Client defaultClient() { + return S3Utils.client( + storageConfig.getS3().getHost(), + storageConfig.getS3().getAccessKeyId(), + storageConfig.getS3().getSecretAccessKey(), + Region.CN_NORTH_1, + true + ); + } + + /** + * 创建S3-rustfs客户端 + * + * @return S3Client + */ + @Bean + @ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "s3") + @ConditionalOnProperty(prefix = "storage.s3", name = "server", havingValue = "rustfs") + public S3Client rustfsClient() { + return S3Utils.rustfsClient( + storageConfig.getS3().getHost(), + storageConfig.getS3().getAccessKeyId(), + storageConfig.getS3().getSecretAccessKey() + ); + } + + /** + * 创建S3存储服务 + * + * @return StorageService + */ + @Bean + @ConditionalOnProperty(prefix = "storage", name = "type", havingValue = "s3") + public StorageService s3Storage() { + return new StorageServiceS3Impl(); + } +} diff --git a/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/selector/BootStorageS3ImportSelector.java b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/selector/BootStorageS3ImportSelector.java new file mode 100644 index 0000000..5917b52 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/selector/BootStorageS3ImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.storage.s3.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootStorageS3ImportSelector

+ *

Description : BootStorageS3ImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootStorageS3ImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.storage.s3"); + } + +} \ No newline at end of file diff --git a/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/service/impl/StorageServiceS3Impl.java b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/service/impl/StorageServiceS3Impl.java new file mode 100644 index 0000000..faa0194 --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/src/main/java/xtools/boot/storage/s3/service/impl/StorageServiceS3Impl.java @@ -0,0 +1,120 @@ +package xtools.boot.storage.s3.service.impl; + +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.services.s3.S3Client; +import xtools.boot.storage.base.service.StorageService; +import xtools.extend.S3Utils; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +/** + *

Title : StorageServiceS3Impl

+ *

Description : StorageServiceS3Impl

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/14 07:35 + */ +@Slf4j +@RequiredArgsConstructor +public class StorageServiceS3Impl implements StorageService { + + /** + * 桶集合 + */ + private final static Set BUCKET_SET = new HashSet<>(); + + @Resource + private S3Client s3Client; + + /** + * 是否存在文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @return true or false + */ + @Override + public boolean exists( + @NotNull String bucket, + @NotNull String fileName + ) { + initBucket(bucket); + return S3Utils.exists(s3Client, bucket, fileName); + } + + /** + * 保存文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param inputStream 输入流 + * @param contentLength 文件长度 + */ + @Override + public void save( + @NotNull String bucket, + @NotNull String fileName, + @NotNull InputStream inputStream, + long contentLength + ) { + initBucket(bucket); + S3Utils.upload(s3Client, bucket, fileName, inputStream, contentLength); + } + + /** + * 获取文件 + * + * @param bucket 桶 + * @param fileName 文件名 + * @param outputStream 输出流 + */ + @Override + public void get( + @NotNull String bucket, + @NotNull String fileName, + @NotNull OutputStream outputStream + ) { + S3Utils.download(s3Client, bucket, fileName, outputStream); + } + + /** + * 删除文件 + * + * @param bucket 桶 + * @param fileName 文件名 + */ + @Override + public void del( + @NotNull String bucket, + @NotNull String fileName + ) { + S3Utils.delete(s3Client, bucket, fileName); + } + + /** + * 初始化桶 + * + * @param bucket 桶 + */ + private void initBucket(String bucket) { + if (BUCKET_SET.contains(bucket)) { + return; + } + try { + S3Utils.createBucket(s3Client, bucket); + } catch (Exception e) { + log.info("文件桶已存在", e); + } + BUCKET_SET.add(bucket); + } +} diff --git a/xtools-boot-storage/xtools-boot-storage-s3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-storage/xtools-boot-storage-s3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0c2343c --- /dev/null +++ b/xtools-boot-storage/xtools-boot-storage-s3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.storage.s3.BootStorageS3Configuration \ No newline at end of file diff --git a/xtools-boot-task/pom.xml b/xtools-boot-task/pom.xml new file mode 100644 index 0000000..4039369 --- /dev/null +++ b/xtools-boot-task/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-task + + + + + + + org.xujun + xtools-boot-core + + + + + \ No newline at end of file diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/BootTaskConfiguration.java b/xtools-boot-task/src/main/java/xtools/boot/task/BootTaskConfiguration.java new file mode 100644 index 0000000..5a53720 --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/BootTaskConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.task; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.task.selector.BootTaskImportSelector; + +/** + *

Title : BootTaskConfiguration

+ *

Description : BootTaskConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootTaskImportSelector.class) +public class BootTaskConfiguration { + + /** + * 构造方法 + */ + public BootTaskConfiguration() { + ModuleLoadUtils.loadSuccess(BootTaskConfiguration.class); + } + +} diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/TaskBus.java b/xtools-boot-task/src/main/java/xtools/boot/task/TaskBus.java new file mode 100644 index 0000000..bf03c7b --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/TaskBus.java @@ -0,0 +1,111 @@ +package xtools.boot.task; + +import xtools.boot.api.exection.BizError; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.task.enums.TaskStatus; +import xtools.boot.task.interfaces.BaseTaskType; +import xtools.boot.task.interfaces.TaskBusInterface; +import xtools.boot.task.model.dto.TaskInfo; +import xtools.core.CollectionUtils; +import xtools.core.StringUtils; + +import java.time.Instant; +import java.util.Collection; +import java.util.Objects; + +/** + *

Title : TaskBus

+ *

Description : TaskBus

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/15 10:07 + */ +public class TaskBus { + + /** + * 任务数据 + **/ + private final TaskInfo taskInfo; + + /** + * 构造方法 + * + * @param code 任务编码 + * @param type 任务类型 + * @param status 任务状态 + */ + private TaskBus(String code, BaseTaskType type, TaskStatus status) { + // 参数处理 + if (StringUtils.isBlank(code)) { + throw new BizError("任务Code不能为空"); + } + if (type == null) { + throw new BizError("任务类型不能为空"); + } + if (status == null) { + throw new BizError("任务状态不能为空"); + } + taskInfo = new TaskInfo(); + // 参数赋值 + taskInfo.setCode(code); + taskInfo.setType(type.desc()); + taskInfo.setStatus(status); + taskInfo.setInfo(type.desc() + "-" + status.desc()); + taskInfo.setTime(Instant.now()); + } + + /** + * 初始化任务 + * + * @param code 任务编码 + * @param type 任务类型 + * @param status 任务状态 + * @return 当前对象 + */ + public static TaskBus init(String code, BaseTaskType type, TaskStatus status) { + return new TaskBus(code, type, status); + } + + /** + * 设置任务信息 + * + * @param info 任务信息 + * @return 当前对象 + */ + public TaskBus info(String info) { + if (StringUtils.isBlank(info)) { + throw new BizError("任务信息不能为空"); + } + taskInfo.setInfo(info); + return this; + } + + /** + * 设置任务时间 + * + * @param time 任务时间 + * @return 当前对象 + */ + public TaskBus time(Instant time) { + if (Objects.isNull(time)) { + throw new BizError("任务时间不能为空"); + } + taskInfo.setTime(time); + return this; + } + + /** + * 保存任务 + */ + public void save() { + Collection beanList = SpringContextUtils.getBeanList(TaskBusInterface.class); + if (CollectionUtils.isNotEmpty(beanList)) { + beanList.forEach(item -> item.save(taskInfo)); + } + } + +} diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/enums/TaskStatus.java b/xtools-boot-task/src/main/java/xtools/boot/task/enums/TaskStatus.java new file mode 100644 index 0000000..66af697 --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/enums/TaskStatus.java @@ -0,0 +1,91 @@ +package xtools.boot.task.enums; + +import xtools.boot.api.enums.BaseEnum; + +/** + *

Title : TaskStatus

+ *

Description : TaskStatus

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 10:31 + */ +public enum TaskStatus implements BaseEnum { + + // 任务进行中 + ING(0, "进行中"), + // 任务成功 + SUCCESS(1, "任务成功"), + // 任务失败 + ERROR(2, "任务失败"); + + /** + * 编码 + */ + private final int code; + + /** + * 说明 + */ + private final String desc; + + /** + * 初始化方法 + * + * @param code Code + * @param desc 说明 + */ + TaskStatus(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 判断枚举值类型 + * + * @param code 枚举值 + * @return 枚举值类型 + */ + public static TaskStatus valueOf(int code) { + for (TaskStatus type : values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("unknown code, code=" + code); + } + + /** + * 获取所有枚举 + * + * @return 所有枚举 + */ + @Override + public BaseEnum[] all() { + return values(); + } + + /** + * 获取枚举编码 + * + * @return 枚举编码 + */ + @Override + public int code() { + return code; + } + + /** + * 获取枚举说明 + * + * @return 枚举说明 + */ + @Override + public String desc() { + return desc; + } + +} \ No newline at end of file diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/BaseTaskType.java b/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/BaseTaskType.java new file mode 100644 index 0000000..462ef4c --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/BaseTaskType.java @@ -0,0 +1,17 @@ +package xtools.boot.task.interfaces; + +import xtools.boot.api.enums.BaseEnum; + +/** + *

Title : BaseTaskType

+ *

Description : BaseTaskType

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/15 09:52 + */ +public interface BaseTaskType extends BaseEnum { +} diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/TaskBusInterface.java b/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/TaskBusInterface.java new file mode 100644 index 0000000..e100c22 --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/interfaces/TaskBusInterface.java @@ -0,0 +1,25 @@ +package xtools.boot.task.interfaces; + +import xtools.boot.task.model.dto.TaskInfo; + +/** + *

Title : TaskBusInterface

+ *

Description : TaskBusInterface

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/15 10:16 + */ +public interface TaskBusInterface { + + /** + * 保存任务 + * + * @param taskInfo 任务 + */ + void save(TaskInfo taskInfo); + +} diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/model/dto/TaskInfo.java b/xtools-boot-task/src/main/java/xtools/boot/task/model/dto/TaskInfo.java new file mode 100644 index 0000000..8bce1e8 --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/model/dto/TaskInfo.java @@ -0,0 +1,47 @@ +package xtools.boot.task.model.dto; + +import lombok.Data; +import xtools.boot.task.enums.TaskStatus; + +import java.io.Serializable; +import java.time.Instant; + +/** + *

Title : TaskInfo

+ *

Description : TaskInfo

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/15 10:07 + */ +@Data +public class TaskInfo implements Serializable { + + /** + * 任务编码 + */ + private String code; + + /** + * 任务类型 + */ + private String type; + + /** + * 任务状态 + */ + private TaskStatus status; + + /** + * 任务信息 + */ + private String info; + + /** + * 任务时间 + */ + private Instant time; +} \ No newline at end of file diff --git a/xtools-boot-task/src/main/java/xtools/boot/task/selector/BootTaskImportSelector.java b/xtools-boot-task/src/main/java/xtools/boot/task/selector/BootTaskImportSelector.java new file mode 100644 index 0000000..ce719ec --- /dev/null +++ b/xtools-boot-task/src/main/java/xtools/boot/task/selector/BootTaskImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.task.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootTaskImportSelector

+ *

Description : BootTaskImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootTaskImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.task"); + } + +} \ No newline at end of file diff --git a/xtools-boot-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..77d1bc0 --- /dev/null +++ b/xtools-boot-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.task.BootTaskConfiguration \ No newline at end of file diff --git a/xtools-boot-thread/pom.xml b/xtools-boot-thread/pom.xml new file mode 100644 index 0000000..74fd59b --- /dev/null +++ b/xtools-boot-thread/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + xtools-boot-thread + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-log + + + + + \ No newline at end of file diff --git a/xtools-boot-thread/src/main/java/xtools/boot/thread/BootThreadConfiguration.java b/xtools-boot-thread/src/main/java/xtools/boot/thread/BootThreadConfiguration.java new file mode 100644 index 0000000..6549238 --- /dev/null +++ b/xtools-boot-thread/src/main/java/xtools/boot/thread/BootThreadConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.thread; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.thread.selector.BootThreadImportSelector; + +/** + *

Title : BootThreadConfiguration

+ *

Description : BootThreadConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootThreadImportSelector.class) +public class BootThreadConfiguration { + + /** + * 构造方法 + */ + public BootThreadConfiguration() { + ModuleLoadUtils.loadSuccess(BootThreadConfiguration.class); + } + +} diff --git a/xtools-boot-thread/src/main/java/xtools/boot/thread/callback/VirtualThreadTaskCallback.java b/xtools-boot-thread/src/main/java/xtools/boot/thread/callback/VirtualThreadTaskCallback.java new file mode 100644 index 0000000..c8eb1c0 --- /dev/null +++ b/xtools-boot-thread/src/main/java/xtools/boot/thread/callback/VirtualThreadTaskCallback.java @@ -0,0 +1,21 @@ +package xtools.boot.thread.callback; + +/** + *

Title : VirtualThreadTaskCallback

+ *

Description : VirtualThreadTaskCallback

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/8 20:48 + */ +public interface VirtualThreadTaskCallback { + + /** + * 回调方法 + */ + void callback(); + +} diff --git a/xtools-boot-thread/src/main/java/xtools/boot/thread/selector/BootThreadImportSelector.java b/xtools-boot-thread/src/main/java/xtools/boot/thread/selector/BootThreadImportSelector.java new file mode 100644 index 0000000..ffec8ef --- /dev/null +++ b/xtools-boot-thread/src/main/java/xtools/boot/thread/selector/BootThreadImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.thread.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootThreadImportSelector

+ *

Description : BootThreadImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootThreadImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.thread"); + } + +} \ No newline at end of file diff --git a/xtools-boot-thread/src/main/java/xtools/boot/thread/utils/VirtualThreadTaskUtils.java b/xtools-boot-thread/src/main/java/xtools/boot/thread/utils/VirtualThreadTaskUtils.java new file mode 100644 index 0000000..8ba120e --- /dev/null +++ b/xtools-boot-thread/src/main/java/xtools/boot/thread/utils/VirtualThreadTaskUtils.java @@ -0,0 +1,82 @@ +package xtools.boot.thread.utils; + +import lombok.extern.slf4j.Slf4j; +import xtools.boot.api.model.dto.log.HolderLogTrack; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.thread.callback.VirtualThreadTaskCallback; +import xtools.core.enums.LogLevel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + *

Title : VirtualThreadTaskUtils

+ *

Description : VirtualThreadTaskUtils

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/8 20:46 + */ +@Slf4j +public class VirtualThreadTaskUtils { + + /** + * 创建子线程并执行任务(不带日志记录,不推荐) + * + * @param callback 回调 + */ + public static void simple(VirtualThreadTaskCallback callback) { + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + try { + // 创建子线程,并异步提交 + CompletableFuture.runAsync(() -> { + try { + callback.callback(); + } catch (Exception e) { + log.error("子线程执行异常", e); + } + }, service); + } finally { + // 关闭线程池 + service.shutdown(); + } + } + + /** + * 创建子线程并执行任务(带日志记录,推荐) + * + * @param callback 回调 + */ + public static void execute(VirtualThreadTaskCallback callback) { + // 获取父线程的追踪日志信息 + LogTrack logTrack = LogTrackHolder.get(); + LogBus.init(LogLevel.INFO, LogBusBaseType.VIRTUAL_THREAD, logTrack).title("创建虚拟线程").save(); + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + try { + // 创建子线程,并异步提交 + CompletableFuture.runAsync(() -> { + // 生成子线程的追踪日志信息 + HolderLogTrack holderLogTrack = LogTrackHolder.newThread(logTrack); + ScopedValue.where(LogTrackHolder.getScoped(), holderLogTrack).run(() -> { + try { + callback.callback(); + } catch (Exception e) { + LogBus.init(LogLevel.ERROR, LogBusBaseType.VIRTUAL_THREAD).error(e).title("子线程执行异常").save(); + } + }); + }, service); + } finally { + // 关闭线程池 + service.shutdown(); + } + } + + +} diff --git a/xtools-boot-thread/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-thread/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..d2ca89f --- /dev/null +++ b/xtools-boot-thread/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.thread.BootThreadConfiguration \ No newline at end of file diff --git a/xtools-boot-web/pom.xml b/xtools-boot-web/pom.xml new file mode 100644 index 0000000..09da069 --- /dev/null +++ b/xtools-boot-web/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + org.xujun + xtools-boot + 5.1.0 + + pom + xtools-boot-web + + + + xtools-boot-web-base + xtools-boot-web-filter + + + \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-base/pom.xml b/xtools-boot-web/xtools-boot-web-base/pom.xml new file mode 100644 index 0000000..63c8dcd --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.xujun + xtools-boot-web + 5.1.0 + + xtools-boot-web-base + + + + + + + org.xujun + xtools-web + + + + + + + org.xujun + xtools-boot-core + + + + org.xujun + xtools-boot-log + + + + org.xujun + xtools-boot-web-filter + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/BootWebBaseConfiguration.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/BootWebBaseConfiguration.java new file mode 100644 index 0000000..a82fa73 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/BootWebBaseConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.web.base; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.web.base.selector.BootWebBaseImportSelector; + +/** + *

Title : BootWebBaseConfiguration

+ *

Description : BootWebBaseConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootWebBaseImportSelector.class) +public class BootWebBaseConfiguration { + + /** + * 构造方法 + */ + public BootWebBaseConfiguration() { + ModuleLoadUtils.loadSuccess(BootWebBaseConfiguration.class); + } + +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/JacksonConfig.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/JacksonConfig.java new file mode 100644 index 0000000..78c9fce --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/JacksonConfig.java @@ -0,0 +1,135 @@ +package xtools.boot.web.base.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import xtools.boot.api.anntation.IgnoreXss; +import xtools.boot.core.utils.TimeUtils; +import xtools.core.time.InstantUtils; +import xtools.web.xss.XssUtils; + +import java.time.Instant; +import java.util.Objects; + +/** + *

Title : JacksonConfig

+ *

Description : JacksonConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 12:15 + */ +@Configuration +public class JacksonConfig { + + /** + * 忽略 null 值 + */ + public static final JsonMapper NON_NULL; + + // 初始化 + static { + NON_NULL = JsonMapper.builder().changeDefaultPropertyInclusion(handler -> handler.withValueInclusion(JsonInclude.Include.NON_NULL)).build(); + } + + /** + * 创建 SimpleModule + * + * @return SimpleModule + */ + @Bean + public SimpleModule simpleModule() { + SimpleModule module = new SimpleModule(); + + // 序列化 + module.addSerializer(Long.class, new ValueSerializer<>() { + @Override + public void serialize(Long value, JsonGenerator gen, SerializationContext context) throws JacksonException { + if (value == null) { + gen.writeNull(); + } else { + gen.writeString(value.toString()); + } + } + }); + module.addSerializer(Instant.class, new ValueSerializer<>() { + @Override + public void serialize(Instant value, JsonGenerator gen, SerializationContext context) throws JacksonException { + if (value == null) { + gen.writeNull(); + } else { + gen.writeString(InstantUtils.format(value)); + } + } + }); + + // 反序列化 + module.addDeserializer(Instant.class, new ValueDeserializer<>() { + @Override + public Instant deserialize(JsonParser p, DeserializationContext context) throws JacksonException { + return TimeUtils.toInstant(p.getString()); + } + }); + // xss处理 + module.addDeserializer(String.class, new XssStringDeserializer()); + + return module; + } + + /** + * XSS 字符串反序列化器 + */ + static class XssStringDeserializer extends ValueDeserializer { + + /** + * 是否忽略 XSS 过滤 + */ + private final boolean ignore; + + /** + * 创建 XSS 忽略字符串反序列化器 + */ + XssStringDeserializer() { + this(false); + } + + /** + * 创建 XSS 忽略字符串反序列化器 + * + * @param ignore 是否忽略 XSS 过滤 + */ + XssStringDeserializer(boolean ignore) { + this.ignore = ignore; + } + + @Override + public String deserialize(JsonParser parser, DeserializationContext context) throws JacksonException { + String value = parser.getString(); + if (ignore) { + return value; + } + return XssUtils.filterParam(value); + } + + @Override + public ValueDeserializer createContextual(DeserializationContext context, BeanProperty property) throws JacksonException { + if (Objects.nonNull(property) && Objects.nonNull(property.getAnnotation(IgnoreXss.class))) { + return new XssStringDeserializer(true); + } + return new XssStringDeserializer(false); + } + } +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/LogTrackConfig.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/LogTrackConfig.java new file mode 100644 index 0000000..d94c24f --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/LogTrackConfig.java @@ -0,0 +1,30 @@ +package xtools.boot.web.base.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Title : LogTrackConfig

+ *

Description : LogTrackConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/2/3 15:07 + */ +@Data +@Component +@ConfigurationProperties(prefix = "sys.log.track") +public class LogTrackConfig { + + /** + * 日志追踪忽略路径 + */ + private List ignorePath = new ArrayList<>(); +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/MvcConverterConfig.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/MvcConverterConfig.java new file mode 100644 index 0000000..f0ad13e --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/config/MvcConverterConfig.java @@ -0,0 +1,42 @@ +package xtools.boot.web.base.config; + +import org.jspecify.annotations.NonNull; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.FormatterRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import xtools.boot.core.utils.TimeUtils; + +import java.time.Instant; + +/** + *

Title : MvcConverterConfig

+ *

Description : MvcConverterConfig

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 14:44 + */ +@Configuration +public class MvcConverterConfig implements WebMvcConfigurer { + + /** + * 添加转换器 + * + * @param registry registry + */ + @Override + public void addFormatters(FormatterRegistry registry) { + + // 添加时间转换器 + registry.addConverter(new Converter() { + @Override + public Instant convert(@NonNull String source) { + return TimeUtils.toInstant(source); + } + }); + } +} \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/exception/GlobalControllerExceptionHandler.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/exception/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000..b17c511 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/exception/GlobalControllerExceptionHandler.java @@ -0,0 +1,332 @@ +package xtools.boot.web.base.exception; + +import com.alibaba.fastjson2.JSONObject; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.resource.NoResourceFoundException; +import xtools.boot.api.enums.ResultType; +import xtools.boot.api.exection.BizError; +import xtools.boot.api.exection.BizPublicKeyError; +import xtools.boot.api.exection.BizWarning; +import xtools.boot.api.exection.UnauthorizedError; +import xtools.boot.api.model.dto.Result; +import xtools.boot.api.model.dto.log.LogTrack; +import xtools.boot.log.LogBus; +import xtools.boot.log.enums.LogBusBaseType; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.log.interfaces.LogBusType; +import xtools.core.StringUtils; +import xtools.core.enums.LogLevel; +import xtools.core.extend.TemplateUtils; + +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +import static xtools.base.config.Constants.CP_COLON; +import static xtools.base.config.Constants.CP_COMMA; + +/** + *

Title : GlobalExceptionHandler

+ *

Description : GlobalExceptionHandler

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/5 15:21 + */ +@Slf4j +@RestControllerAdvice +public class GlobalControllerExceptionHandler { + + /** + * 参数类型不匹配异常模板 + */ + private static final String ARGUMENT_TYPE_MISMATCH_TEMPLATE = "参数[{}]类型不匹配"; + + /** + * 参数类型不匹配 + * + * @param e 参数类型不匹配异常 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public Result handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) { + String errMsg = TemplateUtils.format(ARGUMENT_TYPE_MISMATCH_TEMPLATE, e.getPropertyName()); + debugLog("参数类型不匹配", errMsg, request, e); + return Result.badRequest(errMsg); + } + + /** + * 参数校验异常 + * + * @param e 参数校验异常 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(HandlerMethodValidationException.class) + public Result handleHandlerMethodValidationException(HandlerMethodValidationException e, HttpServletRequest request) { + // 错误结果 + StringJoiner msg = new StringJoiner(CP_COMMA); + // 处理参数验证结果 + e.getParameterValidationResults().forEach(validation -> { + // 获取参数名称 + String paramName = validation.getMethodParameter().getParameterName(); + // 获取字段约束违规 + validation.getResolvableErrors().forEach(item -> msg.add(paramName + CP_COLON + item.getDefaultMessage())); + }); + String errMsg = msg.toString(); + debugLog("参数校验异常", errMsg, request, e); + return Result.badRequest(errMsg); + } + + /** + * 参数校验异常 + * + * @param e 参数校验异常 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { + // 错误结果 + StringJoiner msg = new StringJoiner(CP_COMMA); + // 处理参数验证结果 + e.getBindingResult().getFieldErrors().forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = fieldError.getDefaultMessage(); + msg.add(fieldName + CP_COLON + errorMessage); + }); + String errMsg = msg.toString(); + debugLog("参数校验异常", errMsg, request, e); + return Result.badRequest(errMsg); + } + + /** + * 消息不可读异常 + * + * @param e 消息不可读异常 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) { + String errMsg = "请求参数格式错误"; + debugLog(errMsg, e.getMessage(), request, e); + return Result.badRequest(errMsg); + } + + /** + * 公钥错误 + * + * @param e 公钥错误 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(BizPublicKeyError.class) + public Result handleBizPublicKeyErrorException(BizPublicKeyError e, HttpServletRequest request) { + debugLog("公钥错误", e.getMessage(), request, e); + return new Result<>(true, e.getCode(), e.getMessage(), null); + } + + /** + * 业务异常 + * + * @param e 业务异常 + * @param request 请求 + * @return 错误结果 + */ + @ExceptionHandler(BizError.class) + public Result handleBizErrorException(BizError e, HttpServletRequest request) { + debugLog("业务异常", e.getMessage(), request, e); + return new Result<>(true, e.getCode(), e.getMessage(), null); + } + + /** + * 业务警告 + * + * @param e 业务异常 + * @param request 请求 + * @return 警告结果 + */ + @ExceptionHandler(BizWarning.class) + public Result handleBizWarningException(BizWarning e, HttpServletRequest request) { + debugLog("业务警告", e.getMessage(), request, e); + return new Result<>(true, e.getCode(), e.getMessage(), null); + } + + /** + * 认证异常 + * + * @param e 认证异常 + * @return 错误结果 + */ + @ExceptionHandler(UnauthorizedError.class) + public Result handleUnauthorizedErrorException(UnauthorizedError e) { + errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "认证异常", e); + return new Result<>(ResultType.UNAUTHORIZED, null); + } + + /** + * 请求方法不支持 + * + * @param e 请求方法不支持异常 + * @return 错误结果 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "请求方法不支持", e, false); + return new Result<>(ResultType.METHOD_NOT_ALLOWED, null); + } + + /** + * 资源未找到 + * + * @param e 资源未找到异常 + * @return 错误结果 + */ + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException(NoResourceFoundException e, HttpServletRequest request) { + errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "资源未找到", e, false); + if (checkTextResponse(request)) { + String body = JSONObject.toJSONString(new Result<>(ResultType.NOT_FOUND, null)); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * 检查是否为文本响应 + * + * @param request 请求 + * @return 是否为文本响应 + */ + private boolean checkTextResponse(HttpServletRequest request) { + String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); + if (StringUtils.isBlank(acceptHeader)) { + return true; + } + List acceptedTypes = MediaType.parseMediaTypes(acceptHeader); + for (MediaType item : acceptedTypes) { + // 通配符 */* 表示可接受任意类型,包括文本 + if (item.equals(MediaType.ALL)) { + return true; + } + String type = item.getType(); + switch (type) { + case "text" -> { + return true; + } + case "image", "multipart" -> { + return false; + } + default -> { + } + } + if ( + item.isCompatibleWith(MediaType.APPLICATION_ATOM_XML) || + item.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED) || + item.isCompatibleWith(MediaType.APPLICATION_GRAPHQL_RESPONSE) || + item.isCompatibleWith(MediaType.APPLICATION_JSON) || + item.isCompatibleWith(MediaType.APPLICATION_NDJSON) || + item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_JSON) || + item.isCompatibleWith(MediaType.APPLICATION_PROBLEM_XML) || + item.isCompatibleWith(MediaType.APPLICATION_RSS_XML) || + item.isCompatibleWith(MediaType.APPLICATION_XHTML_XML) || + item.isCompatibleWith(MediaType.APPLICATION_XML) || + item.isCompatibleWith(MediaType.APPLICATION_YAML) + ) { + return true; + } + } + return false; + } + + /** + * 客户端提前断开连接 + * + * @param e 客户端提前断开连接异常 + */ + @ExceptionHandler(ClientAbortException.class) + public void handleClientAbortException(ClientAbortException e) { + errorLog(LogLevel.WARN, LogBusBaseType.CONTROLLER, "客户端提前断开连接", e, false); + } + + /** + * 默认全局异常处理 + * + * @param e 异常 + * @return 错误结果 + */ + @ExceptionHandler(Exception.class) + public Result handleException(Exception e) { + errorLog(LogLevel.ERROR, LogBusBaseType.CONTROLLER, "默认全局异常处理", e); + return new Result<>(ResultType.INTERNAL_SERVER_ERROR, null); + } + + /** + * 错误日志 + * + * @param level 日志级别 + * @param type 异常类型 + * @param title 标题 + * @param e 异常 + */ + private void errorLog(LogLevel level, LogBusType type, String title, Exception e) { + errorLog(level, type, title, e, null); + } + + /** + * 错误日志 + * + * @param level 日志级别 + * @param type 异常类型 + * @param title 标题 + * @param e 异常 + * @param print 是否打印 + */ + private void errorLog(LogLevel level, LogBusType type, String title, Exception e, Boolean print) { + LogTrack logTrack = LogTrackHolder.getDefNull(); + if (Objects.isNull(logTrack)) { + logTrack = LogTrackHolder.getNewMain(); + } + LogBus logBus = LogBus.init(level, type, logTrack).title(title).error(e); + if (Objects.nonNull(print)) { + logBus.print(print); + } + logBus.save(); + } + + /** + * 调试日志 + * + * @param type 异常类型 + * @param msg 错误消息 + * @param request 请求 + * @param e 异常 + */ + private void debugLog(String type, String msg, HttpServletRequest request, Exception e) { + if (StringUtils.isBlank(msg)) { + log.debug("请求[{}]异常,类型[{}]", request.getRequestURI(), type, e); + } else { + log.debug("请求[{}]异常,类型[{}],错误消息[{}]", request.getRequestURI(), type, msg); + } + } + +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/CommonFilter.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/CommonFilter.java new file mode 100644 index 0000000..add5d49 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/CommonFilter.java @@ -0,0 +1,54 @@ +package xtools.boot.web.base.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import xtools.boot.core.holder.CommonHolder; +import xtools.boot.web.filter.base.BaseFilter; + +import java.io.IOException; + +import static xtools.base.config.Constants.CP_NUM1; +import static xtools.base.config.Constants.CP_NUM100; + +/** + *

Title : CommonFilter

+ *

Description : CommonFilter

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:37 + */ +@Slf4j +@Component +public class CommonFilter extends BaseFilter implements Ordered { + + @Override + public int getOrder() { + return CP_NUM100 + CP_NUM1; + } + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) { + ScopedValue.where(CommonHolder.getScoped(), CommonHolder.create()).run(() -> { + try { + filterChain.doFilter(request, response); + } catch (IOException | ServletException e) { + printLog(e, "添加通用ScopedValue异常"); + } + }); + } + +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/LogTrackFilter.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/LogTrackFilter.java new file mode 100644 index 0000000..72d4822 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/filter/LogTrackFilter.java @@ -0,0 +1,75 @@ +package xtools.boot.web.base.filter; + +import jakarta.annotation.Resource; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import xtools.boot.api.constant.BootCommonConstant; +import xtools.boot.api.model.dto.log.HolderLogTrack; +import xtools.boot.core.utils.PathPatternUtils; +import xtools.boot.log.holder.LogTrackHolder; +import xtools.boot.web.base.config.LogTrackConfig; +import xtools.boot.web.filter.base.BaseFilter; +import xtools.core.StringUtils; +import xtools.web.HeaderUtils; + +import java.io.IOException; + +import static xtools.base.config.Constants.CP_NUM100; + +/** + *

Title : LogTrackFilter

+ *

Description : LogTrackFilter

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/1/6 16:37 + */ +@Slf4j +@Component +public class LogTrackFilter extends BaseFilter implements Ordered { + + @Resource + private LogTrackConfig logTrackConfig; + + @Override + public int getOrder() { + return CP_NUM100; + } + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) { + String uri = HeaderUtils.getUri(request); + String headerLogTrack = HeaderUtils.getHeader(request, BootCommonConstant.LOG_TRACK); + HolderLogTrack logTrack; + if (StringUtils.isBlank(headerLogTrack)) { + logTrack = LogTrackHolder.newMain(); + } else { + logTrack = LogTrackHolder.newByBase64(headerLogTrack); + } + ScopedValue.where(LogTrackHolder.getScoped(), logTrack).run(() -> { + // 判断该uri是否忽略记录日志 + if (PathPatternUtils.match(logTrackConfig.getIgnorePath(), uri)) { + LogTrackHolder.saveLog(false); + } + try { + filterChain.doFilter(request, response); + } catch (IOException | ServletException e) { + printLog(e, "添加日志追踪ScopedValue异常"); + } + }); + } + +} diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/selector/BootWebBaseImportSelector.java b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/selector/BootWebBaseImportSelector.java new file mode 100644 index 0000000..a4f5d6a --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/java/xtools/boot/web/base/selector/BootWebBaseImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.web.base.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootWebBaseImportSelector

+ *

Description : BootWebBaseImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootWebBaseImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.web.base"); + } + +} \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-web/xtools-boot-web-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..99aa88e --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-base/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.web.base.BootWebBaseConfiguration \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-filter/pom.xml b/xtools-boot-web/xtools-boot-web-filter/pom.xml new file mode 100644 index 0000000..e371f15 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + org.xujun + xtools-boot-web + 5.1.0 + + xtools-boot-web-filter + + + + + + + + org.xujun + xtools-boot-core + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/BootWebFilterConfiguration.java b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/BootWebFilterConfiguration.java new file mode 100644 index 0000000..d05ebfa --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/BootWebFilterConfiguration.java @@ -0,0 +1,28 @@ +package xtools.boot.web.filter; + +import org.springframework.context.annotation.Import; +import xtools.boot.core.utils.ModuleLoadUtils; +import xtools.boot.web.filter.selector.BootWebFilterImportSelector; + +/** + *

Title : BootWebFilterConfiguration

+ *

Description : BootWebFilterConfiguration

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +@Import(BootWebFilterImportSelector.class) +public class BootWebFilterConfiguration { + + /** + * 构造方法 + */ + public BootWebFilterConfiguration() { + ModuleLoadUtils.loadSuccess(BootWebFilterConfiguration.class); + } + +} diff --git a/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/base/BaseFilter.java b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/base/BaseFilter.java new file mode 100644 index 0000000..27d29b2 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/base/BaseFilter.java @@ -0,0 +1,65 @@ +package xtools.boot.web.filter.base; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; +import org.jspecify.annotations.NonNull; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + *

Title : BaseFilter

+ *

Description : BaseFilter

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/8 11:06 + */ +@Slf4j +public abstract class BaseFilter extends OncePerRequestFilter { + + /** + * 跳过过滤器标识 + */ + private static final String SKIP_FILTER = "skip.filters"; + + /** + * 添加跳过过滤器标识 + * + * @param request 请求 + */ + public void addSkipFilter(HttpServletRequest request) { + request.setAttribute(SKIP_FILTER, true); + } + + /** + * 日志 + * + * @param e 异常 + * @param msg 消息 + */ + public void printLog(Exception e, String msg) { + if (e instanceof ClientAbortException) { + return; + } + log.error(msg, e); + } + + /** + * 跳过过滤器 + * + * @param request 请求 + * @return 是否跳过过滤器 + */ + @Override + protected boolean shouldNotFilter(@NonNull HttpServletRequest request) { + Object attribute = request.getAttribute(SKIP_FILTER); + if (attribute instanceof Boolean skip) { + return skip; + } + return false; + } + +} diff --git a/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/common/SkipFilter.java b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/common/SkipFilter.java new file mode 100644 index 0000000..8860511 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/common/SkipFilter.java @@ -0,0 +1,70 @@ +package xtools.boot.web.filter.common; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.NonNull; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import xtools.boot.core.interfaces.FilterWhitelist; +import xtools.boot.core.utils.PathPatternUtils; +import xtools.boot.core.utils.SpringContextUtils; +import xtools.boot.web.filter.base.BaseFilter; +import xtools.core.CollectionUtils; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static xtools.base.config.Constants.CP_NUM0; + +/** + *

Title : SkipFilter

+ *

Description : SkipFilter

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/3/8 11:00 + */ +@Component +public class SkipFilter extends BaseFilter implements Ordered { + + /** + * 白名单 + */ + private static final Set WHITE_LIST = new HashSet<>(); + + @Override + public int getOrder() { + return CP_NUM0; + } + + @Override + protected void initFilterBean() throws ServletException { + super.initFilterBean(); + Collection beanList = SpringContextUtils.getBeanList(FilterWhitelist.class); + if (CollectionUtils.isEmpty(beanList)) { + return; + } + beanList.forEach(item -> WHITE_LIST.addAll(item.add())); + } + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + // 校验白名单 + if (PathPatternUtils.match(WHITE_LIST, request.getRequestURI())) { + addSkipFilter(request); + } + filterChain.doFilter(request, response); + } + +} diff --git a/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/selector/BootWebFilterImportSelector.java b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/selector/BootWebFilterImportSelector.java new file mode 100644 index 0000000..f5c6949 --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/src/main/java/xtools/boot/web/filter/selector/BootWebFilterImportSelector.java @@ -0,0 +1,36 @@ +package xtools.boot.web.filter.selector; + +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + *

Title : BootWebFilterImportSelector

+ *

Description : BootWebFilterImportSelector

+ *

DevelopTools : Idea_x64_v2026.1

+ *

DevelopSystem : macOS Sequoia 15.7.5

+ *

Company : org.xujun

+ * + * @author : XuJun + * @version : 5.0.0 + * @date : 2026/01/01 09:30 + */ +public class BootWebFilterImportSelector implements ImportBeanDefinitionRegistrar { + + /** + * 根据给定的注释元数据,根据需要注册bean + * + * @param importingClassMetadata AnnotationMetadata + * @param registry BeanDefinitionRegistry + */ + @Override + public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, @NonNull BeanDefinitionRegistry registry) { + // 构建扫描对象 + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, true); + // 扫描包下路径 + scanner.scan("xtools.boot.web.filter"); + } + +} \ No newline at end of file diff --git a/xtools-boot-web/xtools-boot-web-filter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/xtools-boot-web/xtools-boot-web-filter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..12aa76d --- /dev/null +++ b/xtools-boot-web/xtools-boot-web-filter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xtools.boot.web.filter.BootWebFilterConfiguration \ No newline at end of file