Day 02 / 共 20 天 · 第 1 周 后端架构

后端启动:Spring Boot 怎么 boot

昨天看了全景,今天钻进后端的"开机流程":从 main 方法一路到"连上 K8s、把 SDK 各服务注册成 Bean"。最关键的一步是 SdkConfig@PostConstruct——这是 console 和 sdk 的桥接点。

📍 你在整门课的位置(第 1 周 后端架构)
D01 全景 D02 启动 D03 分层 D04 REST D05 横切 D06 SDK D07 存储 D08 客户端 D09 模型 D10 流转
💡 一句话兜住今天(延续"驾驶舱"世界观) 昨天认识了驾驶舱各部件,今天看"开机点火"顺序——就像汽车拧钥匙:先自检(组件扫描)→ 读设置(配置)→ 接通引擎(SdkConfig@PostConstruct 真正连上 K8s 档案室)→ 挂好挡(各 SDK 服务注册成 Bean)→ 仪表亮起(静态资源就绪)→ 怠速待命(Tomcat 监听 8080)。今天最关键的一颗螺丝就是那个"接通档案室"的 @PostConstruct
L01

主类与组件扫描

入口 console/HigressConsoleApplication.java:18-23

@SpringBootApplication(scanBasePackages = {"com.alibaba.higress"})
public class HigressConsoleApplication {
    public static void main(String[] args) {
        SpringApplication.run(HigressConsoleApplication.class, args);
    }
}
为什么 scanBasePackages 要写到 com.alibaba.higress? Spring Boot 默认只扫描主类所在包及其子包。但本项目 sdk 模块里也有一些 Spring 组件(比如 ConfigServiceImpl 上带 @Service),它们的包是 com.alibaba.higress.console.service 之外的。把扫描根设为 com.alibaba.higress,就能同时覆盖 console 和 sdk 两个模块的包,让两边的 Bean 都被发现。
L02

application.properties

console/src/main/resources/application.properties(注意:只有 .properties,没有 .yaml):

