diff --git a/src/main/java/com/sztzjy/linkCommerce/config/DatabaseCharsetChecker.java b/src/main/java/com/sztzjy/linkCommerce/config/DatabaseCharsetChecker.java new file mode 100644 index 0000000..88d87dd --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/config/DatabaseCharsetChecker.java @@ -0,0 +1,69 @@ +package com.sztzjy.linkCommerce.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 启动时检查数据库连接和 AI 评估原始响应列的字符集。 + * 字符集不是 utf8mb4 时,写入中文会报 "Incorrect string value"; + * 这里只记录告警方便排查,不阻止应用启动。 + */ +@Slf4j +@Component +public class DatabaseCharsetChecker { + private static final String SESSION_SQL = + "SELECT @@character_set_client AS client, @@character_set_connection AS conn"; + private static final String COLUMN_SQL = + "SELECT CHARACTER_SET_NAME FROM information_schema.COLUMNS " + + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_training_evaluation' " + + "AND COLUMN_NAME = 'assessment_raw_response'"; + + private final JdbcTemplate jdbcTemplate; + + public DatabaseCharsetChecker(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @PostConstruct + public void check() { + try { + List problems = findProblems(); + if (problems.isEmpty()) { + log.info("Database charset check passed: connection and AI evaluation columns use utf8mb4"); + return; + } + for (String problem : problems) { + log.warn("Database charset check: {}", problem); + } + } catch (RuntimeException e) { + log.warn("Database charset check skipped: {}", e.getMessage()); + } + } + + List findProblems() { + List problems = new ArrayList(); + Map session = jdbcTemplate.queryForMap(SESSION_SQL); + String client = String.valueOf(session.get("client")); + String connection = String.valueOf(session.get("conn")); + if (!"utf8mb4".equalsIgnoreCase(client)) { + problems.add("character_set_client=" + client + ", expected utf8mb4; Chinese text may fail to save"); + } + if (!"utf8mb4".equalsIgnoreCase(connection)) { + problems.add("character_set_connection=" + connection + ", expected utf8mb4; Chinese text may fail to save"); + } + List columnCharsets = jdbcTemplate.queryForList(COLUMN_SQL, String.class); + for (String charset : columnCharsets) { + if (charset != null && !"utf8mb4".equalsIgnoreCase(charset)) { + problems.add("ai_training_evaluation.assessment_raw_response charset=" + charset + + ", expected utf8mb4; AI raw responses may fail to save"); + } + } + return problems; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9fff1cd..0438922 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -63,6 +63,9 @@ spring: validation-query: SELECT 1 validation-query-timeout: 3 keep-alive: true + # 固定驱动端编码:这里的参数优先于连接串,避免生产环境私有配置里的连接串 + # 缺少或写错 characterEncoding 时,驱动按平台默认编码(GBK)发送中文 + connection-properties: characterEncoding=utf8;connectionCollation=utf8mb4_unicode_ci connection-init-sqls: - SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci # 配置监控统计 diff --git a/src/test/java/com/sztzjy/linkCommerce/config/DatabaseCharsetCheckerTest.java b/src/test/java/com/sztzjy/linkCommerce/config/DatabaseCharsetCheckerTest.java new file mode 100644 index 0000000..804a711 --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/config/DatabaseCharsetCheckerTest.java @@ -0,0 +1,75 @@ +package com.sztzjy.linkCommerce.config; + +import org.junit.jupiter.api.Test; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class DatabaseCharsetCheckerTest { + + @Test + void reportsNothingWhenConnectionAndColumnAreUtf8mb4() { + DatabaseCharsetChecker checker = checker("utf8mb4", "utf8mb4", Collections.singletonList("utf8mb4")); + + assertTrue(checker.findProblems().isEmpty()); + } + + @Test + void reportsConnectionCharsetThatIsNotUtf8mb4() { + DatabaseCharsetChecker checker = checker("gbk", "gbk", Collections.singletonList("utf8mb4")); + + List problems = checker.findProblems(); + + assertEquals(2, problems.size()); + assertTrue(problems.get(0).contains("character_set_client=gbk")); + assertTrue(problems.get(1).contains("character_set_connection=gbk")); + } + + @Test + void reportsRawResponseColumnThatIsNotUtf8mb4() { + DatabaseCharsetChecker checker = checker("utf8mb4", "utf8mb4", Collections.singletonList("latin1")); + + List problems = checker.findProblems(); + + assertEquals(1, problems.size()); + assertTrue(problems.get(0).contains("assessment_raw_response")); + assertTrue(problems.get(0).contains("latin1")); + } + + @Test + void skipsColumnCheckWhenTableDoesNotExistYet() { + DatabaseCharsetChecker checker = checker("utf8mb4", "utf8mb4", Collections.emptyList()); + + assertTrue(checker.findProblems().isEmpty()); + } + + @Test + void neverBlocksStartupWhenDatabaseIsUnavailable() { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + when(jdbcTemplate.queryForMap(anyString())).thenThrow(new DataAccessResourceFailureException("db down")); + + assertDoesNotThrow(() -> new DatabaseCharsetChecker(jdbcTemplate).check()); + } + + private static DatabaseCharsetChecker checker(String client, String connection, List columnCharsets) { + JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class); + Map session = new HashMap(); + session.put("client", client); + session.put("conn", connection); + when(jdbcTemplate.queryForMap(anyString())).thenReturn(session); + when(jdbcTemplate.queryForList(anyString(), org.mockito.ArgumentMatchers.>any())) + .thenReturn(columnCharsets); + return new DatabaseCharsetChecker(jdbcTemplate); + } +}