Initial commit

This commit is contained in:
Eric
2026-01-16 18:51:16 +08:00
commit 98c057de11
280 changed files with 16665 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
</parent>
<artifactId>lingniu-framework-plugin-mybatis</artifactId>
<name>lingniu-framework-plugin-mybatis</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<tk.mapper-spring-boot.version>4.2.0</tk.mapper-spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-plugin-core</artifactId>
</dependency>
<dependency>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-plugin-util</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>provided</scope>
</dependency>
<!--必须依赖-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-datasource-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>ojdbc</groupId>
<artifactId>ojdbc6</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${tk.mapper-spring-boot.version}</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,181 @@
package cn.lingniu.framework.plugin.mybatis.base;
import cn.lingniu.framework.plugin.mybatis.config.DataSourceConfig;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
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 java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* 输出SQL 语句及其执行时间
*/
@Slf4j
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = Statement.class),
@Signature(type = StatementHandler.class, method = "batch", args = Statement.class)
})
@RequiredArgsConstructor
public class DBSqlLogInterceptor implements Interceptor {
private static final String DRUID_POOLED_PREPARED_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
private static final String T4C_PREPARED_STATEMENT = "oracle.jdbc.driver.T4CPreparedStatement";
private static final String ORACLE_PREPARED_STATEMENT_WRAPPER = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
private Method oracleGetOriginalSqlMethod;
private Method druidGetSqlMethod;
private final DataSourceConfig dataSourceConfig;
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (!dataSourceConfig.isSqlLog()) {
return invocation.proceed();
}
Statement statement;
Object firstArg = invocation.getArgs()[0];
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
try {
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
} catch (Exception e) {
// do nothing
}
if (stmtMetaObj.hasGetter("delegate")) {//Hikari
try {
statement = (Statement) stmtMetaObj.getValue("delegate");
} catch (Exception ignored) {
// do nothing
}
}
String originalSql = null;
String stmtClassName = statement.getClass().getName();
if (DRUID_POOLED_PREPARED_STATEMENT.equals(stmtClassName)) {
try {
if (druidGetSqlMethod == null) {
Class<?> clazz = Class.forName(DRUID_POOLED_PREPARED_STATEMENT);
druidGetSqlMethod = clazz.getMethod("getSql");
}
Object stmtSql = druidGetSqlMethod.invoke(statement);
if (stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (T4C_PREPARED_STATEMENT.equals(stmtClassName)
|| ORACLE_PREPARED_STATEMENT_WRAPPER.equals(stmtClassName)) {
try {
if (oracleGetOriginalSqlMethod != null) {
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
if (stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
} else {
Class<?> clazz = Class.forName(stmtClassName);
oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
if (oracleGetOriginalSqlMethod != null) {
//OraclePreparedStatementWrapper is not a public class, need set this.
oracleGetOriginalSqlMethod.setAccessible(true);
if (null != oracleGetOriginalSqlMethod) {
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
if (stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
}
}
}
} catch (Exception e) {
//ignore
}
}
if (originalSql == null) {
originalSql = statement.toString();
}
originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE);
int index = indexOfSqlStart(originalSql);
if (index > 0) {
originalSql = originalSql.substring(index);
}
long start = SystemClock.now();
Object result = invocation.proceed();
long timing = SystemClock.now() - start;
Object target = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(target);
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 打印 sql
String sqlLogger = "\nsql日志执行日志开始==============" +
"\nExecute ID {}" +
"\nExecute SQL {}" +
"\nExecute Time{} ms" +
"\nsql日志执行日志结束 ==============\n";
if (log.isInfoEnabled()) {
log.info(sqlLogger, ms.getId(), originalSql, timing);
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
private Method getMethodRegular(Class<?> clazz, String methodName) {
if (Object.class.equals(clazz)) {
return null;
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
return method;
}
}
return getMethodRegular(clazz.getSuperclass(), methodName);
}
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet<>();
set.add(upperCaseSql.indexOf("SELECT "));
set.add(upperCaseSql.indexOf("UPDATE "));
set.add(upperCaseSql.indexOf("INSERT "));
set.add(upperCaseSql.indexOf("DELETE "));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
}
List<Integer> list = new ArrayList<>(set);
list.sort(Comparator.naturalOrder());
return list.get(0);
}
}

View File

@@ -0,0 +1,22 @@
package cn.lingniu.framework.plugin.mybatis.base;
/**
* 对应于多数据源中不同数据源配置
* <p>
* 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
* 注意,默认是 {@link #MASTER} 数据源
* <p>
* 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
*/
public interface DataSourceEnum {
/**
* 主库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解
*/
String MASTER = "master";
/**
* 从库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解
*/
String SLAVE = "slave";
}

View File

@@ -0,0 +1,36 @@
package cn.lingniu.framework.plugin.mybatis.base;
import com.alibaba.druid.util.Utils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Druid 底部广告过滤器
*
*/
public class DruidAdRemoveFilter extends OncePerRequestFilter {
/**
* common.js 的路径
*/
private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
chain.doFilter(request, response);
// 重置缓冲区,响应头不会被重置
response.resetBuffer();
// 获取 common.js
String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
// 正则替换 banner, 除去底部的广告信息
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
text = text.replaceAll("powered.*?shrek.wang</a>", "");
response.getWriter().write(text);
}
}

View File

@@ -0,0 +1,29 @@
package cn.lingniu.framework.plugin.mybatis.config;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 数据源配置--mybatis, mybatis-plus, dynamic-datasource, tk-mapper 配置参数整合
**/
@Data
@ConfigurationProperties(prefix = DataSourceConfig.PRE_FIX)
public class DataSourceConfig {
public static final String PRE_FIX = "framework.lingniu.datasource";
/**
* sql分析日志打印, 默认关闭
*/
private boolean sqlLog = false;
/**
* druid 账号, 默认 SpringApplicationConfig#name
*/
private String druidUsername;
/**
* druid 密码, 默认 SpringApplicationConfig#name + 123
*/
private String druidPassword;
}

View File

@@ -0,0 +1,67 @@
package cn.lingniu.framework.plugin.mybatis.init;
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
import cn.lingniu.framework.plugin.mybatis.base.DBSqlLogInterceptor;
import cn.lingniu.framework.plugin.mybatis.base.DruidAdRemoveFilter;
import cn.lingniu.framework.plugin.mybatis.config.DataSourceConfig;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.support.http.ResourceServlet;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
@Configuration
@EnableConfigurationProperties({DataSourceConfig.class})
public class DataSourceAutoConfiguration {
@Bean
public DBSqlLogInterceptor sqlLogInterceptor(DataSourceConfig dataSourceConfig) {
return new DBSqlLogInterceptor(dataSourceConfig);
}
@Bean(name = "druidStatView")
public ServletRegistrationBean druidStatView(DataSourceConfig dataSourceConfig) {
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
registrationBean.addInitParameter(ResourceServlet.PARAM_NAME_ALLOW, "");
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_DENY, "");
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_USERNAME, Optional.ofNullable(dataSourceConfig.getDruidUsername()).orElseGet(()-> ApplicationNameContext.getApplicationName()));
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_PASSWORD, Optional.ofNullable(dataSourceConfig.getDruidPassword()).orElseGet(() -> ApplicationNameContext.getApplicationName() + "123"));
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_RESET_ENABLE, "false");
return registrationBean;
}
@Bean(name = "druidWebStatFilter")
public FilterRegistrationBean<WebStatFilter> druidWebStatFilter() {
FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(new WebStatFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
/**
* 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告
*/
@Bean
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
// 获取 druid web 监控页面的参数
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
// 提取 common.js 的配置路径
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
// 创建 DruidAdRemoveFilter Bean
FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DruidAdRemoveFilter());
registrationBean.addUrlPatterns(commonJsPattern);
return registrationBean;
}
}

