初始化仓库

This commit is contained in:
2026-04-21 15:08:07 +08:00
parent 444d984122
commit b5119afb9f
195 changed files with 11034 additions and 19 deletions

43
.gitignore vendored
View File

@@ -1,20 +1,29 @@
# ---> Actionscript
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# ---> Project
# Other files and folders
# 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
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
# ---> Idea
.idea/
*.iml

1113
README.md

File diff suppressed because it is too large Load Diff

44
pom.xml Normal file
View File

@@ -0,0 +1,44 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>xtools-boot</artifactId>
<packaging>pom</packaging>
<name>xtools-boot</name>
<description>低调大师工具箱,SpringBoot工具模块,适配JDK25</description>
<!-- 父pom -->
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-parent-boot</artifactId>
<version>5.0.0</version>
<relativePath/>
</parent>
<!-- 子模块 -->
<modules>
<module>xtools-boot-api</module>
<module>xtools-boot-core</module>
<module>xtools-boot-cache</module>
<module>xtools-boot-db</module>
<module>xtools-boot-elasticsearch</module>
<module>xtools-boot-ip</module>
<module>xtools-boot-job</module>
<module>xtools-boot-knife4j</module>
<module>xtools-boot-log</module>
<module>xtools-boot-mask</module>
<module>xtools-boot-mq</module>
<module>xtools-boot-storage</module>
<module>xtools-boot-task</module>
<module>xtools-boot-thread</module>
<module>xtools-boot-web</module>
</modules>
<!-- 属性配置 -->
<properties>
<!-- JDK 版本 -->
<java.version>25</java.version>
</properties>
</project>

27
xtools-boot-api/pom.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-api</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- jakarta.validation -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<!-- swagger annotations -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : IgnoreXss</p>
* <p>Description : IgnoreXss</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/9 10:07
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreXss {
}

View File

@@ -0,0 +1,46 @@
package xtools.boot.api.constant;
/**
* <p>Title : BootCommonConstant</p>
* <p>Description : BootCommonConstant</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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";
}

View File

@@ -0,0 +1,39 @@
package xtools.boot.api.enums;
/**
* <p>Title : BaseEnum</p>
* <p>Description : BaseEnum</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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();
}

View File

@@ -0,0 +1,122 @@
package xtools.boot.api.enums;
import xtools.base.exception.BaseError;
import xtools.base.exception.BaseErrorModule;
/**
* <p>Title : BootError</p>
* <p>Description : BootError</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,51 @@
package xtools.boot.api.enums;
import xtools.base.exception.BaseErrorModule;
/**
* <p>Title : BootErrorModule</p>
* <p>Description : BootErrorModule</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,105 @@
package xtools.boot.api.enums;
import java.util.Objects;
/**
* <p>Title : DeleteEnum</p>
* <p>Description : DeleteEnum</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,73 @@
package xtools.boot.api.enums;
/**
* <p>Title : FileDataType</p>
* <p>Description : FileDataType</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,78 @@
package xtools.boot.api.enums;
/**
* <p>Title : ResultType</p>
* <p>Description : ResultType</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,105 @@
package xtools.boot.api.enums;
import java.util.Objects;
/**
* <p>Title : StatusEnum</p>
* <p>Description : StatusEnum</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,71 @@
package xtools.boot.api.enums;
/**
* <p>Title : ThreadType</p>
* <p>Description : ThreadType</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,27 @@
package xtools.boot.api.exection;
import xtools.base.exception.CommonException;
import xtools.boot.api.enums.BootError;
/**
* <p>Title : BizError</p>
* <p>Description : BizError</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -0,0 +1,27 @@
package xtools.boot.api.exection;
import xtools.base.exception.CommonException;
import xtools.boot.api.enums.BootError;
/**
* <p>Title : BizPublicKeyError</p>
* <p>Description : BizPublicKeyError</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -0,0 +1,27 @@
package xtools.boot.api.exection;
import xtools.base.exception.CommonException;
import xtools.boot.api.enums.BootError;
/**
* <p>Title : BizWarning</p>
* <p>Description : BizWarning</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -0,0 +1,25 @@
package xtools.boot.api.exection;
import xtools.base.exception.CommonException;
import xtools.boot.api.enums.BootError;
/**
* <p>Title : UnauthorizedError</p>
* <p>Description : UnauthorizedError</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/29 11:08
*/
public class UnauthorizedError extends CommonException {
/**
* 构造方法
*/
public UnauthorizedError() {
super(BootError.UNAUTHORIZED_ERROR, "认证异常");
}
}

