初始化项目

This commit is contained in:
2026-08-06 15:19:45 +08:00
commit 63c8d8b757
229 changed files with 12653 additions and 0 deletions
@@ -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);
}
}
@@ -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;
}
@@ -0,0 +1,116 @@
package xtools.boot.db.mybatis.enums;
import lombok.Getter;
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"),
// 表信息
TABLE(1000, "表信息", "SHOW TABLE STATUS"),
// 需要权限
// // 查看正在进行中的事务
// INNODB_TRX(100, "查看正在进行中的事务", "SELECT * FROM information_schema.INNODB_TRX"),
// // 查看正在锁的事务
// INNODB_LOCKS(101, "查看正在锁的事务", "SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS"),
// // 查看等待锁的事务
// INNODB_LOCK_WAITS(102, "查看等待锁的事务", "SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS"),
// // 查询是否锁表
// LOCK_TABLES(103, "查询是否锁表", "SHOW OPEN TABLES where In_use > 0"),
// // 查看最近死锁的日志
// INNODB_STATUS(104, "查看最近死锁的日志", "show engine innodb status"),
;
/**
* 编码
*/
private final int code;
/**
* 说明
*/
private final String desc;
/**
* SQL语句
*/
@Getter
private final String sql;
/**
* 初始化方法
*
* @param code Code
* @param desc 说明
* @param sql SQL
*/
MySqlMonitorEnums(int code, String desc, String sql) {
this.code = code;
this.desc = desc;
this.sql = sql;
}
/**
* 判断枚举值类型
*
* @param code 枚举值
* @return 枚举值类型
*/
public static MySqlMonitorEnums valueOf(int code) {
for (MySqlMonitorEnums type : values()) {
if (type.code == code) {
return type;
}
}
throw new IllegalArgumentException("unknown code, code=" + code);
}
/**
* 获取所有枚举
*
* @return 所有枚举
*/
@Override
public BaseEnum[] all() {
return values();
}
/**
* 获取枚举编码
*
* @return 枚举编码
*/
@Override
public int code() {
return code;
}
/**
* 获取枚举说明
*
* @return 枚举说明
*/
@Override
public String desc() {
return desc;
}
}
@@ -0,0 +1,214 @@
package xtools.boot.db.mybatis.interceptor;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import xtools.boot.api.model.dto.log.LogTrack;
import xtools.boot.db.mybatis.config.MyBatisConfig;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.boot.log.holder.LogTrackHolder;
import xtools.boot.thread.utils.VirtualThreadTaskUtils;
import xtools.core.CollectionUtils;
import xtools.core.enums.LogLevel;
import xtools.core.enums.TimePattern;
import xtools.core.time.InstantUtils;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static xtools.base.config.Constants.CP_COLON;
import static xtools.base.config.Constants.CP_COMMA;
import static xtools.base.config.Constants.CP_NUM0;
import static xtools.base.config.Constants.CP_NUM1;
import static xtools.base.config.Constants.CP_SPACE;
/**
* <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}),
@Signature(type = StatementHandler.class, method = MyBatisInterceptor.BATCH, args = {Statement.class})
})
public class MyBatisInterceptor implements Interceptor {
/**
* 查询方法名
*/
public final static String QUERY = "query";
/**
* 更新方法名
*/
public final static String UPDATE = "update";
/**
* 批量方法名
*/
public final static String BATCH = "batch";
@Resource
private MyBatisConfig conf;
/**
* 拦截操作
*
* @param invocation 调用参数
* @return 结果
*/
@Override
public Object intercept(@NonNull Invocation invocation) throws Throwable {
LogTrack log = LogTrackHolder.getDefNull();
// 是否启动监控
if (!conf.getMonitor() || Objects.isNull(log) || !log.save()) {
return invocation.proceed();
}
// 执行结果
Object result = null;
// 异常信息
Exception err = null;
// 开始时间
long startTime = System.currentTimeMillis();
try {
result = invocation.proceed();
return result;
} catch (Exception e) {
err = e;
throw e;
} finally {
// 结束时间
long endTime = System.currentTimeMillis();
// 获取当前线程日志信息
LogTrack logTrack = LogTrackHolder.get();
// 执行结果信息
final Object execResult = result;
// 执行异常
final Exception exception = err;
// 保存日志
VirtualThreadTaskUtils.simple(() -> saveLog(logTrack, invocation, execResult, exception, startTime, endTime));
}
}
/**
* 保存日志信息
*
* @param logTrack 日志信息
* @param invocation 调用参数
* @param execResult 执行结果
* @param exception 执行异常
* @param startTime 开始时间
* @param endTime 结束时间
*/
private void saveLog(LogTrack logTrack, Invocation invocation, Object execResult, Exception exception, long startTime, long endTime) {
// 获取运行参数
Object runArg = invocation.getArgs()[CP_NUM0];
// 读写标识
String rw = QUERY.equals(invocation.getMethod().getName()) ? "" : "";
// 执行时间
long execTime = endTime - startTime;
// 是否为慢SQL
boolean slowSql = execTime >= conf.getSlowTime();
// SQL语句
String sql;
// SQL参数
List<Object> params = null;
if (invocation.getTarget() instanceof StatementHandler statementHandler) {
BoundSql boundSql = statementHandler.getBoundSql();
sql = boundSql.getSql();
params = getBoundSqlParams(boundSql);
} else {
sql = runArg.toString();
// 获取完整SQL
sql = sql.substring(sql.indexOf(CP_COLON) + CP_NUM1);
}
// 格式化Sql
sql = sql
.replace(CP_COMMA, ", ")
.replaceAll("\t+", CP_SPACE)
.replaceAll("\n+", CP_SPACE)
.replaceAll(" +", CP_SPACE)
.trim();
// 分装日志信息
JSONObject logData = JSONObject.of(
"startTime", InstantUtils.format(InstantUtils.from(startTime), TimePattern.ALL),
"execTime", execTime,
"result", execResult,
"slowSql", slowSql,
"sql", sql,
"params", params
);
// 日志级别
LogLevel level = LogLevel.INFO;
if (Objects.nonNull(exception)) {
level = LogLevel.ERROR;
} else if (slowSql) {
level = LogLevel.WARN;
}
LogBus.init(level, LogBusBaseType.MYBATIS, logTrack).title("SQL/" + rw).data(logData).error(exception).save();
}
/**
* 从BoundSql中获取参数值
*
* @param boundSql MyBatis的BoundSql
* @return 参数集合
*/
private List<Object> getBoundSqlParams(BoundSql boundSql) {
if (Objects.isNull(boundSql)) {
return null;
}
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (CollectionUtils.isEmpty(parameterMappings)) {
return null;
}
Object parameterObject = boundSql.getParameterObject();
Map<String, Object> additionalParams = boundSql.getAdditionalParameters();
List<Object> params = new ArrayList<>(parameterMappings.size());
MetaObject metaObject = Objects.isNull(parameterObject) ? null : SystemMetaObject.forObject(parameterObject);
for (ParameterMapping mapping : parameterMappings) {
String property = mapping.getProperty();
Object value;
// 优先从additionalParameters获取(MyBatis-Plus分页参数等动态参数在这里)
if (additionalParams.containsKey(property)) {
value = additionalParams.get(property);
} else if (Objects.nonNull(metaObject) && metaObject.hasGetter(property)) {
value = metaObject.getValue(property);
} else if (parameterObject instanceof Map<?, ?> map) {
try {
value = map.get(property);
} catch (Exception e) {
value = "[无法解析:" + property + "]";
}
} else {
value = parameterObject;
}
params.add(value);
}
return params;
}
}
@@ -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);
}
@@ -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());
}
}
@@ -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");
}
}