backend scaffold

This commit is contained in:
Guangfei.Zhao
2026-08-17 15:31:27 +08:00
commit 84fc2c0677
159 changed files with 10542 additions and 0 deletions
@@ -0,0 +1,20 @@
package com.continental.retailapp
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
/**
* 唯一的可部署单元(模块化单体,见 01-project-structure.md)。
*
* `scanBasePackages` 写到 `com.continental.retailapp` 这一层:各 domain 和 platform 模块
* 的包名都在它下面,一次扫全。**模块边界不靠扫描范围来保证**——那是 Gradle 依赖图
* 加上 ArchUnit 的职责,Spring 这里只管把 bean 找齐。
*/
@SpringBootApplication(scanBasePackages = ["com.continental.retailapp"])
@ConfigurationPropertiesScan("com.continental.retailapp")
class BootstrapApplication
fun main(args: Array<String>) {
runApplication<BootstrapApplication>(*args)
}
@@ -0,0 +1,11 @@
# dev:只放与其他环境的差异,其余走 application.yml + ConfigMap。
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
logging:
level:
com.continental.retailapp: DEBUG
@@ -0,0 +1,44 @@
# 本地开发。docker compose up -d mysql 之后:
# SPRING_PROFILES_ACTIVE=local ./gradlew :bootstrap:bootRun
# 种子账号 demo / demo1234 由 LocalSeedDataConfig 在启动时写入(只在这个 profile 下存在)。
spring:
cloud:
kubernetes:
config:
enabled: false # 本地不连 K8s API,配置全部走本地文件
reload:
enabled: false
datasource:
url: jdbc:mysql://localhost:3306/?connectionTimeZone=UTC&preserveInstants=true&rewriteBatchedStatements=true
username: conti
password: conti_local_password # 仅本地开发用,不是真实密钥
security:
jwt:
active-key-id: local
keys:
# 仅本地开发用的假密钥;HS256 要求 base64 解码后 ≥ 32 字节,见 04-security-auth.md
local: bG9jYWwtZGV2LW9ubHktc2VjcmV0LW5vdC1mb3ItcmVhbC11c2UtMzJi
integration:
clients:
# 本地没有真实的 F6 / O2O,指向一个不存在的端口即可:
# 走的是降级路径,正好能验证 fallback 的行为。
f6:
base-url: http://localhost:9901
mini:
base-url: http://localhost:9902
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
logging:
level:
com.continental.retailapp: DEBUG
# 本地看 SQL 方便,但**不要**在任何非本地环境打开:绑定参数里会有 PII
org.hibernate.SQL: DEBUG
@@ -0,0 +1,11 @@
# prod:能关的都关掉。
#
# springdoc 保持 application.yml 里的 false —— 接口文档在生产环境是攻击面,不是便利。
# SecurityConfig 里 swagger 路径的 permitAll 也只在非 prod 生效,两道都关上。
logging:
level:
root: INFO
com.continental.retailapp: INFO
# 生产环境绝不打开 SQL 日志:绑定参数里会带 PII
org.hibernate.SQL: WARN
@@ -0,0 +1,12 @@
# uat:尽量贴近 prod,只保留排查问题必需的差异。
# swagger 在 uat 还开着,方便联调;prod 一律关闭(见 SecurityConfig 的 isProd 分支)。
springdoc:
api-docs:
enabled: true
swagger-ui:
enabled: true
logging:
level:
com.continental.retailapp: INFO
@@ -0,0 +1,158 @@
# 所有环境共享的配置。环境差异只放在 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 # 定期轮询 ConfigMapevent 模式需要 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 Secretbase64 解码后必须 ≥ 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