View File

@@ -0,0 +1,34 @@
package xtools.boot.api.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : EnumInfoDto</p>
* <p>Description : EnumInfoDto</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}

View File

@@ -0,0 +1,139 @@
package xtools.boot.api.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import xtools.base.config.BaseParams;
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;
/**
* <p>Title : Result</p>
* <p>Description : Result</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2025/12/22 14:07
*/
@Data
public final class Result<T> implements BaseParams {
@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<Object> ok() {
return new Result<>(ResultType.OK, null);
}
/**
* 请求成功
*
* @param data 数据
* @param <T> 泛型
* @return 结果
*/
public static <T> Result<T> ok(T data) {
return new Result<>(ResultType.OK, data);
}
/**
* 请求成功,业务失败
*
* @param code 业务错误码
* @param msg 业务错误信息
* @return 响应结果
*/
public static Result<Object> 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 <T> Result<T> badRequest() {
return new Result<>(ResultType.BAD_REQUEST, null);
}
/**
* 请求参数缺失 | 参数格式错误(如:期望数字却传了字符串) | 参数类型不匹配 | JSON/XML格式错误 | 必填参数为空
*
* @param msg 错误信息
* @return 响应结果
*/
public static <T> Result<T> 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;
}
}

View File

@@ -0,0 +1,74 @@
package xtools.boot.api.model.dto.log;
import lombok.Data;
import xtools.base.config.BaseParams;
import xtools.boot.api.enums.ThreadType;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>Title : HolderLogTrack</p>
* <p>Description : HolderLogTrack</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/6 16:44
*/
@Data
public class HolderLogTrack implements BaseParams {
/**
* 日志追踪 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);
}
}

View File

@@ -0,0 +1,25 @@
package xtools.boot.api.model.dto.log;
import xtools.boot.api.enums.ThreadType;
/**
* <p>Title : LogTrack</p>
* <p>Description : LogTrack</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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) {
}

View File

@@ -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;
/**
* <p>Title : PageReq</p>
* <p>Description : PageReq</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2025/12/22 14:58
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageReq<T> 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;
}

View File

@@ -0,0 +1,86 @@
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;
/**
* <p>Title : PageResp</p>
* <p>Description : PageResp</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2025/12/22 14:58
*/
@Data
@NoArgsConstructor
public final class PageResp<T> 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<T> data;
/**
* 构造方法
*
* @param req 分页请求
*/
public PageResp(PageReq<?> req) {
this.currentPage = req.getCurrentPage();
this.pageSize = req.getPageSize();
}
/**
* 构造方法
*
* @param req 分页请求
*/
public PageResp(PageReq<?> req, Long total, List<T> data) {
this.currentPage = req.getCurrentPage();
this.pageSize = req.getPageSize();
this.total = total;
this.data = data;
}
/**
* 获取总页数
*
* @return 总页数
*/
public Long getPageCount() {
return PagingUtils.getLastPage(this.total, this.pageSize);
}
}

View File

@@ -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;
/**
* <p>Title : IdListReq</p>
* <p>Description : IdListReq</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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<Long> idList;
}

View File

@@ -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;
/**
* <p>Title : TreeResp</p>
* <p>Description : TreeResp</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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<TreeResp> children;
}

View File

@@ -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;
/**
* <p>Title : BaseEntity</p>
* <p>Description : BaseEntity</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}

19
xtools-boot-cache/pom.xml Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<packaging>pom</packaging>
<artifactId>xtools-boot-cache</artifactId>
<!-- 子模块 -->
<modules>
<module>xtools-boot-cache-redis</module>
</modules>
</project>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-cache</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-cache-redis</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- SpringBoot begin -->
<!-- SpringBoot-Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- SpringBoot end -->
<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootCacheRedisConfiguration</p>
* <p>Description : BootCacheRedisConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootCacheRedisImportSelector.class)
public class BootCacheRedisConfiguration {
/**
* 构造方法
*/
public BootCacheRedisConfiguration() {
ModuleLoadUtils.loadSuccess(BootCacheRedisConfiguration.class);
}
}

View File

