# 所有环境共享的配置。环境差异只放在 application-{profile}.yml 与 K8s ConfigMap 里, # 真实密钥一律走环境变量(K8s Secret),仓库和镜像里不出现任何一个真值。 # 见 07-config-governance.md。 spring: application: name: conti-backend # ---- 数据源(03-persistence.md)---- datasource: # url 里不带库名:每个 domain 有自己的库,由 Flyway/JPA 的 schema 指定。 # connectionTimeZone=UTC + preserveInstants=true:让驱动按 UTC 读写,Instant 不被本地时区拧一遍。 # rewriteBatchedStatements=true:不加它 Hibernate 的 batch_size 在 MySQL 上等于没配。 url: >- jdbc:mysql://${DB_HOST}:3306/?sslMode=REQUIRED&connectionTimeZone=UTC&preserveInstants=true&rewriteBatchedStatements=true username: ${DB_USERNAME} password: ${DB_PASSWORD} hikari: maximum-pool-size: 15 minimum-idle: 5 connection-timeout: 3000 # 拿不到连接就快速失败,不要让请求线程堆在这里 max-lifetime: 570000 # 略小于 MySQL 的 wait_timeout,避免用到已被服务端关闭的连接 transaction-isolation: TRANSACTION_READ_COMMITTED jpa: open-in-view: false # 必须显式关掉,Boot 默认是 true hibernate: ddl-auto: validate # 表结构只由 Flyway 改,Hibernate 只做校验 properties: hibernate: jdbc: time_zone: UTC batch_size: 50 order_inserts: true order_updates: true query: fail_on_pagination_over_collection_fetch: true flyway: enabled: false # 关掉 Boot 的单实例自动配置,改由 DomainFlywayConfig 接管 # ---- 配置中心(07-config-governance.md)---- cloud: kubernetes: config: enabled: true sources: - name: conti-backend-config reload: enabled: true mode: polling # 定期轮询 ConfigMap(event 模式需要 watch 权限) strategy: refresh # 只刷新 @RefreshScope bean,不重启容器 period: 15s # ---- 优雅停机(09-build-deploy.md)---- lifecycle: timeout-per-shutdown-phase: 25s server: shutdown: graceful # Boot 默认是 immediate,收到 SIGTERM 直接掐断在途请求 # ---- 安全(04-security-auth.md)---- # 密钥来自 K8s Secret;base64 解码后必须 ≥ 32 字节,否则 JwtProperties 在启动时就报错。 security: jwt: active-key-id: ${SECURITY_JWT_ACTIVE_KEY_ID:v1} keys: v1: ${SECURITY_JWT_SECRET} access-token-ttl-minutes: 30 refresh-token-ttl-days: 30 # ---- 外部系统(05-integration-layer.md)---- integration: clients: f6: base-url: ${F6_BASE_URL} connect-timeout-ms: 1000 read-timeout-ms: 2000 connection-request-timeout-ms: 500 max-connections: 60 max-connections-per-route: 30 mini: base-url: ${MINI_BASE_URL} connect-timeout-ms: 500 read-timeout-ms: 1000 connection-request-timeout-ms: 300 max-connections: 60 max-connections-per-route: 30 resilience4j: # 叠加顺序由这几个 *-aspect-order 属性决定,跟注解写在方法上的先后顺序无关。 # 这里显式写死,避免依赖框架默认值——默认值会随版本变,而顺序变了语义就变了。 retry: retry-aspect-order: 3 # 最外层:每次重试都被熔断器单独统计 instances: f6-api: max-attempts: 2 wait-duration: 200ms exponential-backoff-multiplier: 2 retry-exceptions: # 同步栈下真实会抛出来的类型:ResourceAccessException 包住了 # SocketTimeoutException/ConnectException 等所有 IO 异常,写后者是无效配置。 - org.springframework.web.client.ResourceAccessException - com.continental.retailapp.integration.f6.F6ServerException ignore-exceptions: # 熔断已打开时抛的异常,重试它毫无意义,只会白白多等一轮 wait-duration - io.github.resilience4j.circuitbreaker.CallNotPermittedException - com.continental.retailapp.integration.f6.F6ClientException circuitbreaker: circuit-breaker-aspect-order: 2 instances: f6-api: sliding-window-type: COUNT_BASED sliding-window-size: 20 minimum-number-of-calls: 10 # 样本太少时不做判断,避免启动后头几个请求就把熔断打开 failure-rate-threshold: 50 slow-call-duration-threshold: 1500ms slow-call-rate-threshold: 80 # 慢调用也算故障:"每次都卡满 2 秒但最终成功"也必须能熔断 wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 5 record-exceptions: - org.springframework.web.client.ResourceAccessException - com.continental.retailapp.integration.f6.F6ServerException bulkhead: bulkhead-aspect-order: 1 # 最内层,贴着真实调用 instances: f6-api: max-concurrent-calls: 20 max-wait-duration: 0 # 拿不到名额立刻失败走降级,排队等于把阻塞换个地方 mini-o2o: max-concurrent-calls: 20 max-wait-duration: 0 # ---- 可观测性(08-observability.md)---- management: tracing: enabled: true # 这是 traceId 进 MDC 的前提,关掉连日志里的 traceId 都没有 export: enabled: false # 但不往任何后端上报 span——现在只要日志关联,不建全链路追踪系统 sampling: probability: 1.0 # 不上报就没有采样成本,全采即可 endpoints: web: exposure: include: health, prometheus, info endpoint: health: probes: enabled: true # 暴露 /actuator/health/liveness、/readiness 供 K8s 探针使用 metrics: tags: application: conti-backend springdoc: api-docs: enabled: false # 默认关掉,只在 local/dev/uat 打开(见各 profile) swagger-ui: enabled: false