feat: P3任务实施 - Sentinel熔断降级/HikariCP优化/AES加密
## 新增功能 ### D.2 Sentinel熔断降级 - SentinelConfig: 自定义熔断降级响应 - SentinelRuleConfig: 网关流控规则配置 - 添加sentinel依赖到fund-gateway ### F.2 HikariCP连接池优化 - 完善HikariCP配置参数 - HikariMonitorConfig: 连接池监控配置 - 每5分钟打印连接池状态 ### H.3 敏感数据加密 - AESUtils: AES-256-GCM加密工具类 - 支持加密/解密/手机号脱敏/身份证脱敏/银行卡脱敏 - 使用SHA-256生成密钥 ### 单元测试 - AESUtilsTest: 10个测试用例验证加密功能 - 测试覆盖: 加密解密/中文/长文本/错误密钥/多次加密/脱敏
This commit is contained in:
parent
5d4cdd5c33
commit
23c8f81ebd
@ -49,6 +49,13 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
package com.fundplatform.common.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AES 加密工具类
|
||||
* 用于敏感数据加密存储(手机号、身份证号等)
|
||||
*/
|
||||
public class AESUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AESUtils.class);
|
||||
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||
private static final int KEY_SIZE = 256; // AES-256
|
||||
private static final int GCM_IV_LENGTH = 12; // GCM推荐的IV长度
|
||||
private static final int GCM_TAG_LENGTH = 128; // 认证标签长度(位)
|
||||
|
||||
// 默认密钥种子(生产环境应从配置中心或密钥管理服务获取)
|
||||
// 通过SHA-256生成32字节的密钥
|
||||
private static final String DEFAULT_KEY_SEED = "FundPlatform2026SecretKey!@#";
|
||||
private static final byte[] DEFAULT_KEY;
|
||||
|
||||
static {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
DEFAULT_KEY = digest.digest(DEFAULT_KEY_SEED.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("初始化默认密钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AES密钥
|
||||
*/
|
||||
public static String generateKey() {
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
|
||||
keyGenerator.init(KEY_SIZE, new SecureRandom());
|
||||
SecretKey secretKey = keyGenerator.generateKey();
|
||||
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
|
||||
} catch (Exception e) {
|
||||
log.error("生成AES密钥失败", e);
|
||||
throw new RuntimeException("生成AES密钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
* @param plainText 明文
|
||||
* @return Base64编码的密文(包含IV)
|
||||
*/
|
||||
public static String encrypt(String plainText) {
|
||||
return encryptWithKey(plainText, DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
* @param plainText 明文
|
||||
* @param key 密钥(Base64编码)
|
||||
* @return Base64编码的密文(包含IV)
|
||||
*/
|
||||
public static String encrypt(String plainText, String key) {
|
||||
try {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(key);
|
||||
return encryptWithKey(plainText, keyBytes);
|
||||
} catch (Exception e) {
|
||||
log.error("解析密钥失败", e);
|
||||
throw new RuntimeException("解析密钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部加密方法
|
||||
*/
|
||||
private static String encryptWithKey(String plainText, byte[] keyBytes) {
|
||||
try {
|
||||
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
|
||||
|
||||
// 生成随机IV
|
||||
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 将IV和密文合并
|
||||
byte[] encryptedWithIv = new byte[GCM_IV_LENGTH + encrypted.length];
|
||||
System.arraycopy(iv, 0, encryptedWithIv, 0, GCM_IV_LENGTH);
|
||||
System.arraycopy(encrypted, 0, encryptedWithIv, GCM_IV_LENGTH, encrypted.length);
|
||||
|
||||
return Base64.getEncoder().encodeToString(encryptedWithIv);
|
||||
} catch (Exception e) {
|
||||
log.error("AES加密失败", e);
|
||||
throw new RuntimeException("AES加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
* @param encryptedText Base64编码的密文(包含IV)
|
||||
* @return 明文
|
||||
*/
|
||||
public static String decrypt(String encryptedText) {
|
||||
return decryptWithKey(encryptedText, DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
* @param encryptedText Base64编码的密文(包含IV)
|
||||
* @param key 密钥(Base64编码)
|
||||
* @return 明文
|
||||
*/
|
||||
public static String decrypt(String encryptedText, String key) {
|
||||
try {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(key);
|
||||
return decryptWithKey(encryptedText, keyBytes);
|
||||
} catch (Exception e) {
|
||||
log.error("解析密钥失败", e);
|
||||
throw new RuntimeException("解析密钥失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部解密方法
|
||||
*/
|
||||
private static String decryptWithKey(String encryptedText, byte[] keyBytes) {
|
||||
try {
|
||||
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM);
|
||||
|
||||
byte[] decoded = Base64.getDecoder().decode(encryptedText);
|
||||
|
||||
// 分离IV和密文
|
||||
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||
byte[] encrypted = new byte[decoded.length - GCM_IV_LENGTH];
|
||||
System.arraycopy(decoded, 0, iv, 0, GCM_IV_LENGTH);
|
||||
System.arraycopy(decoded, GCM_IV_LENGTH, encrypted, 0, encrypted.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
|
||||
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
return new String(decrypted, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
log.error("AES解密失败", e);
|
||||
throw new RuntimeException("AES解密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号脱敏
|
||||
* @param phone 手机号
|
||||
* @return 脱敏后的手机号 (如: 138****8000)
|
||||
*/
|
||||
public static String maskPhone(String phone) {
|
||||
if (phone == null || phone.length() < 7) {
|
||||
return phone;
|
||||
}
|
||||
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份证号脱敏
|
||||
* @param idCard 身份证号
|
||||
* @return 脱敏后的身份证号 (如: 110***********1234)
|
||||
*/
|
||||
public static String maskIdCard(String idCard) {
|
||||
if (idCard == null || idCard.length() < 8) {
|
||||
return idCard;
|
||||
}
|
||||
return idCard.substring(0, 3) + "***********" + idCard.substring(idCard.length() - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 银行卡号脱敏
|
||||
* @param bankCard 银行卡号
|
||||
* @return 脱敏后的银行卡号 (如: 6222****1234)
|
||||
*/
|
||||
public static String maskBankCard(String bankCard) {
|
||||
if (bankCard == null || bankCard.length() < 8) {
|
||||
return bankCard;
|
||||
}
|
||||
return bankCard.substring(0, 4) + "****" + bankCard.substring(bankCard.length() - 4);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
package com.fundplatform.common.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* AES加密工具类单元测试
|
||||
*/
|
||||
class AESUtilsTest {
|
||||
|
||||
@Test
|
||||
void testGenerateKey() {
|
||||
String key = AESUtils.generateKey();
|
||||
assertNotNull(key);
|
||||
assertTrue(key.length() > 0);
|
||||
System.out.println("生成的密钥: " + key);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptAndDecrypt() {
|
||||
String plainText = "13800138000";
|
||||
|
||||
// 加密
|
||||
String encrypted = AESUtils.encrypt(plainText);
|
||||
assertNotNull(encrypted);
|
||||
assertNotEquals(plainText, encrypted);
|
||||
System.out.println("明文: " + plainText);
|
||||
System.out.println("密文: " + encrypted);
|
||||
|
||||
// 解密
|
||||
String decrypted = AESUtils.decrypt(encrypted);
|
||||
assertEquals(plainText, decrypted);
|
||||
System.out.println("解密后: " + decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptAndDecryptWithCustomKey() {
|
||||
String plainText = "320123199001011234";
|
||||
String key = AESUtils.generateKey();
|
||||
|
||||
// 使用自定义密钥加密
|
||||
String encrypted = AESUtils.encrypt(plainText, key);
|
||||
assertNotNull(encrypted);
|
||||
|
||||
// 使用自定义密钥解密
|
||||
String decrypted = AESUtils.decrypt(encrypted, key);
|
||||
assertEquals(plainText, decrypted);
|
||||
System.out.println("身份证加密测试通过: " + plainText + " -> " + encrypted + " -> " + decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptAndDecryptChinese() {
|
||||
String plainText = "张三丰";
|
||||
|
||||
String encrypted = AESUtils.encrypt(plainText);
|
||||
String decrypted = AESUtils.decrypt(encrypted);
|
||||
|
||||
assertEquals(plainText, decrypted);
|
||||
System.out.println("中文加密测试通过: " + plainText + " -> " + encrypted + " -> " + decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptAndDecryptLongText() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sb.append("测试数据");
|
||||
}
|
||||
String plainText = sb.toString();
|
||||
|
||||
String encrypted = AESUtils.encrypt(plainText);
|
||||
String decrypted = AESUtils.decrypt(encrypted);
|
||||
|
||||
assertEquals(plainText, decrypted);
|
||||
System.out.println("长文本加密测试通过, 明文长度: " + plainText.length() + ", 密文长度: " + encrypted.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaskPhone() {
|
||||
// 正常手机号(11位)
|
||||
assertEquals("138****8000", AESUtils.maskPhone("13800138000"));
|
||||
|
||||
// 短号码(小于7位)不脱敏
|
||||
assertEquals("123456", AESUtils.maskPhone("123456"));
|
||||
|
||||
// null
|
||||
assertNull(AESUtils.maskPhone(null));
|
||||
|
||||
// 7位号码(前3位+****+后4位)
|
||||
assertEquals("123****4567", AESUtils.maskPhone("1234567"));
|
||||
|
||||
System.out.println("手机号脱敏测试通过: 13800138000 -> " + AESUtils.maskPhone("13800138000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaskIdCard() {
|
||||
// 正常身份证号(18位)
|
||||
assertEquals("320***********1234", AESUtils.maskIdCard("320123199001011234"));
|
||||
|
||||
// 短号码(小于8位)直接返回原值
|
||||
assertEquals("1234567", AESUtils.maskIdCard("1234567"));
|
||||
|
||||
// null
|
||||
assertNull(AESUtils.maskIdCard(null));
|
||||
|
||||
System.out.println("身份证脱敏测试通过: 320123199001011234 -> " + AESUtils.maskIdCard("320123199001011234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMaskBankCard() {
|
||||
// 正常银行卡号
|
||||
assertEquals("6222****1234", AESUtils.maskBankCard("62220212345678901234"));
|
||||
|
||||
// 短卡号(小于8位)直接返回原值
|
||||
assertEquals("1234567", AESUtils.maskBankCard("1234567"));
|
||||
|
||||
// null
|
||||
assertNull(AESUtils.maskBankCard(null));
|
||||
|
||||
// 8位卡号(前4位+****+后4位)
|
||||
assertEquals("1234****5678", AESUtils.maskBankCard("12345678"));
|
||||
|
||||
System.out.println("银行卡脱敏测试通过: 62220212345678901234 -> " + AESUtils.maskBankCard("62220212345678901234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecryptWithWrongKey() {
|
||||
String plainText = "敏感数据";
|
||||
String key1 = AESUtils.generateKey();
|
||||
String key2 = AESUtils.generateKey();
|
||||
|
||||
String encrypted = AESUtils.encrypt(plainText, key1);
|
||||
|
||||
// 使用错误的密钥解密应该失败
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
AESUtils.decrypt(encrypted, key2);
|
||||
});
|
||||
|
||||
System.out.println("错误密钥解密测试通过: 使用错误密钥解密时抛出异常");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleEncryptionsAreDifferent() {
|
||||
String plainText = "相同的数据";
|
||||
|
||||
// 多次加密同一明文,密文应该不同(因为使用了随机IV)
|
||||
String encrypted1 = AESUtils.encrypt(plainText);
|
||||
String encrypted2 = AESUtils.encrypt(plainText);
|
||||
String encrypted3 = AESUtils.encrypt(plainText);
|
||||
|
||||
assertNotEquals(encrypted1, encrypted2);
|
||||
assertNotEquals(encrypted2, encrypted3);
|
||||
|
||||
// 但都能正确解密
|
||||
assertEquals(plainText, AESUtils.decrypt(encrypted1));
|
||||
assertEquals(plainText, AESUtils.decrypt(encrypted2));
|
||||
assertEquals(plainText, AESUtils.decrypt(encrypted3));
|
||||
|
||||
System.out.println("多次加密生成不同密文测试通过");
|
||||
}
|
||||
}
|
||||
@ -59,6 +59,16 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sentinel 熔断降级 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package com.fundplatform.gateway.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sentinel 熔断降级配置
|
||||
*/
|
||||
@Configuration
|
||||
public class SentinelConfig {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 自定义熔断降级响应
|
||||
GatewayCallbackManager.setBlockHandler((exchange, t) -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 429);
|
||||
result.put("message", "服务繁忙,请稍后重试");
|
||||
result.put("success", false);
|
||||
result.put("data", null);
|
||||
|
||||
String body;
|
||||
try {
|
||||
body = objectMapper.writeValueAsString(result);
|
||||
} catch (JsonProcessingException e) {
|
||||
body = "{\"code\":429,\"message\":\"服务繁忙,请稍后重试\",\"success\":false}";
|
||||
}
|
||||
|
||||
return ServerResponse
|
||||
.status(HttpStatus.TOO_MANY_REQUESTS)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(body);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package com.fundplatform.gateway.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
|
||||
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Sentinel 网关流控规则配置
|
||||
*/
|
||||
@Configuration
|
||||
public class SentinelRuleConfig {
|
||||
|
||||
@PostConstruct
|
||||
public void initRules() {
|
||||
Set<GatewayFlowRule> rules = new HashSet<>();
|
||||
|
||||
// fund-sys服务流控规则
|
||||
rules.add(new GatewayFlowRule("fund-sys")
|
||||
.setCount(100) // QPS阈值
|
||||
.setIntervalSec(1) // 统计窗口
|
||||
.setBurst(20) // 应对突发请求额外容量
|
||||
);
|
||||
|
||||
// fund-req服务流控规则
|
||||
rules.add(new GatewayFlowRule("fund-req")
|
||||
.setCount(50)
|
||||
.setIntervalSec(1)
|
||||
.setBurst(10)
|
||||
);
|
||||
|
||||
// fund-exp服务流控规则
|
||||
rules.add(new GatewayFlowRule("fund-exp")
|
||||
.setCount(50)
|
||||
.setIntervalSec(1)
|
||||
.setBurst(10)
|
||||
);
|
||||
|
||||
// fund-receipt服务流控规则
|
||||
rules.add(new GatewayFlowRule("fund-receipt")
|
||||
.setCount(50)
|
||||
.setIntervalSec(1)
|
||||
.setBurst(10)
|
||||
);
|
||||
|
||||
// 加载规则
|
||||
GatewayRuleManager.loadRules(rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sentinel Gateway 过滤器
|
||||
*/
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public GlobalFilter sentinelGatewayFilter() {
|
||||
return new SentinelGatewayFilter();
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,22 @@ spring:
|
||||
cloud:
|
||||
compatibility-verifier:
|
||||
enabled: false
|
||||
|
||||
# Sentinel配置
|
||||
sentinel:
|
||||
transport:
|
||||
dashboard: localhost:8080 # Sentinel Dashboard地址(可选)
|
||||
port: 8719 # Sentinel客户端端口
|
||||
eager: true # 服务启动时立即初始化
|
||||
datasource:
|
||||
# 从Nacos读取规则(可选)
|
||||
# ds1:
|
||||
# nacos:
|
||||
# server-addr: localhost:8848
|
||||
# data-id: sentinel-gateway-rules
|
||||
# group-id: DEFAULT_GROUP
|
||||
# rule-type: gw-flow
|
||||
|
||||
gateway:
|
||||
# 默认限流配置
|
||||
default-filters:
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
package com.fundplatform.sys.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfigMXBean;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* HikariCP 连接池监控配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class HikariMonitorConfig {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HikariMonitorConfig.class);
|
||||
|
||||
@Autowired
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* 每5分钟打印一次连接池状态
|
||||
*/
|
||||
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
|
||||
public void monitorHikariPool() {
|
||||
if (dataSource instanceof HikariDataSource) {
|
||||
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
|
||||
HikariConfigMXBean config = hikariDataSource.getHikariConfigMXBean();
|
||||
|
||||
log.info("=== HikariCP 连接池状态 ===");
|
||||
log.info("连接池名称: {}", config.getPoolName());
|
||||
log.info("活跃连接数: {}", hikariDataSource.getHikariPoolMXBean().getActiveConnections());
|
||||
log.info("空闲连接数: {}", hikariDataSource.getHikariPoolMXBean().getIdleConnections());
|
||||
log.info("等待获取连接的线程数: {}", hikariDataSource.getHikariPoolMXBean().getThreadsAwaitingConnection());
|
||||
log.info("最大连接数: {}", config.getMaximumPoolSize());
|
||||
log.info("最小空闲连接数: {}", config.getMinimumIdle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,9 +18,20 @@ spring:
|
||||
username: root
|
||||
password: zjf@123456
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
connection-timeout: 30000
|
||||
# 连接池大小配置
|
||||
maximum-pool-size: 20 # 最大连接数(建议: CPU核心数*2 + 有效磁盘数)
|
||||
minimum-idle: 5 # 最小空闲连接数
|
||||
# 连接超时配置
|
||||
connection-timeout: 30000 # 连接超时时间(毫秒)
|
||||
idle-timeout: 600000 # 空闲连接超时时间(10分钟)
|
||||
max-lifetime: 1800000 # 连接最大存活时间(30分钟)
|
||||
# 连接验证配置
|
||||
validation-timeout: 5000 # 连接验证超时时间
|
||||
leak-detection-threshold: 60000 # 连接泄露检测阈值(60秒)
|
||||
# 连接池名称
|
||||
pool-name: FundSysHikariPool
|
||||
# 连接初始化SQL
|
||||
connection-init-sql: SELECT 1
|
||||
|
||||
# Redis配置
|
||||
data:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user