@@ -0,0 +1,458 @@
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.base.config.BaseParams;
import xtools.core.ArrUtils;
import xtools.core.CollectionUtils;
import xtools.core.StringUtils;
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;
/**
* <p>Title : RedisService</p>
* <p>Description : RedisService</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Component
public class RedisService implements BaseParams {
/**
* RedisTemplate
**/
@Resource
private RedisTemplate<String, Object> 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, timeout, unit);
}
}
/**
* 保存数据(如果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, timeout, unit);
}
}
/**
* 获取数据
*
* @param <T> Class 类型
* @param key Key
* @param clazz Class
* @return 数据
*/
public <T> T get(String key, Class<T> 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, timeout, TimeUnit.SECONDS);
}
/**
* 取消对 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<String> 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, expireTime, TimeUnit.SECONDS);
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<Long> 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<Object, String> 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().putIfAbsent(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 <T> 结果类型
* @param key key
* @param clazz 结果类型
* @return 数据集
*/
public <T> T hashEntries(String key, Class<T> clazz) {
// 参数校验
if (StringUtils.isEmpty(key)) {
return null;
}
Map<Object, Object> data = stringRedisTemplate.opsForHash().entries(key);
if (CollectionUtils.isEmpty(data)) {
return null;
}
return JSON.to(clazz, data);
}
/**
* 获取 Hash 中指定 Key 的数据
*
* @param <T> 结果类型
* @param key key
* @param hashKey hashKey
* @param clazz 结果类型
* @return 数据
*/
public <T> T hashGet(String key, Object hashKey, Class<T> 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);
}
}

View File

@@ -0,0 +1,30 @@
package xtools.boot.cache.redis.enums;
/**
* <p>Title : BaseCacheEnum</p>
* <p>Description : BaseCacheEnum</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/3/25 14:57
*/
public interface BaseCacheEnum {
/**
* 获取key
*
* @return key
*/
String key();
/**
* 获取超时时间
*
* @return 超时时间
*/
Long expireTime();
}

View File

@@ -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;
/**
* <p>Title : RedisMonitor</p>
* <p>Description : RedisMonitor</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Component
public class RedisMonitor {
@Resource
private RedisTemplate<String, String> 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);
}
}

View File

@@ -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;
/**
* <p>Title : BootCacheRedisImportSelector</p>
* <p>Description : BootCacheRedisImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,36 @@
package xtools.boot.cache.redis.utils;
import xtools.boot.cache.redis.base.RedisService;
import xtools.boot.core.utils.SpringContextUtils;
/**
* <p>Title : RedisUtils</p>
* <p>Description : RedisUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

31
xtools-boot-core/pom.xml Normal file
View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-core</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-api -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-api</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- spring 依赖 begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- spring 依赖 end -->
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootCoreConfiguration</p>
* <p>Description : BootCoreConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootCoreImportSelector.class)
public class BootCoreConfiguration {
/**
* 构造方法
*/
public BootCoreConfiguration() {
ModuleLoadUtils.loadSuccess(BootCoreConfiguration.class);
}
}

View File

@@ -0,0 +1,98 @@
package xtools.boot.core.holder;
import xtools.base.config.BaseParams;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* <p>Title : CommonHolder</p>
* <p>Description : CommonHolder</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/31 21:20
*/
public class CommonHolder implements BaseParams {
/**
* Mvc通用信息
*/
private static final ScopedValue<Map<String, Object>> COMMON_INFO = ScopedValue.newInstance();
/**
* 获取通用信息 Holder
*
* @return 通用信息
*/
public static ScopedValue<Map<String, Object>> getScoped() {
return COMMON_INFO;
}
/**
* 创建通用信息
*
* @return 通用信息
*/
public static Map<String, Object> create() {
return new HashMap<>(16);
}
/**
* 获取通用信息
*
* @return 通用信息
*/
public static Map<String, Object> 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<String, Object> map = get();
if (Objects.nonNull(map)) {
map.put(key, value);
}
}
/**
* 获取通用信息
*
* @param key Key
* @return Value
*/
public static Object get(String key) {
Map<String, Object> map = get();
if (Objects.nonNull(map)) {
return map.get(key);
}
return null;
}
/**
* 获取通用信息
*
* @param key Key
* @return Value
*/
public static boolean getBoolean(String key) {
Map<String, Object> map = get();
if (Objects.nonNull(map) && map.get(key) instanceof Boolean value) {
return value;
}
return false;
}
}

View File

@@ -0,0 +1,25 @@
package xtools.boot.core.interfaces;
import java.util.Set;
/**
* <p>Title : FilterWhitelist</p>
* <p>Description : FilterWhitelist</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/3/8 12:23
*/
public interface FilterWhitelist {
/**
* 添加过滤器白名单
*
* @return 过滤器白名单
*/
Set<String> add();
}

View File

