20260510215412
This commit is contained in:
1 parent
85bb38e99b
commit
56f5ba4e6b
12 files changed
+167
-108
No files matched your search
+86
-48
@@ -1,24 +1,33 @@
|
||||
package cn.g3soft.config.datasource;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import cn.g3soft.context.DataSourceContextHolder;
|
||||
import cn.g3soft.utils.SpringUtils;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.InputStream;
|
||||
import java.sql.Connection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
|
||||
private static final String CONFIG_PATH = "dbconfigs/";
|
||||
private static final Pattern ORG_CODE_PATTERN = Pattern.compile("^[A-Za-z0-9_-]+$");
|
||||
private static final String VALIDATION_QUERY = "SELECT 1";
|
||||
private static volatile DataSourceManager routingDataSource;
|
||||
|
||||
private static final Map<String, DataSource> dataSources = new ConcurrentHashMap<>();
|
||||
|
||||
public DataSourceManager() {
|
||||
setTargetDataSources(new ConcurrentHashMap<>(dataSources));
|
||||
routingDataSource = this;
|
||||
setTargetDataSources(toTargetDataSources());
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
@@ -27,22 +36,13 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
return DataSourceContextHolder.getDataSourceKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接(带异常保护)
|
||||
*/
|
||||
public static Connection getConnection(String orgCode) throws Exception {
|
||||
try {
|
||||
DataSource dataSource = getOrCreateDataSource(orgCode);
|
||||
return dataSource.getConnection();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("获取数据库连接失败: " + orgCode, e);
|
||||
}
|
||||
DataSource ds = getOrCreateDataSource(orgCode);
|
||||
return ds.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建数据源
|
||||
*/
|
||||
public static DataSource getOrCreateDataSource(String orgCode) throws Exception {
|
||||
validateOrgCode(orgCode);
|
||||
|
||||
DataSource ds = dataSources.get(orgCode);
|
||||
if (ds != null) {
|
||||
@@ -50,47 +50,37 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
}
|
||||
|
||||
synchronized (DataSourceManager.class) {
|
||||
|
||||
ds = dataSources.get(orgCode);
|
||||
if (ds != null) {
|
||||
return ds;
|
||||
}
|
||||
|
||||
// 创建数据源
|
||||
ds = createDataSource(orgCode);
|
||||
|
||||
// ===== 关键:先验证连接,防止缓存坏数据源 =====
|
||||
try (Connection conn = ds.getConnection()) {
|
||||
// 成功才缓存
|
||||
dataSources.put(orgCode, ds);
|
||||
} catch (Exception e) {
|
||||
if (ds instanceof DruidDataSource) {
|
||||
((DruidDataSource) ds).close();
|
||||
}
|
||||
closeQuietly(ds);
|
||||
throw new RuntimeException("数据库连接失败: " + orgCode, e);
|
||||
}
|
||||
|
||||
// 刷新 routing datasource
|
||||
refresh();
|
||||
|
||||
log.info("Loaded datasource for orgCode: {}", orgCode);
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 routing datasource
|
||||
*/
|
||||
private static void refresh() {
|
||||
DataSourceManager routing = SpringUtils.getBean(DataSourceManager.class);
|
||||
routing.setTargetDataSources(new ConcurrentHashMap<>(dataSources));
|
||||
DataSourceManager routing = routingDataSource;
|
||||
if (routing == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
routing.setTargetDataSources(toTargetDataSources());
|
||||
routing.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据源(已优化:防止无限重连/卡死)
|
||||
*/
|
||||
private static DataSource createDataSource(String orgCode) throws Exception {
|
||||
|
||||
String file = CONFIG_PATH + orgCode + ".properties";
|
||||
|
||||
try (InputStream in = DataSourceManager.class
|
||||
@@ -98,39 +88,87 @@ public class DataSourceManager extends AbstractRoutingDataSource {
|
||||
.getResourceAsStream(file)) {
|
||||
|
||||
if (in == null) {
|
||||
throw new RuntimeException("未找到配置文件: " + file);
|
||||
throw new RuntimeException("未找到数据源配置文件: " + file);
|
||||
}
|
||||
|
||||
Properties p = new Properties();
|
||||
p.load(in);
|
||||
requireProperty(p, "url", file);
|
||||
requireProperty(p, "username", file);
|
||||
requireProperty(p, "password", file);
|
||||
requireProperty(p, "driver", file);
|
||||
|
||||
DruidDataSource ds = new DruidDataSource();
|
||||
|
||||
ds.setName(orgCode);
|
||||
ds.setUrl(p.getProperty("url"));
|
||||
ds.setUsername(p.getProperty("username"));
|
||||
ds.setPassword(p.getProperty("password"));
|
||||
ds.setDriverClassName(p.getProperty("driver"));
|
||||
|
||||
// ===== 基础连接池配置 =====
|
||||
ds.setInitialSize(1);
|
||||
ds.setMaxActive(10);
|
||||
ds.setMinIdle(1);
|
||||
ds.setMaxActive(10);
|
||||
ds.setMaxWait(3000);
|
||||
|
||||
// ===== 核心:防止卡死 / 无限重试 =====
|
||||
ds.setMaxWait(3000); // 获取连接最大等待3秒
|
||||
ds.setBreakAfterAcquireFailure(true); // 获取失败直接抛异常
|
||||
ds.setConnectionErrorRetryAttempts(0); // 不重试
|
||||
ds.setFailFast(true); // 初始化时检测连接
|
||||
ds.setBreakAfterAcquireFailure(true);
|
||||
ds.setConnectionErrorRetryAttempts(0);
|
||||
ds.setFailFast(true);
|
||||
|
||||
// ===== 连接校验 =====
|
||||
ds.setValidationQuery("SELECT 1");
|
||||
ds.setTestOnBorrow(true);
|
||||
ds.setTestWhileIdle(false);
|
||||
ds.setValidationQuery(VALIDATION_QUERY);
|
||||
|
||||
ds.setTestOnBorrow(false);
|
||||
ds.setTestOnReturn(false);
|
||||
ds.setTestWhileIdle(true);
|
||||
|
||||
ds.setTimeBetweenEvictionRunsMillis(60000);
|
||||
ds.setMinEvictableIdleTimeMillis(300000);
|
||||
|
||||
ds.setKeepAlive(false);
|
||||
|
||||
ds.setPoolPreparedStatements(false);
|
||||
|
||||
ds.setFilters("stat,slf4j");
|
||||
ds.setUseGlobalDataSourceStat(true);
|
||||
ds.setConnectionProperties(
|
||||
"druid.stat.mergeSql=true;" +
|
||||
"druid.stat.slowSqlMillis=3000;" +
|
||||
"druid.stat.logSlowSql=true"
|
||||
);
|
||||
|
||||
ds.setRemoveAbandoned(false);
|
||||
|
||||
// 初始化(此时若连接失败会快速抛异常)
|
||||
ds.init();
|
||||
|
||||
return ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Object, Object> toTargetDataSources() {
|
||||
return new HashMap<>(dataSources);
|
||||
}
|
||||
|
||||
private static void validateOrgCode(String orgCode) {
|
||||
if (StringUtils.isBlank(orgCode)) {
|
||||
throw new IllegalArgumentException("机构码不能为空");
|
||||
}
|
||||
if (!ORG_CODE_PATTERN.matcher(orgCode).matches()) {
|
||||
throw new IllegalArgumentException("机构码只能包含字母、数字、下划线和中划线: " + orgCode);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireProperty(Properties properties, String key, String file) {
|
||||
if (StringUtils.isBlank(properties.getProperty(key))) {
|
||||
throw new IllegalArgumentException("数据源配置缺少必要项 " + key + ": " + file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeQuietly(DataSource dataSource) {
|
||||
if (dataSource instanceof DruidDataSource) {
|
||||
try {
|
||||
((DruidDataSource) dataSource).close();
|
||||
} catch (Exception e) {
|
||||
log.warn("Close datasource failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-34
@@ -7,64 +7,77 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class DruidConfig {
|
||||
|
||||
// ===================== 固定配置 =====================
|
||||
|
||||
private static final boolean ENABLED = true;
|
||||
|
||||
private static final String URL_PATTERN = "/druid/*";
|
||||
|
||||
private static final String LOGIN_USERNAME = "admin";
|
||||
|
||||
private static final String LOGIN_PASSWORD = "admin";
|
||||
|
||||
// 只允许本机访问(生产环境建议改内网IP)
|
||||
private static final String ALLOW = "127.0.0.1";
|
||||
|
||||
private static final String DENY = "";
|
||||
|
||||
private static final boolean RESET_ENABLE = false;
|
||||
|
||||
private static final String EXCLUSIONS =
|
||||
"*.js,*.gif,*.jpg,*.jpeg,*.png,*.css,*.ico,*.map,/druid/*";
|
||||
|
||||
// ===================================================
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
|
||||
// 监控信息显示页面
|
||||
StatViewServlet statViewServlet = new StatViewServlet();
|
||||
// 访问监控信息显示页面的 url 路径(建议值为 /druid/* )
|
||||
String urlPattern = "/druid/*";
|
||||
|
||||
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(statViewServlet, urlPattern);
|
||||
StatViewServlet servlet = new StatViewServlet();
|
||||
ServletRegistrationBean<StatViewServlet> bean =
|
||||
new ServletRegistrationBean<>(servlet, URL_PATTERN);
|
||||
|
||||
Map<String, String> initParams = new HashMap<>();
|
||||
// 是否允许清空统计数据
|
||||
initParams.put("resetEnable", "false");
|
||||
// 登录监控信息显示页面的用户名
|
||||
initParams.put("loginUsername", "admin");
|
||||
// 登录监控信息显示页面的密码
|
||||
initParams.put("loginPassword", "admin");
|
||||
// 允许访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
|
||||
initParams.put("allow", "127.0.0.1");
|
||||
// 拒绝访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
|
||||
// initParams.put("deny", "");
|
||||
initParams.put("resetEnable", String.valueOf(RESET_ENABLE));
|
||||
initParams.put("loginUsername", LOGIN_USERNAME);
|
||||
initParams.put("loginPassword", LOGIN_PASSWORD);
|
||||
initParams.put("allow", ALLOW);
|
||||
|
||||
if (DENY != null && !DENY.isEmpty()) {
|
||||
initParams.put("deny", DENY);
|
||||
}
|
||||
|
||||
bean.setInitParameters(initParams);
|
||||
bean.setEnabled(ENABLED);
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
|
||||
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
|
||||
// 网络监控过滤器(用于采集 web-jdbc 关联监控的数据)
|
||||
|
||||
FilterRegistrationBean<WebStatFilter> bean =
|
||||
new FilterRegistrationBean<>();
|
||||
|
||||
bean.setFilter(new WebStatFilter());
|
||||
// 过滤所有的 url 路径
|
||||
Collection<String> urlPatterns = new ArrayList<>();
|
||||
urlPatterns.add("/*");
|
||||
bean.setUrlPatterns(urlPatterns);
|
||||
bean.setUrlPatterns(Collections.singletonList("/*"));
|
||||
bean.setOrder(2);
|
||||
|
||||
Map<String, String> initParams = new HashMap<>();
|
||||
// 排除不必要采集的 url 路径,以逗号“,”分割
|
||||
initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.map,/druid/*");
|
||||
// 是否使用 session 监控功能
|
||||
initParams.put("exclusions", EXCLUSIONS);
|
||||
initParams.put("sessionStatEnable", "true");
|
||||
// 是否使用 session 监控最大数量(默认是1000)
|
||||
initParams.put("sessionStatMaxCount", "1000");
|
||||
// 使得 druid 能够知道当前的 session 的用户是谁,根据需要,把改值修改为你 user 信息保存在 session 中的 sessionName
|
||||
initParams.put("principalSessionName", "session_user_key");
|
||||
// 如果你的 user 信息保存在 cookie 中,你可以配置 principalCookieName,使得 druid 知道当前的 user 是谁,根据需要,把该值修改为你 user 信息保存在 cookie 中的 cookieName
|
||||
initParams.put("principalCookieName", "cookie_user_key");
|
||||
// 是否监控单个 url 调用的 sql 列表
|
||||
initParams.put("profileEnable", "true");
|
||||
|
||||
bean.setInitParameters(initParams);
|
||||
bean.setEnabled(ENABLED);
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
server:
|
||||
servlet:
|
||||
context-path: / # 应用的访问路径
|
||||
port: 8080
|
||||
|
||||
file:
|
||||
server-url: http://ser369.g3soft.cn/api
|
||||
@@ -0,0 +1,7 @@
|
||||
server:
|
||||
servlet:
|
||||
context-path: /api/ # 应用的访问路径
|
||||
port: 80
|
||||
|
||||
file:
|
||||
server-url: http://192.168.5.6:80/api
|
||||
@@ -1,13 +1,14 @@
|
||||
server:
|
||||
servlet:
|
||||
context-path: / # 应用的访问路径
|
||||
# context-path: / # 应用的访问路径
|
||||
# context-path: /api/ # 应用的访问路径
|
||||
tomcat:
|
||||
uri-encoding: UTF-8 # URI编码
|
||||
accept-count: 1000 # 连接数满后的排队数
|
||||
threads:
|
||||
max: 800 # 最大线程数
|
||||
min-spare: 100 # 初始化的线程数
|
||||
port: 8080
|
||||
# port: 80
|
||||
|
||||
spring:
|
||||
servlet:
|
||||
@@ -17,13 +18,15 @@ spring:
|
||||
jackson:
|
||||
time-zone: "Asia/Shanghai"
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
file:
|
||||
base-dir: D:/data/file
|
||||
|
||||
static-path: /static/file
|
||||
# server-url: http://192.168.1.137:80/api
|
||||
server-url: http://ser369.g3soft.cn/api
|
||||
# server-url: http://192.168.5.6:80/api
|
||||
# server-url: http://ser369.g3soft.cn/api
|
||||
common-dir: common
|
||||
container-dir: container
|
||||
temp-dir: temp
|
||||
@@ -1,10 +1,10 @@
|
||||
#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
|
||||
#username=sa
|
||||
#password=zhang520..
|
||||
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR
|
||||
url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR;encrypt=false
|
||||
username=sa
|
||||
password=123456@aaa
|
||||
password=zhang520..
|
||||
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR
|
||||
#username=sa
|
||||
#password=123456@aaa
|
||||
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
Reference in new issue
Block a user