Day 05 / 共 20 天 · 第 1 周 后端架构
横切与 Service 业务层
第 1 周收官。今天看两样东西:一是"每个请求都会经过"的 AOP 切面(鉴权/日志/异常统一在这里),二是 console 自身业务 service(登录、系统配置)。理解切面 = 理解后端的"请求生命周期"。
📍 你在整门课的位置(第 1 周 后端架构 · 收官)
D01 全景→
D02 启动→
D03 分层→
D04 REST→
D05 横切→
D06 SDK→
D07 存储→
D08 客户端→
D09 模型→
D10 流转
💡 一句话兜住今天(切面 = 机场安检口)
Day 04 看的是"每个控制器长啥样",今天看的是"所有请求进控制器之前都要先过的那道口子"。AOP 切面就是机场安检:无论你飞哪个航班(调哪个接口),都先过同一个安检口——发一张行李条
traceId(全程日志都印这个号,一搜就串起来)、查门禁(没登录不让进)、出口再统一兜底登记(任何异常都翻译成规整的错误码)。把这些"人人都要做"的事收在一处,就是横切。L01
请求生命周期的枢纽
console/aop/ApiStandardizationAspect.java(@Aspect @Component)用一个切点拦截所有控制器方法:
// :69-70
@Around("execution(* com.alibaba.higress.console.controller..*Controller.*(..))")
public Object around(ProceedingJoinPoint pjp) { ... }
什么是 AOP 切面?
有些逻辑(打日志、鉴权、异常兜底)几乎每个接口都要做,如果每个方法里都写一遍就太啰嗦。AOP(面向切面编程)让你把这些"横切逻辑"抽出来,声明"拦截所有 Controller 方法",Spring 就自动在每次调用前后插入执行。好比机场安检——不管你去哪个登机口,都先过同一个安检口。这个切面就是 Console 后端的"安检口"。
图注:一个切面 = 一整条安检流水线,任何接口都先过它,再到自己的登机口。
🧍 现在你是一个请求,跟着走一遍
你带着
POST /v1/routes 撞进后端 → 安检口先给你贴张行李条 traceId=abc123(之后你产生的每条日志都印 abc123)→ 把你的 request/response 收进 HttpContext 随身包 → 查你的登录凭证(没有?当场轰出去 401)→ 放行到 RoutesController.add 干正事 → 万一你在里面摔了(抛异常),出口的兜底员把你摔的姿势翻译成 400/401/409/500 再送出门。全程你都揣着 abc123,出事一搜就还原整条路径。L02
traceId 与上下文
切面进来第一件事(:72-77):生成 traceId 放进 MDC,并初始化 HttpContext(用 ThreadLocal 保存 request/response)。
读法:
traceId 是每个请求的唯一编号,写进日志(对应 application.properties 日志 pattern 里的 %mdc)。这样一条请求从进来到报错的所有日志都带同一个 traceId,排查问题时一搜就串起来了。HttpContext 用 ThreadLocal 让后续代码能随时拿到当前请求对象,不用一路当参数传。📝 举个例子:traceId 怎么把一次报错的日志串起来
一次
排查时只需
POST /v1/routes 进来 → 切面发号 traceId=abc123 → 后续每条日志都印它:[abc123] validate route demo → [abc123] createIngress... → [abc123] ERROR ApiException 409。排查时只需
grep abc123 app.log,这次请求从进门到报错的全过程就连成一条线,不会和别的并发请求日志搅在一起。L03
鉴权:登录校验
:80-89:若当前接口不是匿名接口,就调 sessionService.validateSession 校验登录,失败抛 AuthException,成功把 user 放进 SessionUserHelper。
if (isLoginRequired(method)) {
User user = sessionService.validateSession(); // 校验会话
SessionUserHelper.setCurrentUser(user);
}
读法:"要不要登录"由
isLoginRequired(:134-162)读取类/方法上的 @AllowAnonymous 注解决定,结果缓存进 ConcurrentHashMap 避免每次反射。集中鉴权的好处:新增一个接口默认就受保护,不用记得手动加校验。L04
统一异常处理
:96-109 catch 所有 Throwable,按类型分级打日志,转成 Response.failure + 对应 HTTP 状态。状态映射在 getHttpStatus(:164-178):
ValidationException → 400
AuthException → 401
NotFoundException → 502
ResourceConflictException→ 409
其余 → 500
为什么 NotFound 是 502 而不是 404?
这里的
NotFoundException 通常指"去 K8s / Higress 控制器找东西没找到",也就是"上游依赖异常",所以用 502(Bad Gateway,网关自身作为客户端访问后端失败)。而"用户查的资源不存在"是在 ControllerUtil 里返回 404(Day 04)。两种"没找到"语义不同,用不同的码区分。K8s 的 ApiException 还会把 Code/Body 一起打进日志(:117-131)。| Service/SDK 抛出 | 含义 | 切面翻译成 |
|---|---|---|
ValidationException | 入参不合法 | 400 |
AuthException | 没登录/凭证失效 | 401 |
ResourceConflictException | 撞名/版本冲突 | 409 |
NotFoundException | 上游依赖(K8s/控制器)找不到 | 502 |
其它 Throwable | 兜底 | 500 |
⚠️ 常见误解:看到 NotFound 就以为该返 404。这里 404 vs 502 是两种"没找到":用户查的资源不存在 →
ControllerUtil 返 404(Day 04);后端去 K8s 找依赖没找到 → 502(我这个"网关"访问上游失败)。语义不同,别混。L05
@AllowAnonymous
登录、就绪探针、落地页这类接口不能要求先登录,用 @AllowAnonymous 标注跳过鉴权。切面读到它就放行。
读法:这是"默认安全"设计——不加注解 = 需要登录,只有明确标
@AllowAnonymous 的才放行。LandingController、HealthzController、SessionController.login 都是匿名的。L06
SessionService:登录
SessionController(/session)提供 login/logout,背后 SessionService 校验用户名密码(来自 Helm 注入的 admin.username/password,helm/values.yaml:113-116)并签发会话。
读法:Console 的登录很轻——单管理员账号,凭据从部署配置来。这也呼应"无数据库":连用户信息都不建表,直接用配置。
UserController(/user)提供 info/changePassword。L07
ConfigService:系统配置存 ConfigMap
console 自身的持久化配置走 console/service/ConfigServiceImpl.java(@Service):
// :42-43 ConfigMap 名默认 higress-console
// :53-64 getString 从 ConfigMap 的 data 读值
// :151-175 setConfigs:读/初始化 ConfigMap → 改 data → replaceConfigMap
读-改-写模式
改配置时它先
readConfigMap 拿到当前 ConfigMap,改动 data,再 replaceConfigMap 写回。K8s 的 replace 带 resourceVersion 乐观锁——如果中途有人也改了,版本号对不上,写入会失败,避免覆盖别人的改动。这就是用 K8s 当数据库时的并发控制方式。注意这个 ConfigService 只管控制台自己的键值,跟"网关业务配置"是两码事。L08
今日小结 + 动手(第 1 周收官)
🧠 第 1 周你应该能回答
- 切面拦截了什么?请求生命周期的五步(traceId→上下文→鉴权→执行→统一异常)?
- 异常类型到 HTTP 状态的映射?为什么 NotFound 是 502?
@AllowAnonymous的"默认安全"设计?- console 系统配置怎么存进 ConfigMap?乐观锁怎么防并发覆盖?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/console/src/main/java/com/alibaba/higress/console
sed -n '69,178p' aop/ApiStandardizationAspect.java
sed -n '42,175p' service/ConfigServiceImpl.java
ls aop/ service/
下周预告 · 第 2 周:钻进 SDK 与配置存储——
HigressServiceProvider 手写依赖注入、KubernetesClientService 怎么连 K8s、KubernetesModelConverter 怎么把路由翻译成 Ingress+注解,以及"创建路由"的端到端追踪。