@@ -0,0 +1,34 @@
package xtools.boot.core.interfaces;
/**
* <p>Title : JobInterface</p>
* <p>Description : JobInterface</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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() {
}
}

View File

@@ -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;
/**
* <p>Title : BootCoreImportSelector</p>
* <p>Description : BootCoreImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,67 @@
package xtools.boot.core.utils;
import xtools.base.config.BaseParams;
import xtools.core.StringUtils;
import xtools.core.encrypt.Md5Utils;
import java.util.List;
/**
* <p>Title : AddrUtils</p>
* <p>Description : AddrUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/3/13 15:09
*/
public class AddrUtils implements BaseParams {
/**
* 本地IP列表
*/
private static final List<String> 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);
}
/**
* 根据IP地址获取通用地址code
*
* @param country 国家
* @param province 省份
* @param city 城市
* @return 通用地址code
*/
public static String getCode(String country, String province, String city) {
String code = country;
if (StringUtils.isNotBlank(province)) {
code += CP_LINE + province;
}
if (StringUtils.isNotBlank(city)) {
code += CP_LINE + city;
}
return Md5Utils.encryptToString(code);
}
}

View File

@@ -0,0 +1,33 @@
package xtools.boot.core.utils;
import xtools.core.extend.TemplateUtils;
/**
* <p>Title : AppUtils</p>
* <p>Description : AppUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -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;
/**
* <p>Title : EnumUtils</p>
* <p>Description : EnumUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/14 16:29
*/
public class EnumUtils implements Serializable {
/**
* 获取枚举信息
*
* @param list 枚举值列表
* @return 枚举的详细信息
*/
public static List<EnumInfoDto> getEnumInfos(BaseEnum... list) {
if (ArrUtils.isEmpty(list)) {
return null;
}
List<EnumInfoDto> dataList = new ArrayList<>();
for (BaseEnum item : list) {
dataList.add(new EnumInfoDto(item.code(), item.desc()));
}
return dataList;
}
}

View File

@@ -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;
/**
* <p>Title : JarUtils</p>
* <p>Description : JarUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/18 20:57
*/
@Slf4j
public class JarUtils implements Serializable {
/**
* 获取所有jar名称
*
* @return jar名称
*/
public static List<String> getJarName() {
String end = "jar";
List<String> mfPath = null;
try {
mfPath = SysBaseInfoUtils.getMfPath();
} catch (Exception e) {
log.error("获取[MANIFEST.MF]文件路径失败", e);
}
if (CollectionUtils.isEmpty(mfPath)) {
return null;
}
List<String> 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();
}
}

View File

@@ -0,0 +1,32 @@
package xtools.boot.core.utils;
import lombok.extern.slf4j.Slf4j;
/**
* <p>Title : ModuleLoadUtils</p>
* <p>Description : ModuleLoadUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -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;
/**
* <p>Title : PathPatternUtils</p>
* <p>Description : PathPatternUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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<String> 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<String> 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;
}
}

View File

@@ -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;
/**
* <p>Title : SpringContextUtils</p>
* <p>Description : SpringContextUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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 <T> 类的类型
* @param requiredType 类型
* @return Bean
*/
public static <T> T getBean(Class<T> requiredType) {
try {
return getBeanFactory().getBean(requiredType);
} catch (Exception e) {
throw CommonException.create(BootError.SPRING_BASE, e, "根据类的类型获取对应的 Bean");
}
}
/**
* 根据类的类型获取对应的 Bean
*
* @param <T> 类的类型
* @param requiredType 类型
* @return Bean
*/
public static <T> T getBeanDefNull(Class<T> requiredType) {
try {
return getBeanFactory().getBean(requiredType);
} catch (Exception e) {
return null;
}
}
/**
* 根据类的类型获取对应的 Bean 列表
*
* @param <T> 类的类型
* @param requiredType 类型
* @return Bean 列表
*/
public static <T> Collection<T> getBeanList(Class<T> 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 <T> Bean 类型
* @param beanName 名称
* @param bean Bean
*/
public static <T> 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;
}
}

View File

@@ -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;
/**
* <p>Title : TimeUtils</p>
* <p>Description : TimeUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}
}

View File

@@ -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;
/**
* <p>Title : TreeUtils</p>
* <p>Description : TreeUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/30 09:41
*/
public class TreeUtils {
/**
* 获取子节点
*
* @param id 父级 ID
* @param list 数据级
* @return 子级
*/
public static List<TreeResp> getChild(Long id, List<TreeResp> list) {
List<TreeResp> childList = new ArrayList<>();
list.stream().filter(item -> item.getParentId().equals(id)).forEach(item -> {
List<TreeResp> child = getChild(item.getValue(), list);
item.setChildren(child);
childList.add(item);
});
return childList;
}
}

