REST API 层:后端对外的门面
今天看后端"对外暴露了哪些接口":一张控制器总表,再精读最典型的路由 CRUD,最后看统一响应和状态码策略——这套模式所有控制器都一样,学一个就会全部。
/v1/routes/demo 指"路由 demo"这件东西),HTTP 动词 = 你要干嘛(GET 查、POST 寄新的、PUT 改、DELETE 撤单)。看一眼"动词 + 地址"就知道这次请求要对哪件东西做什么——这就是 REST 好用的地方。所有控制器都用同一张单据模板,学会一个就会全部 16 个。统一 REST 风格
所有控制器长得几乎一样:@RestController + @RequestMapping("/v1/...") + @Validated + @Tag(Swagger 分组),返回 ResponseEntity<Response<T>> 或 ResponseEntity<PaginatedResponse<T>>,通过 ControllerUtil.buildResponseEntity(...) 包装。
GET /v1/routes 列表、GET /v1/routes/{name} 查一个、POST /v1/routes 新建、PUT /v1/routes/{name} 改、DELETE /v1/routes/{name} 删。看到 URL 和方法就知道在干嘛,这就是 REST 的好处。控制器全表
| 控制器 | 基路径 | 主要端点 |
|---|---|---|
| RoutesController | /v1/routes | list/get/post/put/delete |
| ServicesController | /v1/services | list(只读) |
| ServiceSourceController | /v1/service-sources | CRUD |
| DomainsController | /v1/domains | CRUD |
| ConsumersController | /v1/consumers | CRUD |
| TlsCertificatesController | /v1/tls-certificates | CRUD |
| WasmPluginsController | /v1/wasm-plugins | CRUD + /config + /readme |
| WasmPluginInstancesController | /v1(四作用域) | global/domain/route/service |
| ai/AiRoutesController | /v1/ai/routes | CRUD |
| ai/LlmProvidersController | /v1/ai/providers | CRUD |
| mcp/McpServerController | /v1/mcpServer | CRUD + consumers |
| DashboardController | /dashboard | init/info/configData |
| SystemController | /system | init/info/config |
| SessionController | /session | login/logout |
| UserController | /user | info/changePassword |
| HealthzController | /healthz | ready(就绪探针) |
ServicesController 只读(服务从注册中心发现,不能增删);WasmPluginInstancesController 特殊——它按四个作用域(全局/域名/路由/服务)分别提供接口。精读 Routes CRUD
RoutesController.java 是最典型的 CRUD,逐段看 add(:71-86):
@PostMapping
public ResponseEntity> add(@RequestBody Route route) {
if (StringUtils.isEmpty(route.getName())) // :78 名字非空
throw new ValidationException("name can't be empty.");
if (route.getName().endsWith(HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX)) // :81 禁内部后缀
throw new ValidationException(...);
route.validate(); // :84 领域模型自校验
return ControllerUtil.buildResponseEntity(routeService.add(route));
}
.internal 后缀 → route.validate() 领域自校验)后交给 routeService.add。update(:88-107)还会校验"URL 里的名字"和"body 里的名字"一致。delete(:109-120)成功返回 204。控制器只做"守门",不做业务。POST body {"name":""} → 名字空 → 抛 ValidationException → 400。POST body {"name":"demo.internal"} → 命中禁用后缀 → 400。PUT /v1/routes/demo body {"name":"other"} → URL 名与 body 名不一致 → 400。POST body {"name":"demo","services":[...]} 全部合法 → 交 routeService.add → 201。最小示例:ServicesController
ServicesController.java:35-51 只有一个 list,是最干净的入门样本:
@GetMapping
public ResponseEntity> list(@ParameterObject ServicePageQuery query) {
return ControllerUtil.buildResponseEntity(serviceService.list(query));
}
统一响应 Response DTO
console/controller/dto/Response.java:32-57 是三段式包装:
class Response { boolean success; String message; T data; }
// success(data) / failure(msg|Throwable) 静态工厂
// getErrorMessage :63-75 会把 K8s ApiException 的 responseBody 也拼进错误信息
{success, message, data} 这个统一形状,好处理。特别贴心的是 getErrorMessage 把 K8s 报错的原始 body 也带上——排查"为什么写 K8s 失败"时能直接看到根因。分页版是 PaginatedResponse。ControllerUtil:按方法定状态码
console/controller/util/ControllerUtil.java:46-78 根据 HTTP 方法自动决定状态码:
- GET 且结果为
null→ 404 - GET 且是分页 →
PaginatedResponse.success - POST → 201 CREATED
- DELETE → 有内容 200,否则 204
| 方法 + 返回值 | ControllerUtil 判定 | 最终状态码 |
|---|---|---|
GET 查单个,结果 null | 没找到 | 404 |
| GET 分页列表 | PaginatedResponse.success | 200 |
| POST 新建成功 | 创建类 | 201 |
| DELETE 无返回内容 | 删除且无 body | 204 |
👶 小白:直接全返回 200、把结果塞 body 里,前端自己看不就行了?
👨🏫 老师:能跑,但不专业。状态码是"机器可读的信号"——监控系统看到一片 4xx/5xx 就知道出问题了,浏览器缓存、重试策略也都认状态码。全返 200,等于把所有信息藏进 body,机器就瞎了。用对码,是让整个生态都能读懂你的结果。
分页查询
列表接口普遍接收一个查询对象(如 RoutePageQuery),用 @ParameterObject(Springdoc)把 URL 上的 ?pageNum=&pageSize=&name= 自动绑定成对象字段,返回 PaginatedResult。
@ParameterObject 让 Swagger 把查询对象的每个字段都展开成独立的查询参数文档,前端调用方一目了然。分页在 SDK 层实现(第 2 周会看到 CommonPageQuery/PaginatedResult)。今日小结 + 动手
🧠 今天你应该能回答
- 控制器的统一风格是什么?RESTful 的动词/URL 约定?
RoutesController.add的三步校验?- 为什么服务(Services)只读?
Response的三段式?ControllerUtil怎么定状态码?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/console/src/main/java/com/alibaba/higress/console
sed -n '71,120p' controller/RoutesController.java
sed -n '32,75p' controller/dto/Response.java
sed -n '46,78p' controller/util/ControllerUtil.java
ls controller/
ApiStandardizationAspect(traceId/鉴权/统一异常),以及 console 自身业务 service(登录、系统配置存 ConfigMap)。