View File

@@ -0,0 +1,19 @@
package cn.lingniu.framework.plugin.mybatis.init;
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
/**
* 数据源属性初始化--处理监控开关
*/
@Order(Integer.MIN_VALUE + 100)
public class DataSourceInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
PropertyUtils.setDefaultInitProperty("management.health.db.enabled", "false");
}
}

View File

@@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lingniu.framework.plugin.mybatis.init.DataSourceAutoConfiguration
org.springframework.context.ApplicationContextInitializer=\
cn.lingniu.framework.plugin.mybatis.init.DataSourceInit

View File

@@ -0,0 +1,65 @@
# 【重要】基于 Mybatis-Plus + dynamic-datasource + tk 详细使用参考官网---参考官网
## 概述 (Overview)
1. 基于 Mybatis-Plus + dynamic-datasource + tk 封装的数据源交互工具
2. 核心能力
* 数据源CRUD操作支持mysql, oracle, clickhouse 等
* 多数据源支持
* 分页查询
* Sql语句执行分析
* 默认集成druid
3. 适用场景:
* 依赖外部数据源场景
* 关系型数据库场景
## 如何配置--更多参数参考RedissonConfig/RedissonProperties
```yaml
spring:
application:
name: lingniu-framework-demo
profiles:
active: dev,redisson,jetcache,xxljob,db
#todo db配置
datasource:
dynamic:
primary: master
datasource:
#如需连接mysql需修改下面的配置
master:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: sa
slaver:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: sa
# http://localhost:8080/h2-console 访问 h2 数据库 账号: sa/sa
h2:
console:
enabled: true
path: /h2-console
framework:
lingniu:
# 框架数据源配置
datasource:
# 开启框架 sql执行分析
sqlLog: true
# druid 后台地址http://localhost:8080/druid/login.html
# druid 账号,默认值为 SpringApplicationProperties#name
druidUsername: "your_druid_username"
# druid 密码,默认值为 SpringApplicationProperties#name + "123"
druidPassword: "your_druid_password"
```
## druid 配置请参考https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
## 如何使用--Mybatis基本功能
1. 基本功能使用可参考Mybatis-Plus: https://www.baomidou.com
2. 默认配置下 在 src/main/resources/mapper/ 文件夹下定义 xml 文件
3. 定义 java 接口 继承 com.baomidou.mybatisplus.core.mapper.BaseMapper
4. 使用@DS 注解实现数据源切换:@DS("master")