View File

@@ -0,0 +1 @@
xtools.boot.core.BootCoreConfiguration

20
xtools-boot-db/pom.xml Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<packaging>pom</packaging>
<artifactId>xtools-boot-db</artifactId>
<!-- 子模块 -->
<modules>
<module>xtools-boot-db-mybatis</module>
<module>xtools-boot-db-mybatis-plus</module>
</modules>
</project>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-db</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-db-mybatis-plus</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- SpringBoot begin -->
<!-- mybatis-plus begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
</dependency>
<!-- mybatis-plus end -->
<!-- SpringBoot end -->
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootCacheRedisConfiguration</p>
* <p>Description : BootCacheRedisConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootDbMybatisPlusImportSelector.class)
public class BootDbMybatisPlusConfiguration {
/**
* 构造方法
*/
public BootDbMybatisPlusConfiguration() {
ModuleLoadUtils.loadSuccess(BootDbMybatisPlusConfiguration.class);
}
}

View File

@@ -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;
/**
* <p>Title : MybatisPlusConfig</p>
* <p>Description : MybatisPlusConfig</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -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;
/**
* <p>Title : BootDbMybatisPlusImportSelector</p>
* <p>Description : BootDbMybatisPlusImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,59 @@
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.base.config.BaseParams;
import xtools.core.ArrUtils;
import java.time.Instant;
import java.util.Objects;
/**
* <p>Title : QueryUtils</p>
* <p>Description : QueryUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/1/17 19:16
*/
public class QueryUtils implements BaseParams {
/**
* 添加时间范围查询
*
* @param query 查询条件
* @param timeRange 时间范围
* @param timeField 时间字段
* @param <T> 泛型
*/
public static <T> void addTimeRange(LambdaQueryWrapper<T> query, Instant[] timeRange, SFunction<T, ?> 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 <T> 泛型
* @return 分页条件
*/
public static <T> Page<T> 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);
}
}

View File

@@ -0,0 +1 @@
xtools.boot.db.mybatisplus.BootDbMybatisPlusConfiguration

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-db</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-db-mybatis</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot-log -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-log</artifactId>
</dependency>
<!-- xtools-boot-thread -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-thread</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- SpringBoot begin -->
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- SpringBoot end -->
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Druid(数据库连接池) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-4-starter</artifactId>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootDbMybatisConfiguration</p>
* <p>Description : BootDbMybatisConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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);
}
}

View File

@@ -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;
/**
* <p>Title : MyBatisConfig</p>
* <p>Description : MyBatisConfig</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}

View File

@@ -0,0 +1,96 @@
package xtools.boot.db.mybatis.druid;
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;
/**
* <p>Title : DruidConf</p>
* <p>Description : DruidConf</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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.druid.web-stat-filter.enabled", havingValue = "true")
public FilterRegistrationBean<Filter> 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("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
/**
* 销毁
*/
@Override
public void destroy() {
}
};
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(filter);
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@@ -0,0 +1,33 @@
package xtools.boot.db.mybatis.druid;
import org.springframework.stereotype.Component;
import xtools.boot.core.interfaces.FilterWhitelist;
import java.util.Set;
/**
* <p>Title : DruidFilterWhitelist</p>
* <p>Description : DruidFilterWhitelist</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/3/8 12:28
*/
@Component
public class DruidFilterWhitelist implements FilterWhitelist {
/**
* 添加过滤器白名单
*
* @return 过滤器白名单
*/
@Override
public Set<String> add() {
return Set.of(
"/druid/**"
);
}
}

View File

@@ -0,0 +1,121 @@
package xtools.boot.db.mybatis.enums;
import xtools.boot.api.enums.BaseEnum;
/**
* <p>Title : MySqlMonitorEnums</p>
* <p>Description : MySqlMonitorEnums</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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"),
// 需要权限
// // 查看正在进行中的事务
// 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语句
*/
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;
}
/**
* 获取SQL
*
* @return SQL
*/
public String getSql() {
return sql;
}
}

View File

