diff --git a/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java b/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java index d9bf0ff9..3472e7c6 100644 --- a/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java +++ b/mateclaw-server/src/main/java/vip/mate/config/DatabaseBootstrapRunner.java @@ -44,6 +44,9 @@ public class DatabaseBootstrapRunner implements ApplicationRunner { /** Cached flag: true when running on KingbaseES. */ private volatile Boolean isKingbase; + /** Cached flag: true when running on PostgreSQL. */ + private volatile Boolean isPostgres; + /** * When true, wait for Desktop splash screen to call /setup/init with chosen language. * When false (default), auto-initialize immediately on startup. @@ -114,7 +117,9 @@ public class DatabaseBootstrapRunner implements ApplicationRunner { String scriptName; if (isMySQL()) { scriptName = "en-US".equals(locale) ? "db/data-mysql-en.sql" : "db/data-mysql-zh.sql"; - } else if (isKingbase()) { + } else if (isKingbase() || isPostgres()) { + // PostgreSQL-family seed (covers both PostgreSQL and KingbaseES, + // which share the same ON CONFLICT / SERIAL-free DDL dialect). scriptName = "en-US".equals(locale) ? "db/data-kingbase-en.sql" : "db/data-kingbase-zh.sql"; } else { scriptName = "en-US".equals(locale) ? "db/data-en.sql" : "db/data-zh.sql"; @@ -165,8 +170,13 @@ public class DatabaseBootstrapRunner implements ApplicationRunner { String dbProduct = connection.getMetaData().getDatabaseProductName().toLowerCase(); isMySQL = dbProduct.contains("mysql") || dbProduct.contains("mariadb"); isKingbase = dbProduct.contains("kingbase"); + // KingbaseES reports its own product name ("KingbaseES"), so the + // postgres check stays mutually exclusive with the kingbase one. + isPostgres = dbProduct.contains("postgresql") && !isKingbase; if (isKingbase) { log.info("Detected database: {} (KingbaseES mode)", dbProduct); + } else if (isPostgres) { + log.info("Detected database: {} (PostgreSQL mode)", dbProduct); } else { log.info("Detected database: {} (MySQL mode: {})", dbProduct, isMySQL); } @@ -174,6 +184,7 @@ public class DatabaseBootstrapRunner implements ApplicationRunner { log.warn("Failed to detect database type, falling back to H2 mode", e); isMySQL = false; isKingbase = false; + isPostgres = false; } } return isMySQL; @@ -187,6 +198,14 @@ public class DatabaseBootstrapRunner implements ApplicationRunner { return isKingbase != null && isKingbase; } + private boolean isPostgres() { + if (isPostgres == null) { + // Trigger detection + isMySQL(); + } + return isPostgres != null && isPostgres; + } + private void runScript(String path) { ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.setContinueOnError(false); diff --git a/mateclaw-server/src/main/resources/application-postgres.yml b/mateclaw-server/src/main/resources/application-postgres.yml new file mode 100644 index 00000000..a9e6a829 --- /dev/null +++ b/mateclaw-server/src/main/resources/application-postgres.yml @@ -0,0 +1,71 @@ +spring: + datasource: + # PostgreSQL data source. + # Default connection parameters: + # DB_HOST=localhost, DB_PORT=5432, DB_NAME=mateclaw + # Override via env vars: DB_HOST, DB_PORT, DB_NAME, DB_USERNAME, DB_PASSWORD + # + # JDBC timeouts: + # connectTimeout=10 — TCP connect timeout (s), avoids OS-level stalls + # socketTimeout=30 — socket read timeout (s), prevents dead connections hanging forever + # loginTimeout=10 — database login timeout (s) + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:mateclaw}?currentSchema=mateclaw&connectTimeout=10&socketTimeout=30&loginTimeout=10 + driver-class-name: org.postgresql.Driver + username: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:postgres} + hikari: + maximum-pool-size: 30 + minimum-idle: 5 + connection-timeout: 30000 + idle-timeout: 300000 + # Recycle connections after 10 min so a leak self-heals quickly rather + # than surfacing as a ~30 min stall. + max-lifetime: 600000 + leak-detection-threshold: 30000 + # Fail fast if the first valid connection can't be obtained in time. + initialization-fail-timeout: 30000 + # Force search_path on every new connection as a belt-and-suspenders + # guard alongside currentSchema, so connections never land in public. + connection-init-sql: SET search_path TO mateclaw + + flyway: + # PostgreSQL and KingbaseES share the same migration tree + # (db/migration/kingbase) because they use the same SQL dialect. + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:mateclaw}?currentSchema=mateclaw&connectTimeout=10&socketTimeout=30&loginTimeout=10 + user: ${DB_USERNAME:postgres} + password: ${DB_PASSWORD:postgres} + locations: + - classpath:db/migration/kingbase + # Ensure the target schema exists before migrating (PostgreSQL won't + # auto-create a non-public schema). + init-sqls: + - CREATE SCHEMA IF NOT EXISTS mateclaw + # Skip checksum validation so an in-place migration edit doesn't block startup. + validate-on-migrate: false + + h2: + console: + enabled: false + +# MyBatis Plus — explicit PostgreSQL dialect. +# The no-arg PaginationInnerInterceptor auto-detects from the JDBC URL, which +# can fail under a wrapped/proxied DataSource and fall back to the MySQL +# dialect (LIMIT offset,count). Pin postgre_sql so pagination / ID generation / +# batch operations always use the correct dialect. +mybatis-plus: + global-config: + db-config: + db-type: postgre_sql + +# Production (multi-tenant server) hardening: fail closed on Wiki source-path +# validation. With no allowed-source-roots configured, every KB source +# directory is rejected rather than allowing full-filesystem reads — a missing +# allow-list cannot silently re-open arbitrary directory scanning. Override +# MATE_WIKI_ALLOWED_SOURCE_ROOTS to permit specific roots. The default profile +# (H2 / desktop / single-tenant) leaves this off. +mate: + wiki: + require-allowed-roots: true + allowed-source-roots: ${MATE_WIKI_ALLOWED_SOURCE_ROOTS:} + watcher-enabled: ${MATE_WIKI_WATCHER_ENABLED:false} + watcher-interval-ms: ${MATE_WIKI_WATCHER_INTERVAL_MS:300000}