springdoc.api-docs.enabled=false            # 默认关闭 API 文档
springdoc.pathsToMatch=/v1/**,/session/**,/dashboard/**,/system/**,/user/**
higress-console.build.version=@app.build.version@   # Maven 资源过滤注入版本
server.compression.enabled=true              # 开启响应压缩
读法:pathsToMatch 这一行等于把后端全部 API 前缀列了个总览:/v1(业务资源)、/session(登录)、/dashboard/system/user@app.build.version@ 会被 Maven 替换成 console/pom.xml:202.1.0大量运行期参数不写死在这里,而走 @Value + 环境变量(Helm 注入),见 L06。
L03

WebMvc 与 SPA 回退

console/WebMvcInitializer.java:31-59@Configuration @EnableWebMvc)做两件事:把 /* 映射到 classpath:/static/(打包进来的前端),并实现 SPA 回退

// resolveResourceInternal :48-56(简化)
// 找不到的资源:只要不是 /v1/ API 前缀(:35),
// 也不是 /assets/ /css/ /js/ 等静态目录(:39-40),
// 一律回退到 /index.html
什么叫 SPA 回退? 前端是单页应用(SPA),路由(比如 /route/plugin)都在浏览器里由 JS 处理,服务器上并没有这些文件。如果用户直接刷新 /plugin,服务器找不到这个文件会 404。SPA 回退就是"找不到就统统返回 index.html",让前端 JS 接管路由。但要排除真正的 API(/v1/)和静态资源目录,否则 API 也被吞成 HTML 就乱套了。
📝 举个例子:三个请求进来,服务器分别怎么答 拿三条不同的 URL 单步走一遍 resolveResourceInternal
浏览器请求命中哪条规则服务器返回
GET /v1/routes/v1/ API 前缀(:35)交给后端控制器,返回 JSON
GET /assets/app.js是静态目录(:39-40)返回该静态文件
GET /plugin(刷新页面)都不是 → 兜底返回 /index.html,前端 JS 接管路由
L04

SdkConfig:真正连上 K8s 的地方

整个启动流程最关键的 Bean 是 console/config/SdkConfig.java@Configuration):

// :47-79 一组 @Value 读入 K8s/controller 连接参数
@Value("${higress-console.kube-config:}") private String kubeConfig;
@Value("${higress-console.controller.service.host:...}") private String controllerServiceHost;
// ... jwtPolicy、accessToken、clusterDomainSuffix 等,默认值来自 HigressConstants

// :83-93 @PostConstruct initialize()
@PostConstruct
public void initialize() {
    HigressServiceConfig config = HigressServiceConfig.builder()
        .withKubeConfigPath(...)....build();
    serviceProvider = HigressServiceProvider.create(config);  // ← 这一步真正连 K8s
}
读法:@PostConstruct 表示"这个 Bean 构造完成后立刻执行 initialize()"。它用 @Value 注入的参数搭出一个 HigressServiceConfig,然后 HigressServiceProvider.create(config)——从这一刻起,后端就握着一个能读写 K8s 的客户端了。(Provider 内部怎么装配,第 2 周 Day 06 详讲。)

👶 小白:为什么"连 K8s"要放在 @PostConstruct,不直接写在构造函数里?

👨‍🏫 老师:因为构造 Bean 时,那些 @Value 注入的参数(kubeConfig、controller 地址…)还没被 Spring 填进来——构造函数里读到的全是 null。@PostConstruct 的时机是"对象造好、依赖也都注入完之后",这时参数齐了,才能拿它们去搭 HigressServiceConfig 连档案室。就像点火前得先等钥匙插到位、油路接通,急不得。

L05

@Bean 桥接:console ← sdk

SdkConfig.java:95-168serviceProvider 产出的各 SDK 服务注册成 Spring @Bean

@Bean public RouteService routeService() { return serviceProvider.routeService(); }
@Bean public KubernetesClientService kubernetesClientService() { ... }
@Bean public WasmPluginService wasmPluginService() { ... }
@Bean public AiRouteService aiRouteService() { ... }
// ServiceService / DomainService / LlmProviderService / McpServerService ...
这就是 console 和 sdk 的"接线板" sdk 模块自己不用 Spring(它是纯 Java 库),所以它产出的对象不会自动变成 Bean。SdkConfig 就充当"接线板"——把 sdk 造出来的每个 service 挂到 Spring 容器里。之后 console 的控制器只要 @Resource RouteService routeService 就能拿到,任何 K8s 操作都从这里走,控制器自己从不 new
L06

配置键 SystemConfigKey

所有可注入的配置键定义在 console/constant/SystemConfigKey.java

  • :25 ns(命名空间)、:29 controller.access-token:31 controller.jwt-policy
  • :33 / :35 controller.service.port / host:51 config-map.name:74+ dashboard.*
读法:部署时 Helm 把这些键映射成环境变量(HIGRESS_CONSOLE_*)注入容器,Spring 再通过 @Value 读进来。所以"改配置"= 改 Helm values → 环境变量 → @Value,无需改代码。Day 19 会把这条注入链走完。
L07

启动时序(串起来)

main() → SpringApplication.run
  → 组件扫描 com.alibaba.higress(console + sdk 的 @Component/@Service)
    → 加载 application.properties + 环境变量(@Value)
      → SdkConfig 构造 → @PostConstruct:建 HigressServiceConfig → create(provider) → 连 K8s
        → 注册各 SDK service 为 @Bean
          → WebMvcInitializer 装好静态资源 + SPA 回退
            → 内嵌 Tomcat 监听 8080,就绪
开机点火顺序:main() → 连档案室 → 挂服务 → 就绪 ① main() 组件扫描 ② 读配置 @Value ③ @PostConstruct 连上 K8s 档案室 ④ 各 SDK 服务 注册成 @Bean ⑤ 静态资源+SPA ⑥ Tomcat:8080 就绪 橙框③是 console 与 sdk 的"接线点",也是最容易连不上 K8s 的一步
图注:六步启动,③ 连档案室是承上启下的关键;后面每天都在这条链的某个节点展开。
把这张时序图记牢,后面每一天都在这条链上的某个节点展开。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么 scanBasePackages 要设成 com.alibaba.higress
  • SPA 回退解决什么问题?为什么要排除 /v1/
  • SdkConfig@PostConstruct 干了什么?它为什么是"桥接点"?
  • 配置怎么从 Helm 一路走到 @Value

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend
sed -n '18,23p' console/src/main/java/com/alibaba/higress/console/HigressConsoleApplication.java
sed -n '31,59p' console/src/main/java/com/alibaba/higress/console/WebMvcInitializer.java
sed -n '47,168p' console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java
明天预告 · Day 03后端分层——Controller / Service / SDK 三层各自的职责边界,以及请求进来后在这三层之间怎么流动。
← Day 01 Day 03 · 后端分层 →