@@ -0,0 +1,235 @@
package xtools.boot.db.mybatis.interceptor;
import com.alibaba.druid.pool.DruidPooledPreparedStatement;
import com.alibaba.druid.proxy.jdbc.JdbcParameter;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.apache.ibatis.executor.statement.StatementHandler;
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.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.PreparedStatement;
import java.sql.Statement;
import java.util.Map;
import java.util.Objects;
/**
* <p>Title : MyBatisInterceptor</p>
* <p>Description : MyBatisInterceptor</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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})
})
public class MyBatisInterceptor implements Interceptor {
/**
* 查询方法名
**/
public final static String QUERY = "query";
/**
* 更新方法名
**/
public final static String UPDATE = "update";
@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 {
// 获取当前线程日志信息
LogTrack logTrack = LogTrackHolder.get();
// 结束时间
long endTime = System.currentTimeMillis();
// 执行时间
long execTime = endTime - startTime;
// 执行结果信息
final Object execResult = result;
// 转换参数
Throwable ft = err;
// 处理SQL日志
VirtualThreadTaskUtils.simple(() -> {
// 获取运行参数
Object runArg = invocation.getArgs()[0];
// 读写标识
String rw = QUERY.equals(invocation.getMethod().getName()) ? "" : "";
// 日志信息
JSONObject logData = JSONObject.of(
"startTime", InstantUtils.format(InstantUtils.from(startTime), TimePattern.ALL),
"execTime", execTime,
"result", execResult,
"rw", rw
);
// 是否为慢SQL
logData.put("slowSql", execTime >= conf.getSlowTime());
// 日志分析
this.analysisLog(logTrack, logData, runArg, ft);
});
}
}
/**
* 日志分析
*
* @param logTrack 日志信息
* @param log 日志信息
* @param runArg 运行参数
* @param t 异常
*/
private void analysisLog(LogTrack logTrack, JSONObject log, Object runArg, Throwable t) {
try {
// 获取sql信息
JSONObject sqlInfo = this.getSqlInfo(runArg, logTrack, log);
if (CollectionUtils.isNotEmpty(sqlInfo)) {
log.putAll(sqlInfo);
}
} catch (Exception e) {
t = e;
} finally {
this.execLog(logTrack, log, t);
}
}
/**
* 获取SQL信息
*
* @param runArg 运行参数
* @param log 日志信息
* @return SQL信息
*/
private JSONObject getSqlInfo(Object runArg, LogTrack logTrack, JSONObject log) {
// 执行SQL
String sql;
// 参数
JSONArray params = new JSONArray();
// 个性化参数获取方式
if (DruidPooledPreparedStatement.class.equals(runArg.getClass())) {
// 开启druid监控获取参数方式
DruidPooledPreparedStatement statement = (DruidPooledPreparedStatement) runArg;
sql = statement.getSql();
try {
JSONArray dp = this.getParams(statement);
if (dp != null) {
params = dp;
}
} catch (Exception e) {
this.execLog(logTrack, log, e);
}
} else {
sql = runArg.toString();
// 获取完整SQL
sql = sql.substring(sql.indexOf(":") + 1);
}
// 格式化Sql
sql = sql.replaceAll(",", ", ").replaceAll("\t+", " ").replaceAll("\n+", " ").replaceAll(" +", " ").trim();
// 结果封装
return JSONObject.of("sql", sql, "params", params);
}
/**
* 获取参数
*
* @param statement DruidPooledPreparedStatement
* @return 参数集合
*/
private JSONArray getParams(DruidPooledPreparedStatement statement) {
if (statement == null) {
return null;
}
PreparedStatement ps = statement.getRawStatement();
if (ps == null) {
return null;
}
if (!PreparedStatementProxyImpl.class.equals(ps.getClass())) {
return null;
}
PreparedStatementProxyImpl psp = (PreparedStatementProxyImpl) ps;
int parametersSize = psp.getParametersSize();
if (parametersSize <= 0) {
return null;
}
Map<Integer, JdbcParameter> paramMap = psp.getParameters();
if (CollectionUtils.isEmpty(paramMap)) {
return null;
}
// 参数
JSONArray params = new JSONArray();
paramMap.forEach((key, value) -> params.add(value.getValue()));
return params;
}
/**
* 处理SQL日志
*
* @param logTrack 日志信息
* @param log 日志信息
* @param t 异常
*/
private void execLog(LogTrack logTrack, JSONObject log, Throwable t) {
// 是否为慢SQL
boolean slowSql = log.getBooleanValue("slowSql", false);
// 日志级别
LogLevel level = LogLevel.INFO;
if (t != null) {
level = LogLevel.ERROR;
} else if (slowSql) {
level = LogLevel.WARN;
}
// 获取读写
String rw = log.getString("rw");
log.remove("rw");
LogBus.init(level, LogBusBaseType.MYBATIS, logTrack).title("SQL/" + rw).data(log).error(t).save();
}
}

