网关管理接口.md 36 KB

网关管理接口

本文引用的文件

  • AdminController.java
  • AdminService.java
  • AdminAuthWebFilter.java
  • AdminApiException.java
  • ApiResult.java
  • StickyUnavailableException.java
  • StickyRouteFilter.java
  • GatewayRegistry.java
  • application.yaml
  • gateway.sql
  • ai-gateway-user-sticky-routing.md

目录

  1. 引言
  2. 项目结构与职责边界
  3. 认证与访问控制
  4. 跨域、限流与安全建议
  5. 数据模型说明
  6. 接口总览
  7. 详细接口定义
  8. 架构与调用关系
  9. 节点 DOWN 时返回 503 的排查指南
  10. 运维脚本集成建议
  11. 常见问题与故障定位
  12. 结论

引言

清鉴 AI 调查工具网关由独立模块 ai-gateway 提供,基于 Spring Cloud Gateway(WebFlux/Netty),默认监听端口 8080。它负责把前端请求按“用户粘性”转发到多台 ai-server 后端实例,并通过 PostgreSQL 持久化服务、节点和用户绑定关系。

AdminController 暴露 /gateway/admin/** REST 接口,用于维护 StickyRouteFilter 使用的 gateway_service 和 gateway_node 配置以及用户粘性路由。这些接口通常由运维人员或部署脚本调用,而不是普通业务客户端。

本 API 文档聚焦:

  • 服务注册、修改、删除;
  • 节点增删改查;
  • 节点健康检查;
  • 用户粘性路由分页查询、绑定、解绑、导入已有绑定;
  • 全局配置重载;
  • 认证方式、错误码、HTTP 状态码、响应体结构;
  • 当节点不可用时客户端收到 503 的排查方法。

项目结构与职责边界

ai-gateway 中与管理接口直接相关的代码分层如下:

graph TB
    Client["运维脚本或管理员"] --> Filter["AdminAuthWebFilter<br/>管理接口准入校验"]
    Filter --> Controller["AdminController<br/>REST 路由"]
    Controller --> Service["AdminService<br/>业务逻辑"]
    Service --> Registry["GatewayRegistry<br/>服务/节点内存快照"]
    Service --> ConfigRepo["GatewayConfigRepository<br/>PostgreSQL 写操作"]
    Service --> RouteRepo["UserRouteRepository<br/>用户绑定读写"]
    Service --> HealthChecker["HealthChecker<br/>手动探活"]
    Service --> HealthReg["NodeHealthRegistry<br/>节点健康快照"]

图示来源

  • AdminController.java:1-153
  • AdminService.java:1-200
  • GatewayRegistry.java:1-116
  • AdminAuthWebFilter.java:1-90

控制器只负责参数绑定、异常处理和阻塞操作调度;真实逻辑在 AdminService。所有 JDBC 调用都通过 Schedulers.boundedElastic() 执行,避免占用 Netty 事件循环线程。

章节来源

  • AdminController.java:1-153
  • AdminService.java:1-200

认证与访问控制

认证流程

管理接口不是“绕过 x-token”,而是“额外要求管理员身份”。AdminAuthWebFilter 对 /gateway/admin/** 路径生效,顺序为:

  1. 如果 gateway.admin.enabled=false,全部返回 404;
  2. 校验来源 IP 是否在 gateway.admin.ip-whitelist;
  3. 从请求头 x-token 解析 userId;
  4. 查询 sys_user,要求 is_admin=1 且状态正常;
  5. 否则返回 401 或 403。

因此,调用者仍需携带有效的 x-token,并且该 token 必须属于一个启用状态的管理员账号。

sequenceDiagram
    participant Script as "运维脚本"
    participant Filter as "AdminAuthWebFilter"
    participant Token as "TokenResolver"
    participant DB as "PostgreSQL"
    participant Controller as "AdminController"

    Script->>Filter: "/gateway/admin/** + x-token"
    Filter->>Filter: 检查 gateway.admin.enabled
    Filter->>Filter: 检查 IP 白名单
    Filter->>Token: 解析 x-token → userId
    Token->>DB: 查询 sys_user
    DB-->>Filter: 管理员用户信息
    Filter->>Controller: 放行
    Controller->>Controller: 执行业务接口
    Controller-->>Script: ApiResult

图示来源

  • AdminAuthWebFilter.java:1-90
  • AdminController.java:1-153

关键行为

场景 HTTP 状态码 说明
管理接口未启用 404 gateway.admin.enabled=false 时,管理接口整体消失
IP 不在白名单 403 即使 token 有效也会被拒绝
缺少 x-token 或 token 无效 401 未登录或登录已失效
非管理员或停用用户 403 需要管理员权限
控制器业务异常 400/404/409 由 AdminApiException 决定
请求参数绑定失败 400 由 WebExchangeBindException 处理

章节来源

  • AdminAuthWebFilter.java:1-90
  • AdminController.java:100-153
  • AdminApiException.java:1-30

跨域、限流与安全建议

跨域策略

默认情况下,网关不充当跨域收口:

  • gateway.cors.enabled=false;
  • 后端 ai-server 已有全局 CorsFilter;
  • 网关仅通过 DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE 去除重复响应头。

只有在确实需要网关统一处理跨域时才开启 gateway.cors.enabled=true,并配合 allowed-origin-patterns 限制来源。

章节来源

  • application.yaml:1-81

限流建议

当前 AdminController 没有内置限流逻辑。生产环境建议:

  • 在网关外层使用反向代理或云厂商网关做 IP 级限流;
  • 对写接口(创建、更新、删除、绑定、导入、重载)设置更严格的频率限制;
  • 将管理接口暴露范围限制在内网或运维网络;
  • 结合 gateway.admin.ip-whitelist 缩小来源 IP 范围;
  • 记录审计日志,至少保留操作人、IP、时间、接口、结果。

安全建议

  • 不要将 /gateway/admin 暴露在公网;
  • 白名单应精确到运维服务器地址或 CIDR;
  • 管理员账号应使用强密码并限制登录设备;
  • 定期轮换 x-token 对应的会话生命周期;
  • 不建议在生产环境关闭 IP 白名单;
  • 如需更强保护,可在网关前增加二次认证、跳板机或零信任入口。

数据模型说明

管理接口维护三类核心数据:服务、节点、用户粘性路由。

erDiagram
  GATEWAY_SERVICE ||--o{ GATEWAY_NODE : "包含"
  GATEWAY_SERVICE ||--o{ GATEWAY_USER_ROUTE : "拥有"
  GATEWAY_NODE ||--o{ GATEWAY_USER_ROUTE : "被绑定"

  GATEWAY_SERVICE {
    varchar id PK
    varchar name
    varchar path_prefix UK
    varchar login_path
    varchar health_path
    varchar default_node_id
    smallint enabled
    timestamp create_time
    timestamp update_time
  }

  GATEWAY_NODE {
    varchar id PK
    varchar service_id FK
    varchar name
    varchar base_url
    smallint enabled
    varchar remark
    timestamp create_time
    timestamp update_time
  }

  GATEWAY_USER_ROUTE {
    bigint id PK
    varchar service_id FK
    bigint user_id
    varchar username
    varchar node_id FK
    timestamp create_time
    timestamp update_time
  }

图示来源

  • gateway.sql:1-107

关键字段含义:

  • gateway_service.path_prefix:对外路径前缀,例如 /js/a,全局唯一,不能以 / 结尾,也不能是 /gateway;
  • gateway_service.login_path:相对 path_prefix 的登录路径,为空表示不做登录粘性;
  • gateway_service.default_node_id:存量用户兜底节点;
  • gateway_node.base_url:后端节点地址,不含上下文路径;
  • gateway_user_route.user_id 与 username:至少一个非空,分别建立部分唯一索引,保证同一用户在同一个服务下只绑定一个节点。

章节来源

  • gateway.sql:1-107

接口总览

所有接口基础路径为:

http://网关地址:8080/gateway/admin
接口 HTTP 方法 URL 用途 主要调用方
查看状态 GET /gateway/admin/status 获取服务、节点、健康状态汇总 运维监控脚本
查看服务列表 GET /gateway/admin/services 获取服务及节点快照 运维面板
创建服务 POST /gateway/admin/services 注册新的对外服务 部署脚本
更新服务 PUT /gateway/admin/services/{serviceId} 修改服务配置 运维脚本
删除服务 DELETE /gateway/admin/services/{serviceId} 删除无节点、无绑定的服务 运维脚本
创建节点 POST /gateway/admin/services/{serviceId}/nodes 为服务添加后端节点 部署脚本
更新节点 PUT /gateway/admin/nodes/{nodeId} 修改节点地址或备注 运维脚本
删除节点 DELETE /gateway/admin/nodes/{nodeId} 移除不再使用的节点 运维脚本
检查节点 POST /gateway/admin/nodes/{nodeId}/check 立即探测节点健康 运维脚本
分页查看用户路由 GET /gateway/admin/routes?serviceId=...&keyword=&page=&size= 查看用户粘性绑定 运维面板
绑定用户路由 POST /gateway/admin/routes/bind 为用户绑定节点 迁移脚本
解绑用户路由 DELETE /gateway/admin/routes/{id} 解除用户绑定 运维脚本
导入已有绑定 POST /gateway/admin/routes/import-existing 批量导入现有用户绑定 迁移脚本
重载配置 POST /gateway/admin/reload 强制刷新服务/节点缓存 运维脚本

章节来源

  • AdminController.java:25-99

详细接口定义

通用响应体

除明确说明外,管理接口统一返回 ApiResult:

字段 类型 说明
code int 成功为 200,失败为具体错误码
message string 人类可读消息
data object/null 业务数据;成功时可能为 null

常见错误码:

code HTTP 状态码 含义
200 200 成功
400 400 参数错误或资源冲突
401 401 未登录或 token 无效
403 403 非管理员或 IP 不在白名单
404 404 服务或节点不存在
409 409 存在依赖:仍有节点、仍有用户绑定等

章节来源

  • ApiResult.java:1-37
  • AdminApiException.java:1-30

GET /gateway/admin/status

查看网关当前加载的服务、节点及其健康状态。

  • 方法:GET
  • URL:/gateway/admin/status
  • 请求头:需要 x-token,且为管理员
  • 请求体:无
  • 成功响应 data 结构:
字段 类型 说明
services array 服务列表
services[].id string 服务 ID
services[].name string 服务名称
services[].pathPrefix string 对外路径前缀
services[].enabled int 是否启用
services[].defaultNodeId string/null 兜底节点 ID
services[].routeCount int 该服务的用户绑定数量
services[].nodes array 节点列表
services[].nodes[].id string 节点 ID
services[].nodes[].name string 节点名称
services[].nodes[].baseUrl string 后端地址
services[].nodes[].enabled int 是否启用
services[].nodes[].state string 健康状态:UP/DOWN/UNKNOWN 等
services[].nodes[].lastError string/null 最近一次探活错误
services[].nodes[].lastCheckedAt timestamp/null 最近探活时间
services[].nodes[].routeCount int 该节点的绑定用户数
  • 错误响应:
HTTP 状态码 code 触发条件
401 401 缺少 x-token 或 token 无效
403 403 IP 不在白名单或非管理员

curl 示例:

curl -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/status"

章节来源

  • AdminController.java:31-38
  • AdminService.java:35-82

GET /gateway/admin/services

获取服务与节点快照,便于快速查看当前网关加载了哪些服务和节点。

  • 方法:GET
  • URL:/gateway/admin/services
  • 请求头:需要 x-token,且为管理员
  • 请求体:无
  • 成功响应 data:与服务列表一致,即 services 数组。

curl 示例:

curl -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/services"

章节来源

  • AdminController.java:40-45

POST /gateway/admin/services

创建服务。

  • 方法:POST
  • URL:/gateway/admin/services
  • 请求头:需要 x-token,且为管理员
  • 请求体 JSON:
字段 类型 必填 说明
id string 是 服务 ID,全局唯一
name string 是 服务名称
pathPrefix string 是 对外路径前缀,例如 /js/a
loginPath string/null 否 相对 pathPrefix 的登录路径
healthPath string 是 完整健康检查路径,例如 /js/a/sys/health
defaultNodeId string/null 否 存量用户兜底节点 ID
enabled int 否 是否启用,默认启用
  • 成功响应:code=200,data=null
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 服务已存在或 path-prefix 被其他服务占用
401 401 未登录或 token 无效
403 403 IP 不在白名单或非管理员

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "zsjz-ai",
    "name": "智能分析后端",
    "pathPrefix": "/js/a",
    "loginPath": "/auth/login",
    "healthPath": "/js/a/sys/health",
    "defaultNodeId": "node-1",
    "enabled": 1
  }' \
  "http://localhost:8080/gateway/admin/services"

章节来源

  • AdminController.java:47-56
  • AdminService.java:84-103

PUT /gateway/admin/services/{serviceId}

更新服务。

  • 方法:PUT
  • URL:/gateway/admin/services/{serviceId}
  • 路径参数:
参数 类型 说明
serviceId string 要更新的服务 ID
  • 请求体:与创建服务相同,但 id 可省略或由路径决定;实际实现仍会读取 DTO 中的 id 字段。
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 path-prefix 被其他服务占用
404 404 服务不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X PUT -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "zsjz-ai",
    "name": "智能分析后端",
    "pathPrefix": "/js/a",
    "loginPath": "/auth/login",
    "healthPath": "/js/a/sys/health",
    "defaultNodeId": "node-1",
    "enabled": 1
  }' \
  "http://localhost:8080/gateway/admin/services/zsjz-ai"

章节来源

  • AdminController.java:58-67
  • AdminService.java:105-116

DELETE /gateway/admin/services/{serviceId}

删除服务。只有满足以下条件才能删除:

  • 服务存在;
  • 服务下没有节点;
  • 服务下没有用户绑定。

  • 方法:DELETE

  • URL:/gateway/admin/services/{serviceId}

  • 成功响应:code=200

  • 错误响应:

HTTP 状态码 code 触发条件
404 404 服务不存在
409 409 服务下仍有节点或用户绑定
401/403 401/403 认证或权限失败

curl 示例:

curl -X DELETE -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/services/zsjz-ai"

章节来源

  • AdminController.java:69-74
  • AdminService.java:118-131

POST /gateway/admin/services/{serviceId}/nodes

为服务新增节点。

  • 方法:POST
  • URL:/gateway/admin/services/{serviceId}/nodes
  • 路径参数:
参数 类型 说明
serviceId string 目标服务 ID
  • 请求体 JSON:
字段 类型 必填 说明
id string 是 节点 ID,全局唯一
name string 是 节点名称
baseUrl string 是 后端地址,例如 http://host:port
enabled int 否 是否启用
remark string/null 否 备注
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 节点 ID 已存在或服务下 base-url 已存在
404 404 服务不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "node-1",
    "name": "节点1",
    "baseUrl": "http://192.168.0.101:8980",
    "enabled": 1,
    "remark": "主节点"
  }' \
  "http://localhost:8080/gateway/admin/services/zsjz-ai/nodes"

章节来源

  • AdminController.java:76-84
  • AdminService.java:133-153

PUT /gateway/admin/nodes/{nodeId}

更新节点。

  • 方法:PUT
  • URL:/gateway/admin/nodes/{nodeId}
  • 路径参数:
参数 类型 说明
nodeId string 要更新的节点 ID
  • 请求体:与创建节点相同。
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 同服务下 base-url 已存在
404 404 节点不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X PUT -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "node-1",
    "name": "节点1",
    "baseUrl": "http://192.168.0.101:8980",
    "enabled": 1,
    "remark": "主节点"
  }' \
  "http://localhost:8080/gateway/admin/nodes/node-1"

章节来源

  • AdminController.java:86-93
  • AdminService.java:155-168

DELETE /gateway/admin/nodes/{nodeId}

删除节点。前提是该节点没有被任何用户绑定。

  • 方法:DELETE
  • URL:/gateway/admin/nodes/{nodeId}
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
404 404 节点不存在
409 409 节点上仍有用户绑定
401/403 401/403 认证或权限失败

curl 示例:

curl -X DELETE -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/nodes/node-1"

章节来源

  • AdminController.java:95-100
  • AdminService.java:170-179

POST /gateway/admin/nodes/{nodeId}/check

立即探测指定节点的健康状态,不等待定时任务。

  • 方法:POST
  • URL:/gateway/admin/nodes/{nodeId}/check
  • 成功响应 data:
字段 类型 说明
nodeId string 节点 ID
state string 探测后的健康状态
  • 错误响应:
HTTP 状态码 code 触发条件
404 404 节点不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/nodes/node-1/check"

章节来源

  • AdminController.java:102-107
  • AdminService.java:181-198

GET /gateway/admin/routes

分页查询用户粘性路由。

  • 方法:GET
  • URL:/gateway/admin/routes
  • 查询参数:
参数 类型 必填 默认值 说明
serviceId string 是 — 服务 ID
keyword string 否 — 模糊关键词
page int 否 0 页码
size int 否 20 每页大小
  • 成功响应 data:
字段 类型 说明
total int 总记录数
items array 路由条目列表
  • 错误响应:
HTTP 状态码 code 触发条件
401/403 401/403 认证或权限失败

curl 示例:

curl -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/routes?serviceId=zsjz-ai&page=0&size=20"

章节来源

  • AdminController.java:109-117
  • AdminService.java:200-200

POST /gateway/admin/routes/bind

绑定用户到节点。

  • 方法:POST
  • URL:/gateway/admin/routes/bind
  • 请求体 JSON:
字段 类型 必填 说明
serviceId string 是 服务 ID
userId long/string 二选一 用户 ID
username string 二选一 用户名
nodeId string 是 目标节点 ID
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 参数缺失或绑定失败
404 404 服务或节点不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "zsjz-ai",
    "userId": 1,
    "nodeId": "node-1"
  }' \
  "http://localhost:8080/gateway/admin/routes/bind"

章节来源

  • AdminController.java:119-126
  • AdminService.java:200-200

DELETE /gateway/admin/routes/{id}

解绑用户路由。

  • 方法:DELETE
  • URL:/gateway/admin/routes/{id}
  • 路径参数:
参数 类型 说明
id long 用户路由记录 ID
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
401/403 401/403 认证或权限失败

curl 示例:

curl -X DELETE -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/routes/1"

章节来源

  • AdminController.java:128-133
  • AdminService.java:200-200

POST /gateway/admin/routes/import-existing

批量导入已有用户绑定。

  • 方法:POST
  • URL:/gateway/admin/routes/import-existing
  • 请求体 JSON:
字段 类型 必填 说明
serviceId string 是 目标服务 ID
nodeId string 是 目标节点 ID
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
400 400 参数缺失或导入失败
404 404 服务或节点不存在
401/403 401/403 认证或权限失败

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "zsjz-ai",
    "nodeId": "node-1"
  }' \
  "http://localhost:8080/gateway/admin/routes/import-existing"

章节来源

  • AdminController.java:135-144

POST /gateway/admin/reload

强制刷新服务与节点缓存。

  • 方法:POST
  • URL:/gateway/admin/reload
  • 成功响应:code=200
  • 错误响应:
HTTP 状态码 code 触发条件
401/403 401/403 认证或权限失败

curl 示例:

curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/reload"

章节来源

  • AdminController.java:146-151
  • GatewayRegistry.java:72-96

架构与调用关系

管理接口调用链

sequenceDiagram
    participant Admin as "管理员或脚本"
    participant Filter as "AdminAuthWebFilter"
    participant Controller as "AdminController"
    participant Service as "AdminService"
    participant Registry as "GatewayRegistry"
    participant DB as "PostgreSQL"

    Admin->>Filter: 请求 /gateway/admin/**
    Filter->>Filter: 检查 enabled、IP、x-token、管理员
    Filter->>Controller: 放行
    Controller->>Service: 执行业务方法
    Service->>DB: 读写 gateway_service / gateway_node / gateway_user_route
    Service->>Registry: reload()
    Registry->>Registry: 发布 RefreshRoutesEvent
    Registry-->>Service: 返回
    Service-->>Controller: 成功
    Controller-->>Admin: ApiResult

图示来源

  • AdminAuthWebFilter.java:1-90
  • AdminController.java:25-151
  • AdminService.java:1-200
  • GatewayRegistry.java:1-116

粘性路由与节点 DOWN 的关系

StickyRouteFilter 是普通业务请求的核心过滤器。它根据 x-token 找到用户绑定的节点后,会严格校验:

  1. 节点是否存在;
  2. 节点是否启用;
  3. 节点是否健康;
  4. 若任一条件失败,抛出 StickyUnavailableException,最终转为 HTTP 503。

    flowchart TD
    Start["进入 StickyRouteFilter"] --> MatchService["匹配服务"]
    MatchService --> IsLogin{"是否为登录请求"}
    IsLogin -->|是| LoginDecide["按 username 查找绑定节点"]
    IsLogin -->|否| NormalDecide["按 x-token 解析 userId 并查找绑定节点"]
    LoginDecide --> FoundNode{"找到绑定节点?"}
    NormalDecide --> FoundNode
    FoundNode -->|是| ValidateNode["校验节点启用、健康"]
    FoundNode -->|否| DefaultNode{"是否有默认节点?"}
    DefaultNode -->|有| BindDefault["绑定到默认节点"]
    DefaultNode -->|无| Error503["返回 503:用户未绑定节点"]
    ValidateNode --> NodeUp{"节点 UP?"}
    NodeUp -->|是| Forward["重写目标 URL 并转发"]
    NodeUp -->|否| Error503
    BindDefault --> ValidateNode
    Forward --> End["返回响应,附加 X-Gateway-Node"]
    Error503 --> End
    

图示来源

  • StickyRouteFilter.java:1-180
  • StickyUnavailableException.java:1-25

章节来源

  • StickyRouteFilter.java:1-180
  • StickyUnavailableException.java:1-25

节点 DOWN 时返回 503 的排查指南

当某个用户绑定节点的状态变为 DOWN 时,该用户的后续请求会返回 503,而不是自动切换到其他健康节点。这是设计决策:案件数据、附件、Agent 记忆等落在各节点本地磁盘,切到其他机器会导致“当前案件数据源未打开或已释放”等问题。

排查步骤

  1. 确认用户是否绑定了节点
    调用:

    curl -H "x-token: YOUR_ADMIN_TOKEN" \
     "http://localhost:8080/gateway/admin/routes?serviceId=zsjz-ai&keyword=USERNAME_OR_USERID&page=0&size=100"
    
  2. 查看服务与节点健康状态
    调用:

    curl -H "x-token: YOUR_ADMIN_TOKEN" \
     "http://localhost:8080/gateway/admin/status"
    

关注:

  • services[].nodes[].state;
  • services[].nodes[].lastError;
  • services[].nodes[].lastCheckedAt。
  1. 手动探测节点
    调用:

    curl -X POST -H "x-token: YOUR_ADMIN_TOKEN" \
     "http://localhost:8080/gateway/admin/nodes/NODE_ID/check"
    

如果返回的 state 仍为 DOWN,说明节点健康检查失败。

  1. 检查节点后端是否可达
    使用 baseUrl 拼接 healthPath,从网关所在机器发起探测。例如:

    curl -v "http://192.168.0.101:8980/js/a/sys/health"
    
  2. 判断是否需要迁移用户
    如果节点已下线,应先:

    • 将该节点上的用户解绑或重绑到新节点;
    • 确认新节点健康;
    • 再删除旧节点;
    • 最后让受影响用户重新登录,使粘性路由落到可用节点。
  3. 注意:不要期望 503 后自动重试其他节点
    当前实现不会因绑定节点 DOWN 而切换。运维必须通过管理接口调整绑定。

相关错误语义

现象 原因 处理方式
单个用户请求 503 该用户绑定的节点 DOWN 修复节点或迁移用户绑定
新用户登录 503 服务没有可用节点 先添加健康节点
匿名请求 503 服务暂无健康节点 检查节点配置与健康检查
用户未绑定节点 503 用户既无绑定也无默认节点 引导用户重新登录或预先绑定

章节来源

  • StickyRouteFilter.java:63-170
  • StickyUnavailableException.java:1-25
  • ai-gateway-user-sticky-routing.md:1-200

运维脚本集成建议

推荐工作流

  1. 启动时检查管理接口是否可用;
  2. 使用 GET /gateway/admin/status 拉取当前服务与节点;
  3. 若服务不存在,调用 POST /gateway/admin/services 创建;
  4. 为每个后端实例调用 POST /gateway/admin/services/{serviceId}/nodes 添加节点;
  5. 可选:调用 POST /gateway/admin/nodes/{nodeId}/check 验证节点健康;
  6. 对于存量用户,调用 POST /gateway/admin/routes/import-existing 或 POST /gateway/admin/routes/bind 完成迁移;
  7. 调用 POST /gateway/admin/reload 确保缓存刷新;
  8. 再次调用 GET /gateway/admin/status 验证结果。

幂等性建议

  • 创建服务/节点前应先用 GET /gateway/admin/services 判断是否已存在;
  • 更新服务/节点时使用 PUT,并根据返回值判断是否真正变更;
  • 删除服务/节点前检查依赖;
  • 绑定用户路由前先查询是否已绑定;
  • 脚本应具备重试和失败告警机制。

多网关实例说明

GatewayRegistry 支持多实例共享 PostgreSQL:

  • 写侧调用 reload() 后立即刷新自身缓存;
  • 其他实例通过定时任务轮询数据库,默认间隔 gateway.registry-poll-interval,默认 10 秒;
  • 配置变化时会发布 RefreshRoutesEvent 刷新路由缓存。

因此,单点写入后其他实例不会立即生效,最多延迟一个轮询周期。

章节来源

  • GatewayRegistry.java:18-116
  • AdminService.java:1-200

常见问题与故障定位

问题一:管理接口返回 404

可能原因:

  • gateway.admin.enabled=false;
  • 外部反向代理没有暴露 /gateway/admin;
  • 请求目标不是网关端口 8080。

排查命令:

curl -v "http://localhost:8080/gateway/admin/status"

章节来源

  • AdminAuthWebFilter.java:40-55

问题二:管理接口返回 403

可能原因:

  • 来源 IP 不在 gateway.admin.ip-whitelist;
  • 当前用户不是管理员;
  • 当前用户状态被停用。

排查命令:

curl -H "x-token: YOUR_ADMIN_TOKEN" \
  "http://localhost:8080/gateway/admin/status"

章节来源

  • AdminAuthWebFilter.java:56-85

问题三:创建服务报 path-prefix 冲突

说明另一个服务已经占用了相同的对外路径前缀。需要先修改冲突服务的路径前缀,或删除冲突服务。

章节来源

  • AdminService.java:94-103

问题四:删除服务或节点报 409

删除服务前必须:

  • 先删除其下所有节点;
  • 先解绑其下所有用户。

删除节点前必须:

  • 先解绑或重绑该节点上的用户。

章节来源

  • AdminService.java:118-179

问题五:用户请求 503 且无法自动恢复

这是粘性路由的预期行为。只要用户绑定的节点不可用,网关就不会自动切换。运维必须:

  • 恢复节点;
  • 或迁移用户绑定;
  • 或让用户重新登录。

章节来源

  • StickyRouteFilter.java:120-170
  • ai-gateway-user-sticky-routing.md:1-200

结论

AdminController 暴露的 /gateway/admin/** 接口是清鉴 AI 网关的配置面,用于维护 gateway_service、gateway_node 和 gateway_user_route 数据。它们由运维人员或部署脚本调用,负责:

  • 注册和管理对外服务;
  • 管理后端节点;
  • 查看和维护用户粘性路由;
  • 手动探测节点健康;
  • 刷新网关路由缓存。

与普通业务请求不同,这些接口需要管理员身份,并受 IP 白名单、x-token、管理员权限等多重保护。当用户绑定的节点 DOWN 时,网关返回 503 且不自动切换,运维必须通过管理接口调整绑定或恢复节点。