View File

@@ -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;
/**
* <p>Title : MonitorDatabasesMapper</p>
* <p>Description : MonitorDatabasesMapper</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/14 11:49
*/
@Mapper
public interface MonitorDatabasesMapper {
/**
* 监控MySQL
*
* @param sql SQL语句
* @return 监控数据
*/
@Select({
"<script>",
"${sql}",
"</script>"
})
List<JSONObject> mysql(@Param("sql") String sql);
}

View File

@@ -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;
/**
* <p>Title : MySqlMonitor</p>
* <p>Description : MySqlMonitor</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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<JSONObject> info(MySqlMonitorEnums type) {
if (Objects.isNull(type)) {
throw new BizError("类型参数不能为空");
}
return monitorDatabasesMapper.mysql(type.getSql());
}
}

View File

@@ -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;
/**
* <p>Title : BootDbMybatisImportSelector</p>
* <p>Description : BootDbMybatisImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-elasticsearch</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools begin -->
<!-- xtools-extend 模块 -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-extend</artifactId>
</dependency>
<!-- xtools end -->
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot-log -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-log</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootElasticsearchConfiguration</p>
* <p>Description : BootElasticsearchConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootElasticsearchImportSelector.class)
public class BootElasticsearchConfiguration {
/**
* 构造方法
*/
public BootElasticsearchConfiguration() {
ModuleLoadUtils.loadSuccess(BootElasticsearchConfiguration.class);
}
}

View File

@@ -0,0 +1,126 @@
package xtools.boot.elasticsearch.enums;
import xtools.boot.api.enums.BaseEnum;
/**
* <p>Title : ElasticsearchMonitorEnums</p>
* <p>Description : ElasticsearchMonitorEnums</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,69 @@
package xtools.boot.elasticsearch.monitor;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.stereotype.Component;
import xtools.base.config.BaseParams;
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;
/**
* <p>Title : ElasticsearchMonitor</p>
* <p>Description : ElasticsearchMonitor</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/14 17:58
*/
@Component
public class ElasticsearchMonitor implements BaseParams {
/**
* 获取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.replaceAll("\n", "###").replaceAll("\\s+", " ");
List<String> arr = ArrUtils.toStringList(result, "###");
if (CollectionUtils.isEmpty(arr)) {
throw new BizError("类型参数不能为空");
}
// 封装表头
List<String> titleList = ArrUtils.toStringList(arr.getFirst(), " ");
// 处理主体数据
List<JSONObject> dataList = new ArrayList<>();
for (int i = CP_NUM1; i < arr.size(); i++) {
String line = arr.get(i);
List<String> 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);
}
}

View File

@@ -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;
/**
* <p>Title : BootElasticsearchImportSelector</p>
* <p>Description : BootElasticsearchImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,78 @@
package xtools.boot.elasticsearch.utils;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import xtools.base.config.BaseParams;
import xtools.core.ArrUtils;
import xtools.core.StringUtils;
import java.time.Instant;
import java.util.Objects;
/**
* <p>Title : EsQueryUtils</p>
* <p>Description : EsQueryUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/14 08:58
*/
public class EsQueryUtils implements BaseParams {
/**
* 设置精确查询
*
* @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) {
if (StringUtils.isBlank(params)) {
return;
}
query.add(JSONObject.of("wildcard", JSONObject.of(filedName, "*" + params + "*")));
}
/**
* 时间范围查询
*
* @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))));
}
}

View File

@@ -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;
/**
* <p>Title : EsUtils</p>
* <p>Description : EsUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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 <T> 结果类型
* @param uri 执行的uri
* @param req 请求参数
* @param clazz 结果类型
* @param isGet 是否为get请求
* @return 结果
*/
public static <T> T exec(String uri, JSONObject req, Class<T> 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);
}
}

View File

@@ -0,0 +1 @@
xtools.boot.elasticsearch.BootElasticsearchConfiguration

43
xtools-boot-ip/pom.xml Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-ip</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools begin -->
<!-- xtools-extend 模块 -->
<!-- ip2region(本地IP查询工具) -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-extend</artifactId>
</dependency>
<!-- xtools end -->
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- spring-boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootIpConfiguration</p>
* <p>Description : BootIpConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootIpImportSelector.class)
public class BootIpConfiguration {
/**
* 构造方法
*/
public BootIpConfiguration() {
ModuleLoadUtils.loadSuccess(BootIpConfiguration.class);
}
}

View File

@@ -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.BaseParams;
import xtools.boot.ip.utils.IpUtils;
import xtools.extend.IpLocalUtils;
import java.io.IOException;
import java.io.InputStream;
/**
* <p>Title : InitIp</p>
* <p>Description : InitIp</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/16 10:21
*/
@Component
@Order(BaseParams.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();
}
}
}

View File

@@ -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;
/**
* <p>Title : BootIpImportSelector</p>
* <p>Description : BootIpImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -0,0 +1,83 @@
package xtools.boot.ip.utils;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.core.StringUtils;
import xtools.extend.IpLocalUtils;
import xtools.extend.dto.IpAddrDto;
import java.util.Objects;
import java.util.StringJoiner;
/**
* <p>Title : IpUtils</p>
* <p>Description : IpUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/16 10:41
*/
public class IpUtils implements BaseParams {
/**
* 是否初始化
*/
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, false);
}
/**
* 获取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();
}
}

View File

@@ -0,0 +1 @@
xtools.boot.ip.BootIpConfiguration

Binary file not shown.

19
xtools-boot-job/pom.xml Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<packaging>pom</packaging>
<artifactId>xtools-boot-job</artifactId>
<!-- 子模块 -->
<modules>
<module>xtools-boot-job-xxl</module>
</modules>
</project>

View File

@@ -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://devmysql.xujun.org:11010/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \
--spring.datasource.username=demo \
--spring.datasource.password=demo321 \
--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
```

View File

@@ -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

View File

@@ -0,0 +1,2 @@
# xxl-job-admin配置
PARAMS=--spring.datasource.url=jdbc:mysql://devmysql.xujun.org:11010/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=demo --spring.datasource.password=demo321 --xxl.job.accessToken=xtools-app-xxl-job

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-job</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-job-xxl</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- xxl-job -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<!-- spring-boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootXxlJobConfiguration</p>
* <p>Description : BootXxlJobConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootXxlJobImportSelector.class)
public class BootXxlJobConfiguration {
/**
* 构造方法
*/
public BootXxlJobConfiguration() {
ModuleLoadUtils.loadSuccess(BootXxlJobConfiguration.class);
}
}

View File

@@ -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;
/**
* <p>Title : XxlJobConfig</p>
* <p>Description : XxlJobConfig</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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;
}
}

View File

@@ -0,0 +1,97 @@
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.BaseParams;
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;
/**
* <p>Title : InitIp</p>
* <p>Description : InitIp</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/2/16 10:21
*/
@Slf4j
@Component
@RequiredArgsConstructor
@Order(BaseParams.CP_NUM90)
public class InitXxlJob implements ApplicationRunner, BaseParams {
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<JobInterface> 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.registryJobHandler(name, handler);
}
}

View File

@@ -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;
/**
* <p>Title : BootXxlJobImportSelector</p>
* <p>Description : BootXxlJobImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

View File

@@ -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: ""

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot</artifactId>
<version>5.0.0</version>
</parent>
<artifactId>xtools-boot-knife4j</artifactId>
<!-- 项目依赖 -->
<dependencies>
<!-- xtools-boot begin -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot end -->
<!-- SpringBoot begin -->
<!-- springdoc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<!-- SpringBoot end -->
</dependencies>
</project>

View File

@@ -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;
/**
* <p>Title : BootKnife4jConfiguration</p>
* <p>Description : BootKnife4jConfiguration</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/01/01 09:30
*/
@Import(BootKnife4jImportSelector.class)
public class BootKnife4jConfiguration {
/**
* 构造方法
*/
public BootKnife4jConfiguration() {
ModuleLoadUtils.loadSuccess(BootKnife4jConfiguration.class);
}
}

View File

@@ -0,0 +1,35 @@
package xtools.boot.knife4j.config;
import org.springframework.stereotype.Component;
import xtools.boot.core.interfaces.FilterWhitelist;
import java.util.Set;
/**
* <p>Title : Knife4jFilterWhitelist</p>
* <p>Description : Knife4jFilterWhitelist</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 5.0.0
* @date : 2026/3/8 12:28
*/
@Component
public class Knife4jFilterWhitelist implements FilterWhitelist {
/**
* 添加过滤器白名单
*
* @return 过滤器白名单
*/
@Override
public Set<String> add() {
return Set.of(
"/doc.html",
"/webjars/**",
"/v3/api-docs/**"
);
}
}

View File

@@ -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;
/**
* <p>Title : BootKnife4jImportSelector</p>
* <p>Description : BootKnife4jImportSelector</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @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");
}
}

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