# Cloud WAF — 全量文档(机器可读) Cloud WAF 是企业 Web 防护管理平台,提供域名接入、WAF/CC/ACL 防护、证书管理、流量分析、告警通知与 CLI 自动化能力。后端 Go + Gin REST API,前端 Vue 3 SPA,CLI 工具 zcloud(Cobra)。本文聚合所有用户文档,供 AI agent 一次性下载消化。各文档以 `---` 分隔。 --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 5 大模块:guard / sys / analytics / cli_release / auth > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # 30 秒接入指南 ## 产品定位 Cloud WAF 是一个**企业级 Web 防护管理平台**,提供域名接入、证书管理、WAF 规则、CC/ACL 防护、转发策略、统计大屏等一体化能力。系统由 Go 后端(Gin)+ Vue 3 前端 + Cobra CLI(`zcloud`)组成。 任何 Web 服务接入 Cloud WAF 后,可以通过 Web 控制台、CLI、或 REST API 三种方式管理;本指南面向开发者,演示如何在 30 秒内通过 CLI 完成第一次调用。 ## 前置条件 - 已部署的 Cloud WAF 服务地址(如 `https://waf.example.com`) - 一个具备访问权限的账号(用户名 + 密码) - Linux / macOS 终端,已安装 `curl` 和 `bash` ## 三步走 ### 第 1 步:下载并安装 CLI **方式 A — 一键脚本(推荐):** ```bash curl -fsSL https://waf.example.com/api/cli/install.sh | sh ``` 脚本会自动检测当前操作系统和架构(linux/amd64、linux/arm64、darwin/amd64、darwin/arm64),从 `/api/cli/version` 拉取最新二进制并安装到 `/usr/local/bin/zcloud`。 **方式 B — 手动下载:** ```bash # 1) 查询最新版本和下载 URL curl -s https://waf.example.com/api/cli/version # 2) 下载二进制(替换为返回值里的 download_url) curl -fsSL -o /tmp/zcloud "" chmod +x /tmp/zcloud sudo mv /tmp/zcloud /usr/local/bin/zcloud # 3) 验证安装 zcloud version ``` ### 第 2 步:登录拿到会话 ```bash zcloud config set api_url https://waf.example.com # 交互式登录:会依次提示输入 Username / Password(密码不回显) zcloud auth login ``` 成功后凭据会保存到本地 `~/.zcloud/credentials.toml`,后续所有命令自动携带会话 token。 > 如使用自签证书,请改用 `zcloud --insecure auth login` 跳过 TLS 校验。 如需多套环境,使用 profile: ```bash # profiles create 只接受位置参数 ,不支持任何 flag zcloud config profiles create prod zcloud --profile prod config set api_url https://prod.example.com zcloud config profiles activate prod ``` ### 第 3 步:运行第一个命令 ```bash # 列出当前账号管理的域名 zcloud guard domains list # 查看自己身份和权限 zcloud auth info # 看 CLI 完整命令树 zcloud --help ``` ## 常见错误处理 | 现象 | 原因 | 处理 | |------|------|------| | `401 Unauthorized` | 会话过期 | 重新执行 `zcloud auth login` | | `403 Forbidden` | 当前账号无该资源权限 | 联系管理员授予对应权限 key | | `connection refused` | api_url 错误或服务不可达 | 检查 `zcloud config get api_url` | | `x509: certificate signed by unknown authority` | 自签证书 | 加 `--insecure` 标志或导入 CA | ## 输出格式切换 所有命令都支持 `--format` 切换输出: ```bash zcloud guard domains list --format table # 默认,人类友好 zcloud guard domains list --format json # 机器可读 zcloud guard domains list --format yaml # 配置友好 ``` ## 下一步 - [CLI 完整命令清单](/docs/cli) — 98 条命令全树 - [API 文档](/docs/api) — REST 接口详解 - [认证说明](/docs/auth) — Bearer 会话、API Key 与 401 重试机制 - [示例代码](/docs/examples) — curl / Python / Go 三语示例 - [权限矩阵](/docs/permissions) — Resource × Action 全集 --- *Cloud WAF · zcloud CLI · 文档版本与 API 同步发布* --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 6 大模块:auth / config / sys / guard / analytics / tools > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # CLI 完整命令清单 ## zcloud 是什么 `zcloud` 是 Cloud WAF 平台官方 CLI,使用 Cobra 框架,覆盖管理控制台 100% 的核心运维操作,共 98 条命令分布在 6 大模块(auth / config / sys / guard / analytics / tools)。 二进制由后端 `/api/cli/version` 接口动态分发,支持 linux/darwin × amd64/arm64。 ## 安装 参考 [快速开始](/docs/quickstart#第-1-步下载并安装-cli),或: ```bash curl -fsSL /api/cli/install.sh | sh zcloud version ``` ## 通用 flags 下列 flags 在**所有命令**上有效(继承自根命令): | Flag | 类型 | 说明 | |------|------|------| | `--api-url` | string | 覆盖 api_url,单次调用使用临时地址 | | `--insecure` | bool | 跳过 TLS 证书校验(自签场景) | | `--profile` | string | 使用指定 profile(默认读 active 配置) | | `--format` | enum | 输出格式 `table` / `json` / `yaml`(默认 table) | | `--verbose` | bool | 打印请求详情,便于排障 | | `--quiet` | bool | 静默模式:屏蔽成功提示与分页信息,不影响数据 stdout 与错误 stderr | | `--yes` / `-y` | bool | 对所有交互确认自动回答 yes(等价于每个删除命令都加 `-f`) | | `--help` / `-h` | bool | 输出当前命令的帮助 | ## 1. 认证命令(auth) | 命令 | 说明 | 示例 | |------|------|------| | `zcloud auth login` | 交互式登录(提示输入用户名/密码,密码不回显),凭证写入 `~/.zcloud/credentials.toml` | `zcloud auth login` | | `zcloud --api-url auth login` | 临时覆盖 API 地址后再登录 | `zcloud --api-url https://waf.example.com auth login` | | `zcloud --insecure auth login` | 跳过 TLS 校验登录(仅自签证书内网测试) | `zcloud --insecure auth login` | | `zcloud auth logout` | 注销当前会话,清除本地 token | `zcloud auth logout` | | `zcloud auth info` | 查询当前账号信息和权限 | `zcloud auth info --format json` | | `zcloud auth apikey create --name N [--scopes K1,K2] [--expires-days N]` | 签发新 API Key(明文仅返回一次,请立即保存) | `zcloud auth apikey create --name 生产对接 --expires-days 30` | | `zcloud auth apikey list [--page N] [--size N]` | 查看自己/本 OEM 的 API Key 列表 | `zcloud auth apikey list --format json` | | `zcloud auth apikey revoke [-f]` | 吊销 API Key(幂等,软删保留审计) | `zcloud auth apikey revoke 8f21c0c5-... -f` | | `zcloud auth apikey renew --add-days N` | 续期 API Key 过期时间(--add-days 必填,1-365 天) | `zcloud auth apikey renew 8f21c0c5-... --add-days 90` | | `zcloud auth apikey revoke-all [--user-id U] [--reason R] [-f]` | 一键吊销 user 名下全部 active API Key(应急止损;--user-id 仅平台级用户有效,留空=自己) | `zcloud auth apikey revoke-all -f --reason "凭据可能泄露"` | | `zcloud auth apikey logs [--event call\|manage] [--page N] [--size N\|--limit N]` | 查看指定 Key 的审计日志(默认 event=call,调用流水) | `zcloud auth apikey logs 8f21c0c5-... --limit 50` | | `zcloud auth apikey stats [--since 24h\|7d\|30m]` | 查看指定 Key 的聚合统计(成功率/错误分布/top endpoints/最近1h QPS) | `zcloud auth apikey stats 8f21c0c5-... --since 24h` | 绑定 API:`POST /api/auth/login` / `POST /api/auth/logout` / `GET /api/auth/userinfo` / `GET\|POST\|DELETE /api/sys/api-keys[/]` / `PUT /api/sys/api-keys//renew` / `POST /api/sys/api-keys/revoke-all` / `GET /api/sys/api-keys//logs` / `GET /api/sys/api-keys//stats` > **API Key 调用约定**:请求头使用 `Authorization: ApiKey zck_.`(与 Bearer session 并行的双通道)。effective_perms = user.RBAC ∩ key.scope,scope 为空 = 完整继承 user 权限。 ## 2. 配置命令(config) | 命令 | 说明 | 示例 | |------|------|------| | `zcloud config set ` | 设置当前 profile 的配置项 | `zcloud config set api_url https://waf.example.com` | | `zcloud config get ` | 读取当前 profile 的配置项 | `zcloud config get api_url` | | `zcloud config list` | 列出当前 profile 的所有配置 | `zcloud config list` | | `zcloud config profiles list` | 列出所有 profile | `zcloud config profiles list` | | `zcloud config profiles create ` | 创建空 profile(无 flag;首个 profile 会自动激活) | `zcloud config profiles create prod` | | `zcloud config profiles activate ` | 切换激活 profile | `zcloud config profiles activate prod` | | `zcloud config profiles delete ` | 删除 profile | `zcloud config profiles delete dev` | > 创建 profile 后,请用 `zcloud --profile config set api_url ` 单独写入 api_url 等配置; > `profiles create` 命令本身只接收一个位置参数 ``,**不接受任何 flag**。 config 命令均为 local 操作,不调用后端 API。 ## 3. sys 模块(系统管理) ### 3.1 用户管理 sys users | 命令 | 说明 | 示例 | |------|------|------| | `zcloud sys users list [--page N] [--size N] [--keyword K]` | 分页查询用户 | `zcloud sys users list --page 1 --size 20` | | `zcloud sys users describe ` | 查看用户详情 | `zcloud sys users describe abc-123-...` | | `zcloud sys users create --username U --password P [--nick-name N] [--email E] [--mobile M] [--comment C]` | 创建用户(`--username` 和 `--password` 必填;**没有 `--oem-id` / `--display-name` 之类的 flag**) | `zcloud sys users create --username u1 --password '***' --nick-name '运维 A'` | | `zcloud sys users update [--nick-name N] [--email E] [--mobile M] [--comment C]` | 编辑用户基础信息(按 flag 增量更新) | `zcloud sys users update abc-123 --nick-name '运维 A'` | | `zcloud sys users delete [-f]` | 删除用户(`-f` 跳过确认;亦可全局 `-y`) | `zcloud sys users delete abc-123 -f` | | `zcloud sys users reset-password --password NEW` | 重置密码(`--password` 必填) | `zcloud sys users reset-password abc-123 --password 'New***'` | | `zcloud sys users lock [--locked true\|false]` | 锁定/解锁账号(`--locked` 默认 true) | `zcloud sys users lock abc-123 --locked false` | | `zcloud sys users assign-roles --role-ids 1,2,3` | 分配角色(必填 `--role-ids`,逗号分隔) | `zcloud sys users assign-roles abc-123 --role-ids 5,6` | ### 3.1.1 僵尸微信绑定治理 sys wx-zombies | 命令 | 说明 | 示例 | |---|---|---| | `zcloud sys wx-zombies cleanup [--service-ids ID1,ID2] [--max-pages N]` | dry-run 扫描本地仍绑定、但不在微信公众号关注者列表中的 openid | `zcloud sys wx-zombies cleanup --max-pages 50` | | `zcloud sys wx-zombies cleanup --apply [--max-pages N]` | 执行清理:清空 `users.wx_union_id/wx_nick_name`、对应 `alert_receivers.weixin` 和 `weixin_userinfo` | `zcloud sys wx-zombies cleanup --apply --max-pages 50` | 安全约束:默认使用微信 `user/get` 关注者列表做差集,不逐个调用 `user/info`;达到 `--max-pages` 会中止,避免误清理。若微信关注者列表为空但本地存在绑定,默认拒绝 `--apply`,除非显式加 `--allow-empty-followers`。 ### 3.2 角色管理 sys roles | 命令 | 说明 | 示例 | |------|------|------| | `zcloud sys roles list [--page N] [--size N] [--keyword K]` | 列出角色 | `zcloud sys roles list` | | `zcloud sys roles create --name N [--level L] [--comment C]` | 创建角色(`--level` 默认 1) | `zcloud sys roles create --name 运维员 --level 10` | | `zcloud sys roles update [--name N] [--level L] [--comment C]` | 修改角色(按 flag 增量更新) | `zcloud sys roles update 5 --name 高级运维员` | | `zcloud sys roles delete [-f]` | 删除角色(`-f` 跳过确认;亦可全局 `-y`) | `zcloud sys roles delete 5 -f` | | `zcloud sys roles get-permissions ` | 查询角色已有权限 | `zcloud sys roles get-permissions 5` | | `zcloud sys roles set-permissions --permissions K1,K2,K3` | 覆盖式设置权限(必填) | `zcloud sys roles set-permissions 5 --permissions guard.domain.list,guard.domain.view` | ### 3.3 OEM 管理 sys oems | 命令 | 说明 | 示例 | |------|------|------| | `zcloud sys oems list [--page N] [--size N] [--keyword K]` | 列出 OEM | `zcloud sys oems list` | | `zcloud sys oems describe ` | OEM 详情 | `zcloud sys oems describe oem-abc` | | `zcloud sys oems create --name N --hostname H [--tag T] [--comment C]` | 创建 OEM | `zcloud sys oems create --name 客户A --hostname a.example.com` | | `zcloud sys oems update [--name N] [--hostname H] [--tag T] [--comment C]` | 修改 OEM(按 flag 增量更新) | `zcloud sys oems update oem-abc --name '新名'` | | `zcloud sys oems delete [-f]` | 删除 OEM | `zcloud sys oems delete oem-abc -f` | | `zcloud sys oems get-setting ` | 查 OEM 单项配置 | `zcloud sys oems get-setting oem-abc theme` | | `zcloud sys oems set-setting ` | 写 OEM 单项配置 | `zcloud sys oems set-setting oem-abc theme dark` | ### 3.4 会话管理 sys sessions | 命令 | 说明 | 示例 | |------|------|------| | `zcloud sys sessions list [--page N] [--size N] [--keyword K]` | 列出在线会话 | `zcloud sys sessions list` | | `zcloud sys sessions kill [-f]` | 强制下线会话 | `zcloud sys sessions kill sess-xxx -f` | ### 3.5 审计日志 sys audit-logs | 命令 | 说明 | 示例 | |------|------|------| | `zcloud sys audit-logs list [--page N] [--size N] [--keyword K]` | 操作审计日志 | `zcloud sys audit-logs list --page 1 --size 50` | | `zcloud sys audit-logs login-records list [--page N] [--size N] [--keyword K]` | 登录历史 | `zcloud sys audit-logs login-records list` | | `zcloud sys audit-logs permissions` | 权限树(含 i18n 名) | `zcloud sys audit-logs permissions --format json` | ## 4. guard 模块(Web 防护) ### 4.1 域名 guard domains | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard domains list [--page N] [--size N] [--keyword K]` | 列出域名 | `zcloud guard domains list` | | `zcloud guard domains describe ` | 域名详情 | `zcloud guard domains describe 12` | | `zcloud guard domains create --domain D [--asset-name A] [--policy-id P]` | 创建域名(域名为必填) | `zcloud guard domains create --domain api.example.com --asset-name 主站 --policy-id 1` | | `zcloud guard domains update [--asset-name A] [--policy-id P]` | 修改域名(按 flag 增量更新;至少一项) | `zcloud guard domains update 12 --policy-id 2` | | `zcloud guard domains delete [-f]` | 删除域名 | `zcloud guard domains delete 12 -f` | | `zcloud guard domains get-settings ` | 读域名详细配置 | `zcloud guard domains get-settings 12` | | `zcloud guard domains set-settings --key K --value V` | 写域名单项配置(KV 模式,**不是 JSON 文件**) | `zcloud guard domains set-settings 12 --key cc_protect --value enable` | ### 4.2 证书 guard certs | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard certs list [--page N] [--size N] [--keyword K]` | 列出证书 | `zcloud guard certs list` | | `zcloud guard certs describe ` | 证书详情 | `zcloud guard certs describe 3` | | `zcloud guard certs upload --name N --cert F --key F [--sign-cert F] [--sign-key F]` | 上传证书(PEM;可选签名证书 / 国密双证) | `zcloud guard certs upload --name star --cert ./fullchain.pem --key ./private.key` | | `zcloud guard certs update [--name N] [--cert F] [--key F] [--sign-cert F] [--sign-key F]` | 替换证书(按 flag 增量更新) | `zcloud guard certs update 3 --cert ./new.pem --key ./new.key` | | `zcloud guard certs delete [-f]` | 删除证书 | `zcloud guard certs delete 3 -f` | | `zcloud guard certs bind --domain-id D` | 证书绑定到域名(必填 `--domain-id`) | `zcloud guard certs bind 3 --domain-id 12` | | `zcloud guard certs unbind --domain-id D` | 证书解绑域名(必填 `--domain-id`) | `zcloud guard certs unbind 3 --domain-id 12` | ### 4.3 策略 guard policies | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard policies list [--page N] [--size N] [--keyword K]` | 列出策略 | `zcloud guard policies list` | | `zcloud guard policies describe ` | 策略详情 | `zcloud guard policies describe 8` | | `zcloud guard policies create --name N [--comment C]` | 创建策略 | `zcloud guard policies create --name p1` | | `zcloud guard policies update [--name N] [--comment C]` | 修改策略 | `zcloud guard policies update 8 --name p1-v2` | | `zcloud guard policies delete [-f]` | 删除策略 | `zcloud guard policies delete 8 -f` | ### 4.4 黑白名单 guard bwlist | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard bwlist sets list [--page N] [--size N] [--keyword K]` | 列出名单集合 | `zcloud guard bwlist sets list` | | `zcloud guard bwlist sets create --name N --type black\|white [--policy-id P] [--describe D]` | 创建名单集合(`--name` 和 `--type` 必填) | `zcloud guard bwlist sets create --name block-cn --type black` | | `zcloud guard bwlist sets update [--name N] [--status enable\|disable] [--describe D]` | 修改名单集合 | `zcloud guard bwlist sets update 5 --name allow-asia` | | `zcloud guard bwlist sets delete [-f]` | 删除名单集合 | `zcloud guard bwlist sets delete 5 -f` | | `zcloud guard bwlist ips list [--page N] [--size N]` | 列出集合下的 IP | `zcloud guard bwlist ips list 5` | | `zcloud guard bwlist ips add --ip IP` | 加入单个 IP(必填 `--ip`) | `zcloud guard bwlist ips add 5 --ip 1.2.3.4` | | `zcloud guard bwlist ips batch-add --ips IP1,IP2,...` | 批量加 IP(逗号分隔字符串,**不接受 `--file`**) | `zcloud guard bwlist ips batch-add 5 --ips 1.2.3.4,5.6.7.8` | | `zcloud guard bwlist ips delete [-f]` | 删除单个 IP | `zcloud guard bwlist ips delete 1234 -f` | | `zcloud guard bwlist ips batch-delete --ip-ids ID1,ID2,...` | 批量删 IP(逗号分隔的 IP 条目 ID,限定集合内) | `zcloud guard bwlist ips batch-delete 5 --ip-ids 100,101,102 -f` | ### 4.5 WAF 规则 guard waf | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard waf rules list [--page N] [--size N] [--policy-id P]` | 列出规则组 | `zcloud guard waf rules list` | | `zcloud guard waf rules create --name N --policy-id P [--describe D] [--scope S] [--action 1\|2\|3] [--status 1\|2] [--waf-type N]` | 创建规则组(`--name` 和 `--policy-id` 必填;动作 1=block / 2=log / 3=captcha) | `zcloud guard waf rules create --name sqli-1 --policy-id 8 --action 1` | | `zcloud guard waf rules update [--name N] [--describe D] [--scope S] [--action N] [--status N] [--waf-type N]` | 修改规则组 | `zcloud guard waf rules update 7 --name sqli-1-v2` | | `zcloud guard waf rules delete [-f]` | 删除规则组 | `zcloud guard waf rules delete 7 -f` | | `zcloud guard waf rules set-status --status enable\|disable` | 启停规则组(**字符串枚举,非布尔**) | `zcloud guard waf rules set-status 7 --status enable` | ### 4.6 转发 guard forwards | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard forwards list [--page N] [--size N] [--domain-id D] [--keyword K]` | 列出转发 | `zcloud guard forwards list --domain-id 12` | | `zcloud guard forwards create --domain-id D --port P [--schema 3\|4] [--domain D] [--describe D]` | 新增转发(`--domain-id` 和 `--port` 必填;`--schema` 3=TCP 4=UDP) | `zcloud guard forwards create --domain-id 12 --port 8443 --schema 3` | | `zcloud guard forwards update [--port P] [--schema N] [--status 1\|2] [--describe D]` | 修改转发(`--status` 1=禁用 2=启用) | `zcloud guard forwards update 4 --status 2` | | `zcloud guard forwards delete [-f]` | 删除转发 | `zcloud guard forwards delete 4 -f` | ### 4.7 调度管理 guard schedules > 本组命令只做 DNS 解析调度(切换源站/节点、一键启停、批量启停记录)。**不直接调用三方 DNS API**,所有写操作通过 NSQ topic=`dns` 通知 zdns 服务异步生效。**必须用 `affairs` 子命令轮询事务最终状态**(`AffairsStatus_Start` → `Succeed` / `Faild`)。 | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard schedules domains [--page N] [--size N] [--keyword K] [--user-id U] [--mode 0\|1\|2] [--status S]` | DNS 调度域名列表(`--mode` 0=全部 1=源站 2=节点) | `zcloud guard schedules domains --keyword example.com` | | `zcloud guard schedules switch-mode --target-mode src\|node [--comment C]` | 切换源站/节点(必填 `--target-mode`) | `zcloud guard schedules switch-mode d_8a3b1c --target-mode node` | | `zcloud guard schedules init [--comment C]` | 初始化解析(按当前配置重建记录并提交同步事务) | `zcloud guard schedules init d_8a3b1c` | | `zcloud guard schedules reset [--comment C]` | 重置该域名所有解析(switch_state 归位、status 回滚) | `zcloud guard schedules reset d_8a3b1c` | | `zcloud guard schedules records [--group-type 1\|2] [--status 1\|2] [--page N] [--size N]` | 列出域名解析记录(直查 zdns_db,只读) | `zcloud guard schedules records d_8a3b1c --status 2` | | `zcloud guard schedules batch-status --record-ids id1,id2 --status enable\|disable [--comment C]` | 批量启停 DNS 解析记录(`--record-ids` 与 `--status` 均必填) | `zcloud guard schedules batch-status --record-ids r1,r2 --status disable` | | `zcloud guard schedules affairs [--page N] [--size N] [--user-id U] [--status S] [--ctime-from MS] [--ctime-to MS] [--domain-id D]` | 事务记录列表(`--status` 取 `AffairsStatus_Start\|AffairsStatus_Succeed\|AffairsStatus_Faild`) | `zcloud guard schedules affairs --domain-id d_8a3b1c` | | `zcloud guard schedules affair ` | 事务记录详情(含完整 message HTML 与 json_content) | `zcloud guard schedules affair 1715600000_aB3xY9LmNq` | ### 4.8 下发 guard applies | 命令 | 说明 | 示例 | |------|------|------| | `zcloud guard applies list [--page N] [--size N] [--domain-id D] [--status S]` | 列出下发任务(`--status` 取 `pending\|success\|failed\|running\|quit`) | `zcloud guard applies list --status running` | | `zcloud guard applies describe ` | 任务概要 | `zcloud guard applies describe 100` | | `zcloud guard applies create --domain-id D` | 触发下发(`--domain-id` 必填) | `zcloud guard applies create --domain-id 12` | | `zcloud guard applies detail ` | 任务详情(含子任务) | `zcloud guard applies detail 100` | | `zcloud guard applies retry [--node-ids id1,id2]` | 失败重试(`--node-ids` 指定子节点;为空则重试所有失败节点) | `zcloud guard applies retry 100` | | `zcloud guard applies quit [-f]` | 终止任务 | `zcloud guard applies quit 100 -f` | ## 5. analytics 模块(统计大屏) 通用 flags(适用于所有 `analytics ` 命令;不显式指定时不会进入 query string,由后端使用默认值): | Flag | 类型 | 说明 | |------|------|------| | `--window` | string | 时间窗口,如 `last_1h` / `last_24h` / `last_7d`(取值由后端定义) | | `--stime` / `--etime` | int64 | 起止时间(Unix 毫秒,用于自定义窗口) | | `--site-id` / `--domain-id` | string | 按站点/域名过滤 | | `--compare` | bool | 启用环比 | | `--top` | int | TopN 限制(1-100) | | `--order` | string | 排序方式(`asc` / `desc`,具体含义视图表定义) | | `--page` / `--size` | int | 列表分页(仅日志/记录类图表)| | `--charts` | string | 当 chart-key 省略时,按逗号列表批量拉取(fallback batch) | 各页面 chart-key 清单: | 页面 | chart-key | |------|-----------| | `overview` | `kpi`, `bandwidth`, `request-attack`, `event-type`, `waf-type`, `geo`, `top-domains`, `recent-events` | | `access` | `request-hm`, `flow-hm`, `cache-hm`, `bandwidth`, `status`, `flow-duration`, `isp`, `top-ip`, `top-url`, `geo` | | `protect` | `overview`, `waf/statistics`, `waf/types`, `waf/top-ip`, `waf/geo`, `cc/statistics`, `cc/top-ip`, `cc/geo`, `cc/top-url`, `ddos/statistics`, `ddos/types`, `ddos/top-ip` | | `ai` | `attack-trend`, `top-ip`, `top-url`, `detection`, `test-results`, `logs` | | `bot` | `statistics`, `advance-warn`, `browser`, `operating`, `geo`, `top-agent`, `top-ip`, `scatter`, `sessions` | | `alert` | `total`, `hm`, `types`, `domains`, `list` | | 命令 | 说明 | 示例 | |------|------|------| | `zcloud analytics overview [chart-key] [flags]` | 总览页(不传 chart-key 时整页 batch) | `zcloud analytics overview kpi --window last_24h --format json` | | `zcloud analytics access [chart-key] [flags]` | 访问统计 | `zcloud analytics access top-url --window last_7d --format json` | | `zcloud analytics protect [chart-key] [flags]` | 防护统计 | `zcloud analytics protect waf/statistics --format json` | | `zcloud analytics ai [chart-key] [flags]` | AI 识别统计 | `zcloud analytics ai logs --page 1 --format json` | | `zcloud analytics bot [chart-key] [flags]` | 主动防护(Bot) | `zcloud analytics bot statistics --format json` | | `zcloud analytics alert [chart-key] [flags]` | 告警统计 | `zcloud analytics alert list --page 1 --size 20 --format json` | | `zcloud analytics glossary` | 字段术语表 | `zcloud analytics glossary --format json` | | `zcloud analytics batch --page P --charts C [...]` | 跨页通用 batch(`--page` 与 `--charts` 必填) | `zcloud analytics batch --page overview --charts kpi,bandwidth --format json` | | `zcloud analytics overview export --charts C [--type csv\|json] [...]` | 导出 overview 报表(**仅 overview 子页支持 export**) | `zcloud analytics overview export --type csv --charts kpi,bandwidth > overview.csv` | | `zcloud analytics alert ack ` | 确认告警 | `zcloud analytics alert ack a-20260422-0001` | | `zcloud analytics bot session ` | Bot 会话详情(仅接受 `--window` / `--stime` / `--etime`) | `zcloud analytics bot session s-abc123 --format json` | ### 5.1 2026-04-30 chart-rebuild 6 phase 扩展(4 page + 2 独立子命令组) | 命令 | 说明 | 示例 | |------|------|------| | `zcloud analytics health [chart-key] [flags]` | Phase 3 业务健康(status-breakdown / slow-uri / availability 等 7 chart-key) | `zcloud analytics health summary --window last_24h --format json` | | `zcloud analytics ops [chart-key] [flags]` | Phase 5 平台运维(traffic-users / error-domains / nodes 等 8 chart-key,**仅平台运维/超管可见**) | `zcloud analytics ops traffic-users --top 20 --format json` | | `zcloud analytics closure [chart-key] [flags]` | Phase 6 处置闭环(summary / alerts / risks / trend 4 chart-key) | `zcloud analytics closure summary --format json` | | `zcloud analytics cache [chart-key] [flags]` | Phase 6 缓存收益(summary / trend / top-uri / content-types) | `zcloud analytics cache summary --window last_7d --format json` | | `zcloud analytics logs list [flags]` | Phase 1 原始日志列表(uuid/session/ip/uri/status/z_final_* 12 字段筛选) | `zcloud analytics logs list --window last_24h --status 403 --format json` | | `zcloud analytics logs detail ` | Phase 1 单条日志详情 | `zcloud analytics logs detail req-abc123 --format json` | | `zcloud analytics logs export --format csv [--fields ...] [flags]` | Phase 1 原始日志导出(size ≤ 10000,超出走异步任务) | `zcloud analytics logs export --format csv --fields ctime,uuid,host,uri,status > logs.csv` | | `zcloud analytics closure {alerts\|risks} confirm --ids ID1,ID2,...` | Phase 6 批量确认告警/风险(代理 `/api/alert/records/confirm` 与 `/api/chart/risk/events/:event_id/confirm`) | `zcloud analytics closure alerts confirm --ids a1,a2,a3` | | `zcloud analytics reports templates` | Phase 4 列出报表模板 | `zcloud analytics reports templates --format json` | | `zcloud analytics reports list` | Phase 4 报表历史列表 | `zcloud analytics reports list --format json` | | `zcloud analytics reports describe ` | Phase 4 报表详情 | `zcloud analytics reports describe r-001 --format json` | | `zcloud analytics reports generate --template T --window W [...]` | Phase 4 触发生成(同步阈值 ≤ 100k 行;超出走异步) | `zcloud analytics reports generate --template protection-value --window last_30d` | | `zcloud analytics reports download [--output file]` | Phase 4 下载报表产物(pdf/csv/json/html) | `zcloud analytics reports download r-001 --output report.pdf` | **Phase 4 模板枚举(`--template`)**:`protection-value` / `asset-risk` / `attack-source` / `business-health` / `platform-summary`(仅平台运维/超管) / `raw-log-export`。 **真值字段约束(cross-cutting D3/D8/D10)**: - `--z-final-action` 取值仅 `0=放行` / `1=拦截` / `2=验证码`;`--z-white` 是独立 bool 字段(白名单命中),不参与 action 枚举。 - 缓存指标固定使用 `total_cache_count` / `total_cache_bytes` / `total_cache_response_bytes` 三字段(**禁** `cache_count` / `cache_bytes`)。 - 处置字段固定使用 `process_uid` / `process_time` / `status` / `level`(**禁** `handle_user` / `handle_time` / `risk_score`)。 **占位兜底**:部分 chart-key 当前服务端返回 `{available: false, reason: "数据源待接入..."}`;接口/CLI 契约稳定,业务 SQL/ES 真接入按 chart-key 滚动落地。 ## 6. 套餐目录(plan) > **对外范围说明**:仅以下两个只读命令对外公开。套餐的创建/编辑/删除、为用户开通、订阅查询属平台控制台管理操作,直接操作在线计费数据,**不在对外对接 API/CLI 范围**(仅平台运维经控制台 + RBAC 使用)。 | 命令 | 说明 | 所需权限 | |------|------|---------| | `zcloud plan list [--prod-type N] [--keyword KW] [--page N] [--page-size N]` | 分页查询套餐目录;可按产品类型(1=WAF/2=Monitor/3=GFIP)和名称关键词过滤 | `plan.plan.list` | | `zcloud plan describe ` | 查看套餐详情(含 content 配额 JSON) | `plan.plan.view` | **示例** ```bash # 列出 WAF 类型套餐 zcloud plan list --prod-type 1 --format json # 搜索套餐名称含"基础版"的套餐 zcloud plan list --keyword 基础版 # 查看指定套餐详情 zcloud plan describe 550e8400-e29b-41d4-a716-446655440000 ``` 绑定 API:`GET /api/plan/plans` / `GET /api/plan/plans/:id` ## 7. cli_release / tools | 命令 | 说明 | 示例 | |------|------|------| | `zcloud update` | 自更新(查询 `/api/cli/version` 后下载新版本) | `zcloud update` | | `zcloud completion ` | 生成 shell 补全脚本 | `zcloud completion zsh > ~/.zsh/_zcloud` | | `zcloud version` | 显示当前 CLI 版本 | `zcloud version` | 后端发布相关 API:`GET /api/cli/version`(公开) / `GET /api/cli/install.sh`(公开)。 ## 8. 节点安装 / 升级(命令行) > **CLI 豁免说明(规则 5)**:节点安装 / 升级**没有** `zcloud node ...` 子命令,`zcloud` 当前仅有 auth / config / sys / guard / analytics / plan 六个命令组。节点链路的"命令行"是平台生成的**一次性安装一行命令**(在目标节点主机上以 root 执行)+ 管理面 HTTP 接口。完整接口语义见 [API 文档 §8 节点安装 / 升级](/docs/api#8-节点安装--升级node-install)。 ### 8.1 一键安装 / 升级命令(节点主机执行) 平台 `POST /api/node/install/commands`(升级用 `/upgrades`)返回的 `command` 形如: ```bash curl -fsSL --connect-timeout 10 --max-time 60 \ -H 'Authorization: Bearer nit_xxx' 'https:///api/node/install/script' \ | sudo bash -s -- --token 'nit_xxx' --server 'https://' ``` 脚本参数 / 环境变量: | 参数 | 环境变量 | 默认 | 说明 | |------|---------|------|------| | `--token ` | `SETUP_TOKEN` | 必填 | 一次性安装 token(`nit_` 前缀),仅展示一次 | | `--server ` | `SERVER_ADDR` | 必填 | cloud 对外地址,须匹配配置的 public base URL | | `--install-dir ` | `INSTALL_DIR` | `/opt/skynet-node` | 安装目录 | 脚本行为:校验依赖(curl/tar/gzip/sha256sum)→ 下载 package 并校验 `X-Artifact-SHA256` → 拉取 env 覆盖包内 `env.conf` → 执行 `install.sh` → 经 `/report` 回报 `running`/`success`/`failed`。所有下载带 `--retry`,回报调用短超时。 ### 8.2 管理操作(curl / API) | 操作 | 方法 + 路径 | 所需权限(`node.node.*`) | |------|------------|--------------------------| | 注册安装包 + 预检 | `POST /api/node/install/artifacts` | `artifact` | | 查询安装包 | `GET /api/node/install/artifacts` | `artifact` | | 生成安装命令 | `POST /api/node/install/commands` | `install` | | 生成升级命令 | `POST /api/node/install/upgrades` | `upgrade` | | 查询任务列表 / 详情 | `GET /api/node/install/jobs[/:id]` | `job` | | 撤销 token | `POST /api/node/install/tokens/:id/revoke` | `revoke` | 要点:`max_uses` 最低 5 / 默认 20 / 上限 100(保留重试余量);`ttl_seconds` 默认 3600 / 上限 86400;明文 token 只展示一次、库中仅存 hash + prefix;安装机侧鉴权失败返回 401 + challenge;`script` 不消耗次数、`package`/`env` 消耗 `use_count`、`report` 用独立 `report_count` 且 `success` 才置 token `used`。详见 API §8。 ## 完整命令树 ``` zcloud ├── auth (login / logout / info) ├── config (set / get / list / profiles[list,create,activate,delete]) ├── sys │ ├── users (list / describe / create / update / delete / reset-password / lock / assign-roles) │ ├── roles (list / create / update / delete / get-permissions / set-permissions) │ ├── oems (list / describe / create / update / delete / get-setting / set-setting) │ ├── sessions (list / kill) │ └── audit-logs (list / login-records list / permissions) ├── guard │ ├── domains (list / describe / create / update / delete / get-settings / set-settings) │ ├── certs (list / describe / upload / update / delete / bind / unbind) │ ├── policies (list / describe / create / update / delete) │ ├── bwlist (sets[*] / ips[*]) │ ├── waf (rules[*]) │ ├── forwards (list / create / update / delete) │ ├── schedules(domains / switch-mode / init / reset / records / batch-status / open / close / affairs / affair) │ └── applies (list / describe / create / detail / retry / quit) ├── analytics (overview / access / protect / ai / bot / alert / glossary / batch / export / ack / session) ├── plan (list / describe) [只读对外] ├── update ├── completion └── version ``` ## 相关文档 - [API 文档](/docs/api) — 命令对应的 REST 接口 - [认证说明](/docs/auth) — profile / 401 重试机制 - [权限矩阵](/docs/permissions) — 命令所需权限 key --- *Cloud WAF · zcloud CLI · 共 98 条命令,源数据来自 `src/frontend/src/views/docs/cli-commands.ts`* --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 5 大模块:guard / sys / analytics / cli_release / auth > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # API 文档 > 商业级 REST API 文档 · 57 个 endpoint · 80+ 图表数据接口(chart-key)· 双通道鉴权 > 适用对象:客户对接工程师、SRE、SaaS 集成商、AI agent > 阅读顺序:先看 [§0 总体约定](#0-总体约定) → [§1 鉴权](#1-鉴权先读) → 再按业务模块跳转 --- ## 目录 **必读** - [§0 总体约定](#0-总体约定) — 协议 / 信封 / HTTP 状态码 / 分页 / 三类读者画像 - [§1 鉴权(先读)](#1-鉴权先读) — Bearer Session + API Key 双通道 / scopes 收窄 **公开接口(无需鉴权)** - [§2 CLI 发布](#2-cli-发布公开接口) — version · install.sh · download · checksums - [§3 用户认证](#3-用户认证) — login · logout **业务接口(双通道鉴权)** - [§4 系统管理](#4-系统管理) - [4.1 用户管理](#41-用户管理) — `/api/sys/users` - [4.2 API Key 管理](#42-api-key-管理) — `/api/sys/apikeys` - [4.3 权限树](#43-权限树) — `/api/sys/permissions/tree` - [§5 Guard 资源管理](#5-guard-资源管理) - [5.1 域名](#51-域名-apiguarddomains) — `/api/guard/domains` - [5.2 证书](#52-证书-apiguardcerts) — `/api/guard/certs` - [5.3 策略](#53-策略-apiguardpolicies) — `/api/guard/policies` - [5.4 CC 规则](#54-cc-规则-apiguardpoliciesidccrules) — `/api/guard/policies/{id}/cc/rules` - [5.5 ACL 规则](#55-acl-规则-apiguardpoliciesidaclrules) — `/api/guard/policies/{id}/acl/rules` - [5.6 黑白名单](#56-黑白名单-apiguardbwlist) — `/api/guard/bwlist` - [5.7 IP 转发](#57-ip-转发-apiguardforwards) — `/api/guard/forwards` - [5.8 调度管理](#58-调度管理-apiguardschedules) — `/api/guard/schedules` - [5.9 WAF 规则](#59-waf-规则-apiguardwafrules) — `/api/guard/waf/rules` **套餐目录** - [§7 套餐目录(Plan)](#7-套餐目录plan) — 只读对外:list · describe;admin 操作不在此范围 **节点运维** - [§8 节点安装 / 升级(Node Install)](#8-节点安装--升级node-install) — artifacts · commands · upgrades · jobs · tokens + 安装机侧 script/package/env/report **Analytics 统计分析(74 chart-key · 单一形状契约)** - [§6.0 本章统一调用约定](#60-本章统一调用约定) — 5 字段契约 / render_hint 8 词白名单 - [§6.1 术语表](#61-术语表) · [§6.2 跨页 Batch](#62-跨页-batch) · [§6.3 单图 GET 调用入口](#63-单图-get-调用入口) - [§6.4 总览页面 Overview](#64-总览页面overview) · 8 chart-key - [§6.5 访问分析页面 Access](#65-访问分析页面access) · 10 chart-key - [§6.6 防护分析页面 Protect](#66-防护分析页面protect) · 12 chart-key(WAF / CC / DDoS) - [§6.7 AI 识别页面](#67-ai-识别页面ai) · 6 chart-key - [§6.8 主动防护 / Bot 页面](#68-主动防护-bot-页面) · 10 chart-key - [§6.9 告警统计页面 Alert](#69-告警统计页面alert) · 5 chart-key - [§6.10 业务健康 Health](#610-业务健康health-phase-3) · 7 chart-key - [§6.11 平台运维 Ops](#611-平台运维ops-phase-5) · 8 chart-key - [§6.12 处置闭环 Closure](#612-处置闭环closure-phase-6) · 4 chart-key - [§6.13 缓存收益 Cache](#613-缓存收益cache-phase-6) · 4 chart-key - [§6.14 原始日志 Logs](#614-原始日志logs-phase-1) — 非 chart endpoint - [§6.15 报表中心 Reports](#615-报表中心reports-phase-4) — 非 chart endpoint - [§6.16 CLI 对应关系](#616-cli-对应关系) — 30 cobra 命令映射 **参考** - [§A 通用查询参数](#a-analytics-通用查询参数) — window / stime / etime / site_id / domain_id / target_user_id / compare / top / order - [§B 可视化建议总表](#b-可视化建议总表) — B.1 render_hint 速查 · B.2 数据形态速查 - [§C 相关文档](#c-相关文档) > 完整 OpenAPI: [/api/openapi.json](/api/openapi.json) · AI 速读: [/llms.txt](/llms.txt) · 错误码: [/docs/errors](/docs/errors) · CLI: [/docs/cli](/docs/cli) · 权限: [/docs/permissions](/docs/permissions) --- ## §0 总体约定 Cloud WAF 后端基于 Gin 实现的 RESTful 服务。 ### 0.1 基础协议 | 项目 | 值 | |------|----| | 协议 | HTTPS(推荐)/ HTTP | | 数据格式 | 请求与响应均为 `application/json`(特例:`POST /api/guard/certs` 仍是 JSON,PEM 走字符串字段;导出/下载接口返回 `text/csv`、`application/pdf` 等) | | 字符集 | UTF-8 | | 路径前缀 | 所有业务 API 都挂在 `/api/` 下 | | 时间格式 | Unix 毫秒时间戳(int64),不是 ISO 字符串 | | 国际化 | `Accept-Language: zh-CN` 或 `en-US`,影响错误消息和权限名称 | ### 0.2 统一响应信封 任何 JSON 响应都遵循下面三段结构: ```json { "code": 0, "message": "ok", "data": { /* 业务载荷,类型依接口而定 */ } } ``` | 字段 | 类型 | 含义 | |------|------|------| | `code` | number | `0`=成功;非 0 = 业务错误码 | | `message` | string | 错误描述(受 `Accept-Language` 影响) | | `data` | any | 业务数据;列表接口为 `{ list, total, page, size }` | > **特例**:导出文件下载接口(`POST /api/analytics/overview/export`、`GET /api/analytics/reports/:id/download`、`POST /api/analytics/logs/export`)直接返回二进制流或 CSV/JSON 原文,**不**包裹信封。 ### 0.3 HTTP 状态码 | 状态码 | 何时出现 | |--------|----------| | 200 | 业务成功(仍需检查 `code`) | | 201 | 资源创建成功 | | 400 | 入参错误(参数缺失、格式非法、超出范围) | | 401 | 未登录、token 过期、API Key 已吊销 | | 403 | 已登录但权限不足 / 跨 OEM 越权(参见 [权限矩阵](/docs/permissions)) | | 404 | 资源不存在或不在可见范围 | | 429 | 限流(默认每 Key 每秒 100 请求) | | 5xx | 服务端异常 | ### 0.4 列表接口分页约定 所有列表接口统一使用 `page` + `size`(**不是 `page_size`**): | 字段 | 类型 | 默认 | 范围 | |------|------|------|------| | `page` | int | `1` | ≥ 1 | | `size` | int | `20` | 1 - 100 | 响应: ```json { "code": 0, "data": { "list": [ /* ... */ ], "total": 42, "page": 1, "size": 20 } } ``` ### 0.5 跨模块设计标记(D*) 文档中少量 `D*` 标记来自跨模块设计决策,用于提醒对接方不要使用不存在或语义错误的字段: | 标记 | 含义 | |------|------| | D4 | percentile / p50 / p95 / p99 不作为通用字段暴露;仅时间窗 ≤ 24h 时走实时 ES percentile 计算 | | D7 | 报表模板枚举为闭集,超出枚举的模板名不可调用 | | D8 | 缓存价值字段以 `total_cache_*` 为准,**不存在** `cache_count` / `cache_bytes` / `cache_hit` 这类单字段 | | D10 | 告警/风险处置闭环字段以 `process_uid` / `process_time` / `status` / `level` 为准,**不使用**旧字段 `handle_user` / `handle_time` / `risk_score` / `alert_status` | ### 0.6 三类读者的阅读路径 | 读者 | 入口 | 优先用 | |------|------|--------| | 人类对接工程师 | 本文档 + [快速上手](/docs/quickstart) | curl / Postman 调单接口 | | 脚本 / CI / 第三方系统 | 本文档 + [API Key 管理](#42-api-key-管理) | API Key + 受限 scopes | | 机器 / AI agent | `/api/openapi.json` / `/llms.txt` / `/llms-full.txt` | OpenAPI v3 schema | --- ## §1 鉴权(先读) Cloud WAF 当前支持两条认证通道,**同一请求只能选其中一种**: | 场景 | 请求头 | 适用对象 | 说明 | |------|--------|----------|------| | 人工登录 / Web 控制台 / CLI 交互登录 | `Authorization: Bearer ` | 人 | token 由 `POST /api/auth/login` 颁发,会过期,适合短期会话 | | 脚本 / CI / 第三方系统集成 | `Authorization: ApiKey zck_.` | 机器调用 | API Key 明文仅签发时返回一次,适合长期自动化对接 | 公开接口(无需鉴权)只有 4 个: - `GET /api/cli/version` - `GET /api/cli/install.sh` - `GET /api/cli/download/{filename}` - `GET /api/cli/checksums.txt` - `POST /api/auth/login` 其它所有接口都必须携带上述任一认证头。下面各接口示例默认使用 `Bearer`;如改用 API Key,只需把请求头替换为 `Authorization: ApiKey zck_.`,并确保该 Key 的 `scopes` 覆盖接口所需权限。 ### 1.1 API Key 权限规则 ``` effective_perms = user.RBAC ∩ key.scope ``` API Key 的权限不会超过签发用户当前的 RBAC;`scope` 只能收窄,不能放大。中间件认证通过后会注入与会话通道一致的 `user_id` / `role_id` 上下文,下游 RBAC/OEM 隔离保持一致。 **安全约束**: - 明文 API Key 仅在签发响应里出现一次,落库零明文(`key_hash` 是 bcrypt 摘要) - 用户被锁定或删除后,其名下 API Key 自动失效 - `scopes` 非空时,请求权限必须精确命中其中之一;为空表示完整继承当前 RBAC - 跨 OEM 越权由中间件拦截:`api_keys.oem_id` 与请求 hostname 不一致时直接 403 - 可选 IP 白名单 `allowed_ip_cidrs`:非空时只放行命中 CIDR 的请求 > API Key 管理接口的完整说明见 [§4.2](#42-api-key-管理)。 --- ## §2 CLI 发布(公开接口) 供 zcloud CLI 自更新与一键安装使用,无需鉴权。 ### `GET /api/cli/version` — 查询 CLI 最新版本 **用途**:客户端启动时自检版本;安装脚本 `/api/cli/install.sh` 内部依赖此接口决定下载哪个二进制。 **鉴权**:无(公开) **输入参数**:无 **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.version` | string | 形如 `v0.1.0-31`,对齐 git tag | | `data.binaries[]` | array | 4 个 `os/arch` 组合的下载地址 | | `data.binaries[].os` | string | `linux` / `darwin` | | `data.binaries[].arch` | string | `amd64` / `arm64` | | `data.binaries[].download_url` | string | 拼接服务地址即可下载 | **可视化建议**:纯文本展示(版本徽章),不适合图表。前端可用作"系统设置 - CLI 版本"页面的 KPI 数字卡。 **示例响应**: ```json { "code": 0, "message": "ok", "data": { "version": "v0.1.0-31", "binaries": [ { "os": "linux", "arch": "amd64", "download_url": "/api/cli/download/linux-amd64" }, { "os": "linux", "arch": "arm64", "download_url": "/api/cli/download/linux-arm64" }, { "os": "darwin", "arch": "amd64", "download_url": "/api/cli/download/darwin-amd64" }, { "os": "darwin", "arch": "arm64", "download_url": "/api/cli/download/darwin-arm64" } ] } } ``` --- ### `GET /api/cli/install.sh` — 一键安装脚本 **用途**:在 Linux/macOS 上一行命令完成 CLI 安装。返回 `text/x-shellscript`,可直接 `curl ... | sh`。 **鉴权**:无(公开) **输入参数**:无 **输出字段**:纯 shell 脚本文本,**不**走 JSON 信封。 **可视化建议**:不适合图表,作为代码片段展示。 **示例**: ```bash curl -fsSL https://waf.example.com/api/cli/install.sh | sh ``` --- ### `GET /api/cli/download/{filename}` — 下载指定二进制 **用途**:拉取特定平台的 zcloud 二进制(已签名)。`filename` 取自 `/api/cli/version` 返回的 `download_url` 的最后一段,如 `linux-amd64`。 **鉴权**:无(公开) **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `filename` | path | 是 | `linux-amd64` / `linux-arm64` / `darwin-amd64` / `darwin-arm64` 其一 | **输出字段**:二进制流(`application/octet-stream`)。 **可视化建议**:不适合图表。 --- ### `GET /api/cli/checksums.txt` — 二进制校验和 **用途**:配合 `/api/cli/download/*` 做 SHA256 完整性校验,安装脚本会先 fetch 这个文件再下载二进制。 **鉴权**:无(公开) **输入参数**:无 **输出字段**:纯文本 `text/plain`,每行一个 ` `。 **可视化建议**:不适合图表。 --- ## §3 用户认证 ### `POST /api/auth/login` — 登录 **用途**:用用户名 + 密码换取一个会话 token。Web 控制台、CLI 交互式登录、移动端均走此接口。 **鉴权**:无(公开) **输入参数**(请求体): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `username` | string | 是 | 用户名 | | `password` | string | 是 | 密码(**前端 base64 编码后传入**,后端 decode 后再 bcrypt 比对,兼容老系统) | **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.token` | string | 32 位会话 token,用于后续 `Authorization: Bearer ` | | `data.user_id` | string | 用户唯一 ID | | `data.user_name` | string | 用户名 | | `data.nick_name` | string | 显示名 | | `data.need_change_password` | bool | true 表示首次登录需强制改密码 | **可视化建议**:登录响应不直接做图,但 `need_change_password=true` 时前端应跳转改密页。 **示例请求**: ```bash curl -X POST https://waf.example.com/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"'$(echo -n 'your_password' | base64)'"}' ``` **示例响应**: ```json { "code": 0, "message": "ok", "data": { "token": "550e8400-e29b-41d4-a716-446655440000", "user_id": "u-admin", "user_name": "admin", "nick_name": "系统管理员", "need_change_password": false } } ``` **常见误用**: - 直接传明文密码 → 后端 base64 decode 失败,统一返回"用户名或密码错误",无法定位真实原因 - token 在多端复用 → 注销时可能影响其它端,建议每端独立登录 --- ### `POST /api/auth/logout` — 注销 **用途**:主动失效当前 `Bearer` token;再次请求该 token 返回 401。 **鉴权**:`Bearer `(API Key 通道无 logout 概念,吊销走 `DELETE /api/sys/api-keys/:id`) **输入参数**:无 **输出字段**:`data` 为 `null`。 **可视化建议**:不适合图表。 **示例请求**: ```bash curl -X POST https://waf.example.com/api/auth/logout \ -H "Authorization: Bearer $TOKEN" ``` --- ## §4 系统管理 ### 4.1 用户管理 > 适用场景:在"系统设置 - 用户管理"页面增删改查用户。所有用户接口都受 OEM 隔离,跨 OEM 操作会返回 403。 #### `GET /api/sys/users` — 用户列表(分页) **用途**:在"用户管理"页面渲染用户表格,支持关键字模糊搜索 + 分页。 **鉴权**:`sys.user.list` **输入参数**: | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `page` | int | 否 | `1` | 页码,从 1 起 | | `size` | int | 否 | `20` | 每页条数,1-100 | | `keyword` | string | 否 | `admin` | 用户名/显示名模糊搜索 | **输出字段**(`data.list[]`): | 字段 | 类型 | 说明 | |------|------|------| | `user_id` | string | 用户唯一 ID | | `user_name` | string | 登录用户名 | | `nick_name` | string | 显示名 | | `email` | string | 邮箱 | | `mobile` | string | 手机号 | | `locked` | int | 0=正常,非 0=锁定 | | `role_ids` | int64[] | 角色 ID 列表(可多角色) | | `roles[]` | array | 角色摘要 `{role_id, name, level}` | | `ctime` | int64 | 创建时间,Unix 毫秒 | **可视化建议**:表格展示。`locked` 列建议用徽章(绿/红);`roles` 用 chip 标签。 **示例请求**: ```bash curl -H "Authorization: Bearer $TOKEN" \ "https://waf.example.com/api/sys/users?page=1&size=20&keyword=admin" ``` **示例响应**: ```json { "code": 0, "message": "ok", "data": { "list": [ { "user_id": "u-001", "user_name": "admin", "nick_name": "系统管理员", "email": "admin@example.com", "mobile": "", "locked": 0, "role_ids": [1], "roles": [{ "role_id": 1, "name": "超级管理员", "level": 1 }], "ctime": 1714521600000 } ], "total": 42, "page": 1, "size": 20 } } ``` **常见误用**: - 把 `role_ids` 当成单值字段 → 用户可有多角色,必须按数组处理 - 用 `level` 做权限阈值判断 → `level` 是展示字段(业务层级),权限阈值用 `role_id`(参见 [pitfall_role_level_vs_role_id](/docs/permissions)) --- #### `POST /api/sys/users` — 创建用户 **用途**:在"用户管理"页面提交"新建用户"表单。 **鉴权**:`sys.user.create` **输入参数**(请求体): | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `user_name` | string | 是 | `u1` | 2-255 字符 | | `password` | string | 是 | `InitPassw0rd!` | 6-72 字符(bcrypt 上限) | | `nick_name` | string | 否 | `运维 A` | ≤ 100 字符 | | `email` | string | 否 | `u1@x.com` | 标准邮箱格式 | | `mobile` | string | 否 | `13800138000` | ≤ 20 字符 | | `comment` | string | 否 | `值班同事` | 备注 | **输出字段**:返回新建用户对象,结构同列表项。 **可视化建议**:不适合图表,是一次性写操作;前端应在成功后刷新用户列表。 **示例请求**: ```bash curl -X POST https://waf.example.com/api/sys/users \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"user_name":"u1","password":"InitPassw0rd!","nick_name":"运维 A","email":"u1@example.com"}' ``` --- #### `DELETE /api/sys/users/{id}` — 删除用户 **用途**:用户管理页面"删除"按钮的后端接口。删除会级联清理会话、API Key、角色绑定。 **鉴权**:`sys.user.delete` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | path | 是 | 用户 `user_id` | **输出字段**:`data` 为 `null`。 **可视化建议**:不适合图表。 --- #### `PUT /api/sys/users/{id}/password` — 重置密码 **用途**:管理员替用户重置密码。被重置用户下次登录后**强制**改密码。 **鉴权**:`sys.user.resetpwd` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | path | 是 | 用户 `user_id` | | `password` | string | 是 | 新密码(明文,6-72 字符;后端自动 bcrypt) | **输出字段**:`data` 为 `null`。 **可视化建议**:不适合图表。 **示例请求**: ```bash curl -X PUT https://waf.example.com/api/sys/users/u-001/password \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"password":"NewPassw0rd!"}' ``` **常见误用**: - 期望前端做 base64 编码 → 此接口直接传明文,与登录接口不同(登录接口 base64 是历史兼容) --- ### 4.2 API Key 管理 > 适用场景:在"系统设置 - API Key"页面发放/回收脚本调用凭证。配合 `/api/sys/api-keys/:id/logs|stats|audit-actions` 做调用审计。 #### `POST /api/sys/api-keys` — 签发新 API Key **用途**:为脚本/CI/第三方系统签发一条机器调用凭证。明文 `api_key` 字段**仅此一次返回**,前端必须立即让用户复制保存。 **鉴权**:`sys.apikey.create` **输入参数**(请求体): | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `name` | string | 是 | `生产对接` | ≤ 100 字符,用于审计识别 | | `scopes` | string[] | 否 | `["guard.domain.list"]` | 权限 full key 列表;空数组 = 完整继承签发用户当前 RBAC | | `expires_in_days` | int | 否 | `90` | 过期天数,默认 90,最大 365 | | `allowed_ip_cidrs` | string[] | 否 | `["203.0.113.0/24"]` | E14 IP 白名单 CIDR;为空表示不限制来源 IP | **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.key_id` | string | API Key 唯一 ID(吊销/查日志用此 ID) | | `data.name` | string | 与请求一致 | | `data.api_key` | string | **完整明文 `prefix.secret`,仅此一次返回** | | `data.prefix` | string | 形如 `zck_abc12345`,可写入日志 | | `data.last4` | string | secret 末 4 位,前端用于"我刚签的那条"识别 | | `data.expires_at` | int64 | 过期时间,Unix 毫秒 | **错误码**: - `1052` scope 超出用户 RBAC 边界 - `1054` `expires_in_days > 365` - `1055` `name` 缺失 **可视化建议**:签发响应是一次性写操作,建议前端用模态框 + 一次性复制按钮 + 遮码展示明文(参考行业惯例 GitHub/Stripe)。 **示例请求**: ```bash curl -X POST https://waf.example.com/api/sys/api-keys \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"生产对接","scopes":["guard.domain.list"],"expires_in_days":90}' ``` **示例响应**: ```json { "code": 0, "data": { "key_id": "8f21c0c5-55ae-4cbd-a60a-8e64a6e2b1d0", "name": "生产对接", "api_key": "zck_abc12345.A1b2C3d4E5f6G7h8I9j0K1L2M3n4O5p6Q7r8S9t0", "prefix": "zck_abc12345", "last4": "5t0", "expires_at": 1732982400000 } } ``` **常见误用**: - 把 `api_key` 字段存数据库 → 应只存 `key_id` + `prefix`,明文交给业务方一次性 - `scopes` 列空数组以为是无权限 → 空数组实际代表完整继承当前 RBAC,要明确收窄就传具体权限 key --- #### `GET /api/sys/api-keys` — 查询 API Key 列表 **用途**:在 API Key 管理页面展示当前用户/OEM 内的 Key 清单与状态。 **鉴权**:`sys.apikey.list` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `page` | int | 否 | 默认 1 | | `size` | int | 否 | 默认 20,最大 100 | **输出字段**(`data.list[]`): | 字段 | 类型 | 说明 | |------|------|------| | `key_id` | string | API Key 唯一 ID | | `name` | string | 名称 | | `prefix` | string | `zck_xxx` 前缀 | | `last4` | string | secret 末 4 位 | | `user_id` | string | 持有人 | | `oem_id` | string | OEM 隔离边界 | | `scopes` | string[] | 限定权限列表 | | `allowed_ip_cidrs` | string[] | E14 IP 白名单 | | `status` | int | `1`=active,`2`=revoked | | `expires_at` | int64 | 过期时间 | | `last_used_at` | int64 | 最近调用时间;从未用为 `0` | | `last_used_ip` | string | 最近调用 IP;从未用为 `""` | | `ctime` | int64 | 签发时间 | **可见范围**: - 普通用户(`role_id ≥ 10`):只看自己签发的 - 平台级用户(`role_id < 10`:超管/运维/审计员):看本 OEM 全部 **可视化建议**:表格展示。`status` 用徽章(绿=active/灰=revoked),`expires_at` 即将到期(< 7d)建议高亮。配合 `/stats` 接口可做柱状图"调用量 TOP 5 Key"。 **示例请求**: ```bash curl -H "Authorization: Bearer $TOKEN" \ "https://waf.example.com/api/sys/api-keys?page=1&size=20" ``` --- #### `DELETE /api/sys/api-keys/{id}` — 吊销 API Key **用途**:软吊销(`status` → `revoked`),保留审计痕迹。中间件认证时 `status != active` 直接拒绝。 **鉴权**:`sys.apikey.delete` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | path | 是 | API Key `key_id` | **输出字段**:`data` 为 `null`。 **幂等性**:重复吊销返回 `200`,前端反复操作不报错。 **可操作范围**: - 普通用户:只能吊销自己签发的 - 平台级用户:可吊销本 OEM 内任意 key(管理止损用) **错误码**:`1051` API Key 不存在或不在可见范围。 **可视化建议**:不适合图表;前端"吊销"按钮触发,建议加二次确认对话框。 **示例请求(用 API Key 调用)**: ```bash curl -X DELETE https://waf.example.com/api/sys/api-keys/8f21c0c5-55ae-4cbd-a60a-8e64a6e2b1d0 \ -H "Authorization: ApiKey zck_abc12345.A1b2C3d4..." ``` --- #### `GET /api/sys/api-keys/{id}/logs` — 查询某 Key 的调用流水(E12) **用途**:审计某条 API Key 的调用历史。返回该 Key 在审计表 `api_key_audit_logs` 中的事件列表。 **鉴权**:`sys.apikey.logs` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | path | 是 | API Key `key_id` | | `event` | string | 否 | `call`(默认,调用流水)/ `manage`(管理操作) | | `page` | int | 否 | 默认 1 | | `size` | int | 否 | 默认 20,最大 100 | **输出字段**(每条记录): | 字段 | 类型 | 说明 | |------|------|------| | `id` | int | 自增主键 | | `event_type` | string | `call` / `manage` | | `key_id` | string | 关联的 API Key ID | | `user_id` | string | 操作主体(call=key 持有人;manage=操作人) | | `auth_mode` | string | `apikey` / `session`,记录请求通过的认证通道 | | `action` | string | call=` `;manage=`create` / `revoke` / `renew` / `revoke-all` | | `status_code` | int | HTTP 响应状态码(call 类型有效) | | `biz_code` | int | 业务错码(0 = 成功;call 类型有效) | | `client_ip` | string | 客户端 IP | | `user_agent` | string | UA(≤ 255 字符,超长截断) | | `extra` | string | JSON 字符串,承载续期 / 批量动作等结构化扩展字段 | | `ctime` | int | Unix 毫秒时间戳 | **可视化建议**: - **推荐图表**:表格 + 时序折线图(按 ctime 聚合调用次数) - **派生指标**:成功率柱状图(按 status_code 拆分 2xx/4xx/5xx) **可见范围**: - 普通用户:仅可查询自己签发的 Key - 平台级用户:可查询本 OEM 内任意 Key(超管全库) **错误码**:`1051` API Key 不存在或不在可见范围。 --- #### `GET /api/sys/api-keys/{id}/stats` — 查询某 Key 的聚合统计 **用途**:在 API Key 详情页展示该 Key 的调用聚合 KPI(总次数、成功率、QPS、TOP 接口)。仅基于 `event_type=call` 的记录。 **鉴权**:`sys.apikey.stats` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | path | 是 | API Key `key_id` | | `since` | string/int | 否 | 聚合窗口起点;支持 `24h` / `7d` / `30m` 相对值,或纯整数 = 毫秒时间戳;留空 = 全量历史 | **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `total_calls` | int | 窗口内总调用次数 | | `success` | int | `200 ≤ status_code < 400` 的次数 | | `client_err` | int | `400 ≤ status_code < 500` 的次数 | | `server_err` | int | `status_code ≥ 500` 的次数 | | `top_endpoints` | array | 调用次数 Top 5 的 action(`{action, count}`,按次数降序) | | `last_1h_qps` | float | 最近 1 小时 QPS(次数 / 3600) | **可视化建议**: - **推荐图表**:KPI 数字卡(4 个:total_calls / 成功率 / QPS / 错误数)+ 横向 bar 图(top_endpoints) - **派生指标**:饼图(success / client_err / server_err 三段占比) **错误码**:`400` `since` 解析失败;`1051` API Key 不存在或不在可见范围。 **示例请求**: ```bash curl -H "Authorization: Bearer $TOKEN" \ "https://waf.example.com/api/sys/api-keys/8f21c0c5-55ae-4cbd-a60a-8e64a6e2b1d0/stats?since=24h" ``` --- #### `GET /api/sys/api-keys/audit-actions` — 查询管理操作流水(E13) **用途**:跨 Key 的审计视图,返回 API Key 管理动作(创建 / 吊销 / 续期 / 批量吊销)的流水。 **鉴权**:`sys.apikey.audit` **输入参数**:`page` / `size`,标准分页。 **输出字段**:与 `GET /api/sys/api-keys/{id}/logs` 相同;`event_type` 全部为 `manage`。 **可见范围**: - 普通用户:只看自己作为操作人的记录(`user_id = currentUserID`) - 平台级用户:本 OEM 全部(超管全库) **可视化建议**: - **推荐图表**:表格(按 ctime 倒序) - **派生指标**:按 action 聚合的柱状图(create / revoke / renew / revoke-all 计数) --- ### 4.3 权限树 #### `GET /api/sys/permissions/tree` — 权限树 **用途**:返回当前 OEM 下的完整权限树(含模块/资源/动作三层 + i18n 名)。前端"角色权限"页用此渲染勾选树;签发 API Key 选 scope 时也用同一颗树。 **鉴权**:登录态(无具体权限要求) **输入参数**:可附加 `Accept-Language: en-US` 切换权限名语言。 **输出字段**(节选,`data[]`): | 字段 | 类型 | 说明 | |------|------|------| | `module` | string | 模块名,如 `guard` / `sys` / `analytics` | | `resources[]` | array | 该模块下的资源列表 | | `resources[].prefix` | string | 资源前缀,如 `guard.domain` | | `resources[].name` | string | 资源 i18n 显示名 | | `resources[].actions[]` | array | 该资源的动作列表 | | `resources[].actions[].key` | string | 动作短 key,如 `list` / `create` | | `resources[].actions[].name` | string | 动作 i18n 显示名 | | `resources[].actions[].full_key` | string | 完整权限 key,如 `guard.domain.list` | **可视化建议**: - **推荐图表**:三层树(n-tree / el-tree),module → resource → action - **复用场景**:API Key scope 选择器、角色权限分配勾选器 **示例响应**(节选): ```json { "code": 0, "data": [ { "module": "guard", "resources": [ { "prefix": "guard.domain", "name": "域名", "actions": [ { "key": "list", "name": "列表", "full_key": "guard.domain.list" }, { "key": "view", "name": "查看", "full_key": "guard.domain.view" }, { "key": "create", "name": "创建", "full_key": "guard.domain.create" } ] } ] } ] } ``` --- ## §5 Guard 资源管理 > 📦 **Guard 资源管理** · 30 个 endpoint · 用于配置防护对象(域名/证书/策略/CC&ACL 规则/名单/转发/调度/WAF 规则),是 WAF 防护能力的"配置面" > 完整 schema 见 `/api/openapi.json`,本节给出对接最关键的字段名、枚举与典型踩坑点。 ### 5.1 域名 `/api/guard/domains` 域名是 Guard 的核心资源——所有防护策略、证书绑定、统计聚合都以 `domain_id` 为锚点。 #### `GET /api/guard/domains` — 域名列表 **用途**:在"防护管理 - 域名"页面渲染域名表格,按审核状态筛选 + 关键字搜索。 **鉴权**:`guard.domain.list` **输入参数**: | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `page` | int | 否 | `1` | 页码 | | `size` | int | 否 | `20` | 每页条数,1-100(**不是 page_size**) | | `keyword` | string | 否 | `api.example` | 模糊搜索 `domain` 或 `asset_name` | | `audit_status` | int | 否 | `4` | `1`=未审核 `2`=审核中 `3`=未通过 `4`=通过 | **输出字段**(`data.list[]` 即 `DomainVO`): | 字段 | 类型 | 说明 | |------|------|------| | `domain_id` | string | 域名唯一 ID | | `domain` | string | 域名本身,如 `api.example.com` | | `asset_name` | string | 资产备注名 | | `user_id` | string | 归属用户 UUID(保留兼容老平台) | | `user_name` | string | 归属用户名(**P1.3 新增**,来源 `cloud sys.users`) | | `policy_id` | string | 关联策略 ID | | `cname` | string | 后端为该域名分配的 CNAME | | `auto_cert` | bool | 是否启用自动签发证书 | | `mode` | int32 | 接入模式(`1`=反代等,参见运维文档) | | `audit_status` | int32 | 1=未审核 2=审核中 3=未通过 4=通过 | | `switches` | map | 保护开关,key 取自 `waf/cc/acl/bot/cache`,1=开 0=关 | | `ctime` / `utime` | int64 | 创建/更新时间 | **可视化建议**: - **推荐图表**:表格(主图)+ 顶部 KPI 卡片(总数 / 已通过 / 待审核)+ `switches` 用 chip 方阵渲染开关状态 - **派生指标**:饼图(`audit_status` 4 状态占比) **示例请求**: ```bash curl -H "Authorization: ApiKey $ZCLOUD_API_KEY" \ "https://waf.example.com/api/guard/domains?page=1&size=20&audit_status=4" ``` **示例响应**: ```json { "code": 0, "data": { "list": [ { "domain_id": "d_8a3b1c", "domain": "api.example.com", "asset_name": "线上 API 网关", "user_id": "u_abc", "policy_id": "p_default", "cname": "api.example.com.cname.zcloud.io", "auto_cert": false, "mode": 1, "audit_status": 4, "switches": { "waf": 1, "cc": 1, "acl": 1, "bot": 0, "cache": 1 }, "ctime": 1714521600000, "utime": 1714608000000 } ], "total": 8, "page": 1, "size": 20 } } ``` **常见误用**: - 用 `page_size` 分页 → 实际是 `size`,传 `page_size` 会被忽略 - 期望返回 `protection_enabled` / `current_cert_id` / `origin_addr` → 这些字段不存在;保护开关在 `switches` map 里,证书绑定走 `/api/guard/certs/:id/domains` 反查 --- #### `POST /api/guard/domains` — 创建域名 **用途**:在"添加域名"表单提交后调用,创建一条新的防护域名。 **鉴权**:`guard.domain.create` **输入参数**(请求体 `DomainCreateReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `domain` | string | 是 | 域名本身,如 `api.example.com` | | `asset_name` | string | 否 | 资产备注名 | | `policy_id` | string | 否 | 绑定策略;不传走默认 | > **重要**:`domain` 是唯一必填项;证书绑定走 `POST /api/guard/certs/:id/bind`,**不要**通过 create-domain 设置;`origin_addr` / `port` / `cert_id` 字段**不存在**。 **输出字段**:返回 `DomainVO`(结构同列表项),含分配的 `domain_id` 和 `cname`。 **可视化建议**:不适合图表。建议创建成功后立即跳转域名详情页或刷新列表。 --- #### `GET /api/guard/domains/{id}` — 域名详情 **用途**:进入域名详情页时拉单条详情。 **鉴权**:`guard.domain.view` **输入参数**:path `id` = `domain_id`。 **输出字段**:`DomainVO`,与列表项完全一致。 **可视化建议**:表单展示。`switches` 渲染为开关组;`audit_status` 用徽章。 --- #### `PUT /api/guard/domains/{id}` — 更新域名 **用途**:编辑域名的资产名、策略绑定、自动证书等可变字段。 **鉴权**:`guard.domain.edit` **输入参数**(请求体 `DomainUpdateReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `asset_name` | string | 否 | 资产备注名 | | `policy_id` | string | 否 | 切换策略 | | `auto_cert` | bool | 否 | 切换自动签发开关 | | `mode` | int32 | 否 | 接入模式 | **输出字段**:返回更新后的 `DomainVO`。 **可视化建议**:不适合图表。 --- #### `DELETE /api/guard/domains/{id}` — 删除域名 **用途**:从防护列表中移除域名。删除会级联清理 settings、证书绑定、统计快照。 **鉴权**:`guard.domain.delete` **输入参数**:path `id` = `domain_id`。 **输出字段**:`data` 为 `null`。 **可视化建议**:不适合图表。建议删除前二次确认,提示"会清理统计与绑定"。 --- #### `GET /api/guard/domains/{id}/settings` — 域名设置查询 **用途**:在域名详情页"高级设置"标签下展示当前生效的 settings(保护模块开关、缓存策略、CC 限速等)。 **鉴权**:`guard.domain.view` **输入参数**:path `id` = `domain_id`。 **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.settings` | map | key 取自后端 settings.* 字典,典型 `waf/cc/acl/bot/cache`,value 是 stringified 配置 JSON | **可视化建议**:表单展示,每个 key 一行配置卡片。 --- #### `PUT /api/guard/domains/{id}/settings` — 域名设置更新 **用途**:修改域名的 settings map。 **鉴权**:`guard.domain.edit` **输入参数**(请求体 `DomainSettingsUpdateReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `settings` | map | 是 | key 必须取自 `GET /settings` 返回的 settings.* 键名;非法 key 后端会拒绝 | **输出字段**:`data` 为 `null`,调用方应紧接调用 GET 拉新值。 **可视化建议**:不适合图表。 **常见误用**: - 把顶层布尔字段当 settings 传 → settings value 是 stringified JSON,不是 bool - 自创不存在的 key → 后端按白名单校验,未知 key 返回 400 --- ### 5.2 证书 `/api/guard/certs` 证书管理走"上传 PEM 文本 → 绑定域名"两步流程;**Content-Type 是 `application/json`,不是 multipart/form-data**。 #### `GET /api/guard/certs` — 证书列表 **用途**:"防护管理 - 证书"页面表格。 **鉴权**:`guard.cert.list` **输入参数**:`page` / `size` / `keyword`(搜索 name/common_name)。 **输出字段**(`data.list[]` = `CertVO`): | 字段 | 类型 | 说明 | |------|------|------| | `id` | uint64 | 证书唯一 ID | | `name` | string | 自定义名称 | | `user_name` | string | 归属用户名(**P1.3 替换原 `user_id`**,来源 `cloud sys.users`) | | `certificate_type` | int32 | 证书类型枚举 | | `common_name` | string | 证书 CN | | `issuer` | string | 颁发者 | | `expired_at` | int64 | 到期时间,Unix 毫秒 | | `auto_cert` | bool | 是否自动续期 | | `ctime` / `utime` | int64 | 创建/更新时间 | > **不存在** `bound_domains` 字段;要查证书绑定哪些域名,调 `GET /api/guard/certs/{id}/domains`。 **可视化建议**: - **推荐图表**:表格 + 即将到期高亮(`expired_at - now < 30d` 染色) - **派生指标**:KPI 卡(总数 / 30d 内到期数 / 已过期数);饼图(`auto_cert` true/false 占比) --- #### `POST /api/guard/certs` — 上传证书 **用途**:上传一条 PEM 证书。客户对接最容易踩的坑就是误用 multipart/form-data,请务必用 JSON。 **鉴权**:`guard.cert.create` **安全提示**:直接集成本接口时,`cert` / `key` 仍按 JSON PEM 文本传入;但不要把私钥写入 AI 对话、工单、日志或可观测埋点。若通过 Aegeon Cloud 对话助手操作证书,优先使用其"安全证书附件"上传入口:浏览器将证书/私钥上传到 Aegeon 后只在对话中保留附件引用,私钥不会进入 AI 消息内容。 **输入参数**(请求体 `CertUploadReq`,**`application/json`**): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `name` | string | 是 | 证书名 | | `cert` | string | 是 | PEM 文本字符串(含 `-----BEGIN CERTIFICATE-----` 头尾) | | `key` | string | 是 | 私钥 PEM 文本字符串 | | `sign_cert` | string | 否 | 国密签名证书 PEM(可选) | | `sign_key` | string | 否 | 国密签名私钥 PEM(可选) | **输出字段**:返回新建 `CertVO`。 **可视化建议**:不适合图表。前端上传组件应支持"粘贴 PEM 文本"和"读取本地文件"两种方式。 **示例请求**: ```bash curl -X POST https://waf.example.com/api/guard/certs \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"prod-2026","cert":"-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----","key":"-----BEGIN PRIVATE KEY-----\nMII...\n-----END PRIVATE KEY-----"}' ``` **常见误用**: - 用 `multipart/form-data` 上传文件 → 后端不支持,返回 400 - `cert` / `key` 字段传文件路径 → 必须是 PEM 文本字符串本身 - 将私钥原文放进 AI 对话或日志 → 应使用安全附件/密钥托管路径,仅在服务端短暂解密后调用本接口 --- #### `GET /api/guard/certs/{id}` — 证书详情 **用途**:详情页展示,含完整 PEM 文本。 **鉴权**:`guard.cert.view` **输入参数**:path `id`。 **输出字段**:`CertDetailVO` = `CertVO` + `cert` + `sign_cert`(PEM 文本,方便下载)。 **可视化建议**:表单展示。可加"下载证书"按钮(前端拼 PEM 触发下载)。 --- #### `PUT /api/guard/certs/{id}` — 更新证书 **用途**:直接替换 PEM 文本(无需先删后建)。 **鉴权**:`guard.cert.edit` **输入参数**(请求体 `CertUpdateReq`,字段同 Upload 但全部可选):`name` / `cert` / `key` / `sign_cert` / `sign_key`。 **输出字段**:返回更新后的 `CertVO`。 --- #### `DELETE /api/guard/certs/{id}` — 删除证书 **鉴权**:`guard.cert.delete` **输入参数**:path `id`。 **输出字段**:`data` 为 `null`。 > **副作用**:删除前会自动解绑该证书绑定的所有域名。 --- #### `GET /api/guard/certs/{id}/domains` — 查询证书已绑定的域名 **用途**:在证书详情页展示"该证书正在保护哪些域名"。 **鉴权**:`guard.cert.view` **输入参数**:path `id`。 **输出字段**(`data[]` = `CertDomainVO[]`): | 字段 | 类型 | 说明 | |------|------|------| | `domain_id` | string | 域名 ID | | `domain` | string | 域名 | | `cert_id` | uint64 | 证书 ID(即 path `id`) | | `ctime` | int64 | 绑定时间 | **可视化建议**:表格展示。 --- #### `POST /api/guard/certs/{id}/bind` — 绑定证书到域名 **用途**:把指定证书绑到一个域名上。 **鉴权**:`guard.cert.edit` **输入参数**(请求体 `CertBindReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `domain_id` | string | 是 | 目标域名 ID | **输出字段**:`data` 为 `null`。 **可视化建议**:不适合图表。 --- #### `DELETE /api/guard/certs/{id}/bind/{domainId}` — 解绑证书与域名 **用途**:解除证书与某个域名的绑定关系。 **鉴权**:`guard.cert.edit` **输入参数**:path `id` = 证书 ID;path `domainId` = 域名 ID。 **输出字段**:`data` 为 `null`。 > **路径**:是 `DELETE /bind/{domainId}`,**不是** `POST /unbind`。 --- ### 5.3 策略 `/api/guard/policies` 策略是规则的容器:CC / ACL / 黑白名单 都挂在策略下。一个域名绑一个策略。 #### `GET /api/guard/policies` — 策略列表 **鉴权**:`guard.policy.list` **输入参数**:`page` / `size` / `keyword`。 **输出字段**(`data.list[]` = `PolicyVO`): | 字段 | 类型 | 说明 | |------|------|------| | `policy_id` | string | 策略 ID | | `name` | string | 策略名 | | `comment` | string | 备注(**不是 `remark`**) | | `user_id` | string | 归属用户 UUID(保留兼容老平台) | | `user_name` | string | 归属用户名(**P1.3 新增**,来源 `cloud sys.users`) | | `default_main_rule_version` | string | 默认主规则版本 | | `is_default` | bool | 是否系统默认策略 | | `schema_id` | int64 | schema 版本 | | `cc_rule_count` | int32 | 该策略下的 CC 规则数 | | `bwl_rule_count` | int32 | 黑白名单规则数 | | `acl_rule_count` | int32 | ACL 规则数 | | `ctime` / `utime` | int64 | 创建/更新时间 | **可视化建议**:表格 + 三个 chip(cc/bwl/acl 数量)。 --- #### `POST /api/guard/policies` — 创建策略 **鉴权**:`guard.policy.create` **输入参数**(`PolicyCreateReq`):`name`(必填)/ `comment`。 **输出字段**:返回新建 `PolicyVO`。 --- #### `GET /api/guard/policies/{id}` — 策略详情 **鉴权**:`guard.policy.view` **输入参数**:path `id`。 **输出字段**:`PolicyVO`。 --- #### `PUT /api/guard/policies/{id}` — 更新策略 **鉴权**:`guard.policy.edit` **输入参数**(`PolicyUpdateReq`):`name` / `comment`。 **输出字段**:返回更新后的 `PolicyVO`。 --- #### `DELETE /api/guard/policies/{id}` — 删除策略 **鉴权**:`guard.policy.delete` **输入参数**:path `id`。 **输出字段**:`data` 为 `null`。删除前需保证该策略未被任何域名引用。 --- ### 5.4 CC 规则 `/api/guard/policies/{id}/cc/rules` > CC = HTTP 速率限制规则(Connection / Concurrency Control)。挂在策略下,按 policy_id 隔离。 #### `GET /api/guard/policies/{id}/cc/rules` — CC 规则列表 **鉴权**:`guard.cc.list` **输入参数**:path `id` = `policy_id`;query `page` / `size`。 **输出字段**(`data.list[]` = `CcRuleVO`): | 字段 | 类型 | 说明 | |------|------|------| | `rule_id` | int64 | 规则 ID | | `name` | string | 规则名 | | `describe` | string | 描述 | | `matches[]` | array | 匹配条件结构(路径/方法/头部等) | | `stats` | object | 统计聚合维度 | | `limit` | object | 速率限制阈值 | | `action` | object | 命中动作(拦截/验证码/限速等) | | `stime` / `etime` | int64 | 生效起止时间 | | `status` | int32 | `1`=启用 `2`=禁用 | | `ctime` / `utime` | int64 | 创建/更新时间 | **可视化建议**:表格 + status 徽章。`matches` 太复杂建议折叠为"详情"按钮弹出。 --- #### `POST /api/guard/policies/{id}/cc/rules` — 创建 CC 规则 **鉴权**:`guard.cc.create` **输入参数**(`CcRuleCreateReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `name` | string | 是 | 规则名 | | `describe` | string | 否 | 描述 | | `matches[]` | array | 是 | 至少 1 项匹配条件 | | `stats` | object | 否 | 统计维度(IP/URI/UA 等) | | `limit` | object | 是 | 速率上限 | | `action` | object | 是 | 命中动作 | | `stime` / `etime` | int64 | 否 | 生效时间窗 | > 复杂结构建议先调 list 取一条样本作为模板。 **输出字段**:返回新建 `CcRuleVO`。 --- #### `GET /api/guard/policies/{id}/cc/rules/{rid}` — CC 规则详情 **鉴权**:`guard.cc.view` **输入参数**:path `id` = `policy_id`,`rid` = `rule_id`。会校验 `rule_id` 是否归属当前 `policy_id`,跨策略读取返回 `NotFound`。 **输出字段**:`CcRuleVO`。 --- #### `PUT /api/guard/policies/{id}/cc/rules/{rid}` — 更新 CC 规则 **鉴权**:`guard.cc.edit` **输入参数**:path `id` + `rid`,body 同 Create。 **输出字段**:返回更新后的 `CcRuleVO`。 --- #### `DELETE /api/guard/policies/{id}/cc/rules/{rid}` — 删除 CC 规则 **鉴权**:`guard.cc.delete` **输入参数**:path `id` + `rid`。 **输出字段**:`data` 为 `null`。 --- #### `PUT /api/guard/policies/{id}/cc/rules/{rid}/status` — 切换 CC 规则状态 **用途**:在列表页用开关组件启用/禁用规则。 **鉴权**:`guard.cc.edit` **输入参数**(`CcRuleStatusReq`): | 字段 | 类型 | 必填 | 取值 | |------|------|:---:|------| | `status` | int32 | 是 | `1`=启用 `2`=禁用 | > **重要**:`status` 是 **int32 数字**,不是 `"enabled"` / `"disabled"` 字符串。 **输出字段**:`data` 为 `null`。 **示例请求**: ```bash curl -X PUT https://waf.example.com/api/guard/policies/p_default/cc/rules/12345/status \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"status":1}' ``` --- ### 5.5 ACL 规则 `/api/guard/policies/{id}/acl/rules` > ACL = 访问控制列表(基于 IP / Header / URI 的放行/拦截)。结构与 CC 规则相似但**没有** `priority` 字段。 #### `GET /api/guard/policies/{id}/acl/rules` — ACL 规则列表 **鉴权**:`guard.acl.list` **输入参数**:path `id`,query `page` / `size`。 **输出字段**(`data.list[]` = `AclRuleVO`): | 字段 | 类型 | 说明 | |------|------|------| | `rule_id` | int64 | 规则 ID | | `name` | string | 规则名 | | `describe` | string | 描述 | | `matches[]` | array | 匹配条件 | | `action` | object | 命中动作(`block`/`page`/`pass`) | | `stime` / `etime` | int64 | 生效起止时间 | | `status` | int32 | `1`=启用 `2`=禁用 | | `ctime` / `utime` | int64 | 创建/更新时间 | **可视化建议**:表格 + status 徽章 + `action.type` chip 染色(block 红 / pass 绿 / page 蓝)。 --- #### `POST /api/guard/policies/{id}/acl/rules` — 创建 ACL 规则 **鉴权**:`guard.acl.create` **输入参数**(`AclRuleCreateReq`):`name` / `describe` / `matches[]`(≥1)/ `action` / `stime` / `etime`。 > `action.type` 为 `block` / `page` 时 `content` 字段服务端会 base64 编码存储;调用方传明文。 **输出字段**:返回新建 `AclRuleVO`。 --- #### `GET /api/guard/policies/{id}/acl/rules/{rid}` — ACL 规则详情 **鉴权**:`guard.acl.view` **输入参数**:path `id` + `rid`。 **输出字段**:`AclRuleVO`。 --- #### `PUT /api/guard/policies/{id}/acl/rules/{rid}` — 更新 ACL 规则 **鉴权**:`guard.acl.edit` **输入参数**:path `id` + `rid`,body 同 Create。 **输出字段**:返回更新后的 `AclRuleVO`。 --- #### `DELETE /api/guard/policies/{id}/acl/rules/{rid}` — 删除 ACL 规则 **鉴权**:`guard.acl.delete` **输入参数**:path `id` + `rid`。 **输出字段**:`data` 为 `null`。 --- #### `PUT /api/guard/policies/{id}/acl/rules/{rid}/status` — 切换 ACL 规则状态 **鉴权**:`guard.acl.edit` **输入参数**(`AclRuleStatusReq`):`status` int32(1=启用 2=禁用,**数字**)。 **输出字段**:`data` 为 `null`。 --- ### 5.6 黑白名单 `/api/guard/bwlist` 黑白名单分两层:**集合(set)** 是逻辑容器(黑/白/灰黑/灰白),**IP** 是集合内的具体条目。灰黑、灰白会在配置生成时分别按黑名单、白名单下发。 #### `GET /api/guard/bwlist/sets` — 名单集合列表 **鉴权**:`guard.bwlist.list` **输入参数**:`page` / `size` / `keyword`。 **输出字段**(`data.list[]` = `IPSetVO`): | 字段 | 类型 | 说明 | |------|------|------| | `id` | uint64 | 集合 ID | | `user_name` | string | 归属用户名(**P1.3 替换原 `user_id`**,来源 `cloud sys.users`) | | `name` | string | 集合名 | | `policy_id` | string | 关联策略 | | `ip_set_type` | int32 | **`1`=黑名单 `2`=白名单 `3`=灰黑名单 `4`=灰白名单**(数字枚举,**不是字符串**) | | `status` | int32 | `1`=禁用 `2`=启用 | | `count` | int64 | 总条目数 | | `enable_count` | int64 | 启用条目数 | | `unable_count` | int64 | 禁用条目数 | | `describe` | string | 描述(**不是 `remark`**) | | `is_default` | bool | 是否默认集合 | | `is_private` | bool | 是否私有 | | `private_domain_id` | string | 私有集合关联的域名 | | `ctime` / `utime` | int64 | 时间戳 | **可视化建议**: - **推荐图表**:表格(主图)+ KPI 卡(黑名单总数 / 白名单总数 / 启用率) - **派生指标**:饼图(`ip_set_type` 1/2/3/4 占比) --- #### `POST /api/guard/bwlist/sets` — 创建名单集合 **鉴权**:`guard.bwlist.create` **输入参数**(`BWListSetCreateReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `name` | string | 是 | 集合名 | | `ip_set_type` | int32 | 是 | `1`=黑名单 `2`=白名单 `3`=灰黑名单 `4`=灰白名单 | | `policy_id` | string | 否 | 关联策略 | | `status` | int32 | 否 | `1`=禁用 `2`=启用,默认 2 | | `describe` | string | 否 | 描述 | **输出字段**:返回新建 `IPSetVO`。 > 当 `policy_id` 非空时,后端会把集合同步到策略与该策略下域名的黑白名单配置;后续添加、批量添加或删除 IP 会触发配置下发。 --- #### `PUT /api/guard/bwlist/sets/{id}` — 更新名单集合 **鉴权**:`guard.bwlist.edit` **输入参数**(`BWListSetUpdateReq`):`name` / `status` / `describe`(全部可选)。 **输出字段**:返回更新后的 `IPSetVO`。 > 修改 `status` 会同步策略/域名配置并触发下发:启用集合进入黑/白名单,禁用集合进入禁用列表。 --- #### `DELETE /api/guard/bwlist/sets/{id}` — 删除名单集合 **鉴权**:`guard.bwlist.delete` **输入参数**:path `id`。 **输出字段**:`data` 为 `null`。**会级联删除集合内所有 IP 条目**。 > 删除集合会同时从策略/域名黑白名单配置中解绑,并触发相关域名重新下发。 --- #### `GET /api/guard/bwlist/sets/{id}/ips` — 集合内 IP 列表 **鉴权**:`guard.bwlist.ip_list` **输入参数**:path `id` = 集合 ID;query `page` / `size`。 **输出字段**(`data.list[]` = `IPVO`): | 字段 | 类型 | 说明 | |------|------|------| | `id` | uint64 | 条目 ID | | `ipset_id` | uint64 | 所属集合 | | `ip_addr` | string | IP 或 CIDR(**不是 `ip`**) | | `status` | bool | true=启用 false=禁用,默认 true | | `ctime` / `utime` | int64 | 时间戳 | > **不存在** `expires_at` / `remark` 字段。 **可视化建议**:表格。 --- #### `POST /api/guard/bwlist/sets/{id}/ips` — 单条添加 IP **鉴权**:`guard.bwlist.ip_add` **输入参数**(`BWListIPAddReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `ip_addr` | string | 是 | IP 或 CIDR | | `status` | bool | 否 | 默认 `true` | **输出字段**:返回新建 `IPVO`。 --- #### `POST /api/guard/bwlist/sets/{id}/ips/batch` — 批量添加 IP **用途**:一次导入数百条 IP(如威胁情报源),减少多次往返开销。 **鉴权**:`guard.bwlist.ip_add` **输入参数**(`BWListIPBatchAddReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `ips[]` | array | 是 | 至少 1 项;每项 `{ip_addr, status?}` | **输出字段**:`data` 为 `null` 或返回新增 ID 列表(按实现)。 --- #### `POST /api/guard/bwlist/sets/{id}/ips/batch/delete` — 批量删除 IP **用途**:一次删除集合下多条 IP(如清理过期封禁),一个事务原子完成,避免逐条删除的部分失败与审计刷屏。 **鉴权**:`guard.bwlist.ip_delete` **输入参数**(`BWListIPBatchDeleteReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `ip_ids[]` | array<int64> | 是 | 至少 1 项;要删除的 IP 条目 ID。删除范围限定在集合 `{id}` 内,不属于该集合的 ID 会被忽略 | **输出字段**:`data.deleted` 为实际删除条数。集合内无任一匹配返回 `404`。 --- #### `DELETE /api/guard/bwlist/ips/{id}` — 删除单条 IP > **重要**:删除路径是**顶级路径** `DELETE /api/guard/bwlist/ips/{id}`,**不是**嵌套在 sets 下的 `DELETE /sets/{sid}/ips/{id}`。 **鉴权**:`guard.bwlist.ip_delete` **输入参数**:path `id` = IP 条目 ID。 **输出字段**:`data` 为 `null`。 --- ### 5.7 IP 转发 `/api/guard/forwards` > 这是 **TCP/UDP 端口转发**(4 层),**不是路径转发 / 反向代理**(7 层)。 #### `GET /api/guard/forwards` — IP 转发列表 **鉴权**:`guard.forward.list` **输入参数**: | 字段 | 类型 | 说明 | |------|------|------| | `page` / `size` | int | 标准分页 | | `user_id` | string | 按归属用户过滤 | | `domain_id` | string | 按域名过滤 | | `status` | int32 | `1`=禁用 `2`=启用 | | `keyword` | string | 搜索 `domain` / `describe` | **输出字段**(`data.list[]` = `ForwardVO`): | 字段 | 类型 | 说明 | |------|------|------| | `id` | uint64 | 转发 ID | | `user_id` | string | 归属用户 UUID(保留兼容老平台) | | `user_name` | string | 归属用户名(**P1.3 新增**,来源 `cloud sys.users`) | | `domain` | string | 转发的域名/IP | | `domain_id` | string | 关联域名 ID | | `schema` | int32 | **`3`=TCP `4`=UDP**,默认 3 | | `port` | int32 | 端口 1-65535 | | `node_ipaddrs` | string | 源 IP 列表(逗号分隔) | | `describe` | string | 描述 | | `status` | int32 | `1`=禁用 `2`=启用 | | `src_setting` | json | 源设置 raw JSON | | `adv_settings` | json | 高级设置 raw JSON | | `node_setting` | json | 节点设置 raw JSON | | `dev_setting` | string | 设备设置 | | `ctime` / `utime` | int64 | 时间戳 | **可视化建议**: - **推荐图表**:表格 + status / schema 双 chip - **派生指标**:饼图(TCP/UDP 占比) --- #### `POST /api/guard/forwards` — 创建 IP 转发 **鉴权**:`guard.forward.create` **输入参数**(`ForwardCreateReq`): | 字段 | 类型 | 必填 | 取值 | 说明 | |------|------|:---:|------|------| | `domain` | string | 是 | `*.example.com` | 转发域名 | | `domain_id` | string | 是 | `d_8a3b1c` | 关联域名 | | `port` | int32 | 是 | `443` | 1-65535 | | `schema` | int32 | 否 | `3` | 3=TCP(默认)/4=UDP | | `node_ipaddrs` | string | 否 | `10.0.0.1,10.0.0.2` | 源 IP 列表 | | `describe` | string | 否 | | 描述 | | `status` | int32 | 否 | `2` | 1=禁 2=启用 | | `src_setting` / `adv_settings` / `node_setting` | json | 否 | | raw JSON 配置 | | `dev_setting` | string | 否 | | 设备配置 | > **不存在** `source_path` / `target` 字段。 **输出字段**:返回新建 `ForwardVO`。 --- #### `GET /api/guard/forwards/{id}` — IP 转发详情 **鉴权**:`guard.forward.view` **输入参数**:path `id`。 **输出字段**:`ForwardVO`。 --- #### `PUT /api/guard/forwards/{id}` — 更新 IP 转发 **鉴权**:`guard.forward.edit` **输入参数**:path `id`,body 同 Create(字段全部可选)。 **输出字段**:返回更新后的 `ForwardVO`。 --- #### `DELETE /api/guard/forwards/{id}` — 删除 IP 转发 **鉴权**:`guard.forward.delete` **输入参数**:path `id`。 **输出字段**:`data` 为 `null`。 --- ### 5.8 调度管理 `/api/guard/schedules` > **本模块只做 DNS 解析调度(域名解析模式切换 / 批量启停记录)。** 旧版 cron 定时任务接口已下架。 > > ⚠️ **异步生效约定**:本组接口**不直接调用三方 DNS API**,所有写操作仅落库老平台 `guard_db.dns_records` + `guard_db.dns_affairs`,最终通过 NSQ topic=`dns` 通知 zdns 服务异步生效。**前端 / 调用方必须轮询 affairs 接口获取最终状态**(初始 `AffairsStatus_Start` → `AffairsStatus_Succeed` / `AffairsStatus_Faild`)。 > > ⚠️ **数据真值源**:`dns_records.group_type` 是模式真相源,`guard_configs.parsing_state` 异步同步,均来自老平台 `guard_db`;存在最长 30s 不一致窗口。VO 同时返回两者,前端在不一致时显示"同步中" badge。 #### 5.8.1 域名调度 ##### `GET /api/guard/schedules/domains` — 域名列表 **鉴权**:`guard.schedule.list` **输入参数**: | 字段 | 类型 | 说明 | |------|------|------| | `page` / `size` | int | 分页(size 上限 100) | | `keyword` | string | 按域名模糊搜索 | | `user_id` | string | 按归属用户过滤(仅超管/总代有效) | | `mode` | int32 | `0`=全部 / `1`=源站(SRC) / `2`=节点(NODE) | **输出字段**(`data` = `ScheduleDomainListResp`): | 字段 | 类型 | 说明 | |------|------|------| | `list[].domain_id` | string | 域名 ID(`guard_configs.domain_id`) | | `list[].domain_name` | string | 域名 | | `list[].user_id` / `user_name` | string | 归属用户 | | `list[].parsing_state` | int32 | `guard_configs.parsing_state`(1=SRC / 2=NODE) | | `list[].dns_group_type` | int32 | `dns_records.group_type` 多数票(真值源,1=SRC / 2=NODE) | | `list[].src_count` | int | `group_type=1` 的记录数 | | `list[].node_count` | int | `group_type=2` 的记录数 | | `list[].src_records[]` | array | 源站解析摘要:`subdomain` / `record_type` / `record_line` / `value` / `status` | | `list[].node_records[]` | array | 节点解析摘要:`subdomain` / `record_type` / `record_line` / `value` / `status` | | `list[].last_affair_status` | string | 最近一条事务状态(`AffairsStatus_Start` / `AffairsStatus_Succeed` / `AffairsStatus_Faild`) | | `list[].last_affair_ctime` | int64 | 最近一条事务创建时间(毫秒) | | `list[].last_affair_message` | string | 最近一条事务消息(HTML 片段) | | `total` | int64 | 总条数 | --- ##### `POST /api/guard/schedules/domains/{id}/switch-mode` — 切换源站/节点 **鉴权**:`guard.schedule.switch` **输入参数**: | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | path `id` | string | 是 | `domain_id` | | body `target_mode` | int32 | 是 | `1`=源站(SRC) / `2`=节点(NODE) | | body `comment` | string | 否 | 事务备注(写入 `dns_affairs.message`) | **实现要点**:guard_db 单库事务内 `SELECT FOR UPDATE` 锁住该域名全部 dns_records → 更新 `group_type` 与 `switch_state=2`(切换中)→ INSERT `dns_affairs`(`status=AffairsStatus_Start`)→ 提交后 NSQ Cmd=0 通知。 **输出字段**:返回新建 `ScheduleAffairVO`,前端应将 `affairs_id` 写入轮询轮换。 --- ##### `POST /api/guard/schedules/domains/{id}/init` — 初始化解析 **鉴权**:`guard.schedule.init` **输入参数**:path `id`(`domain_id`);body 可选 `comment`。 **实现要点**:读取 `guard_domain_settings` 的源站/调度配置与 `domain_node_ships` 节点绑定,锁住该域名旧 `dns_records` 后删除并重建源站/节点记录;随后 INSERT `dns_affairs`,以 NSQ Cmd=0 通知 zdns 同步一次。 **输出字段**:返回新建 `ScheduleAffairVO`。 --- ##### `POST /api/guard/schedules/domains/{id}/reset` — 重置解析 **鉴权**:`guard.schedule.reset` **输入参数**:path `id`(`domain_id`);body 可选 `comment`。 **实现要点**:所有 `dns_records.switch_state` 归位为 `1`,`status` 回滚到 `last_status`;落事务后 NSQ Cmd=0 通知。 **输出字段**:返回新建 `ScheduleAffairVO`。 --- ##### `GET /api/guard/schedules/domains/{id}/records` — 域名解析记录 **鉴权**:`guard.schedule.records` **输入参数**: | 字段 | 类型 | 说明 | |------|------|------| | path `id` | string | `domain_id` | | `group_type` | int32 | `1`=SRC / `2`=NODE | | `status` | int32 | `1`=禁用 / `2`=启用 | | `page` / `size` | int | 分页 | **输出字段**(`data` = `ScheduleRecordsResp`): | 字段 | 类型 | 说明 | |------|------|------| | `list[]` | `DnsRecordVO` | 解析记录视图 | | `list[].record_id` | string | 主键 | | `list[].associated_id` | string | DNS 服务商侧解析记录 ID,用于确认记录已在 ZDNS/服务商侧重建或关联 | | `list[].domain` / `subdomain` / `value` | string | 域 / 子域 / 解析值 | | `list[].record_type` | int32 | DNS 记录类型内部编码 | | `list[].record_line` | int32 | 解析线路 | | `list[].ttl` | int64 | TTL(秒) | | `list[].status` / `last_status` | int32 | `1`=禁用 / `2`=启用 | | `list[].group_type` | int32 | `1`=SRC / `2`=NODE | | `list[].switch_state` | int32 | `1`=就绪 / `2`=切换中 | | `list[].ctime` / `utime` | int64 | 时间戳(毫秒) | > 直查 `zdns_db.dns_records`,只读,不落事务。 --- #### 5.8.2 解析调度 ##### `POST /api/guard/schedules/records/batch-status` — 批量启停记录 **鉴权**:`guard.schedule.batch` **输入参数**(`ScheduleBatchStatusReq`): | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `record_ids` | string[] | 是 | 目标 `record_id` 集合 | | `status` | int32 | 是 | `1`=禁用 / `2`=启用 | | `comment` | string | 否 | 事务备注 | **实现要点**:`UPDATE dns_records SET last_status=status, status=? WHERE record_id IN (?)`;落事务后 NSQ Cmd=0 通知。 **输出字段**:返回新建 `ScheduleAffairVO`。 --- #### 5.8.3 事务记录 ##### `GET /api/guard/schedules/affairs` — 事务列表 **鉴权**:`guard.schedule.affairs` **输入参数**: | 字段 | 类型 | 说明 | |------|------|------| | `page` / `size` | int | 分页 | | `user_id` | string | 按归属用户过滤 | | `status` | string | `AffairsStatus_Start` / `AffairsStatus_Succeed` / `AffairsStatus_Faild` | | `ctime_from` / `ctime_to` | int64 | 时间范围(毫秒) | | `domain_id` | string | 按受影响 domain 过滤(实现走 `content LIKE`) | **输出字段**(`data` = `ScheduleAffairListResp`):见下方 `ScheduleAffairVO`。 --- ##### `GET /api/guard/schedules/affairs/{id}` — 事务详情 **鉴权**:`guard.schedule.affairs` **输入参数**:path `id`(`affairs_id`)。 **输出字段**(`data` = `ScheduleAffairVO`): | 字段 | 类型 | 说明 | |------|------|------| | `affairs_id` | string | 事务 ID,格式 `{ts}_{rand10}` | | `user_id` / `user_name` | string | 操作人 | | `status` | string | `AffairsStatus_Start` / `AffairsStatus_Succeed` / `AffairsStatus_Faild`(字符串枚举,对齐老平台 MarshalJSON 行为) | | `message` | string | 事务消息,含 HTML 片段(如 `
`) | | `content` | string | 受影响 `domain_id`,逗号分隔 | | `json_content` | object | 扩展 JSON,含 outbox 重试计数等 | | `affairs_oper` | string | `AffairsOperType_Page` / `AffairsOperType_Cron` / `AffairsOperType_Cli` | | `ctime` / `utime` | int64 | 创建/更新时间(毫秒) | > **轮询建议**:前端发起写操作后,每 2~5s 轮询一次本接口,直到 `status` 切换为 `Succeed` 或 `Faild`;若长时间停在 `Start`,说明 outbox 重试中,可在 UI 显示"同步中"。 --- ### 5.9 WAF 规则 `/api/guard/waf/rules` > WAF 规则组挂在策略(`policy_id`)下,一个规则组含若干子规则(`rules[]`),由网关侧匹配 `zone` / `pattern` 决定命中后 `action`(拦截 / 记录 / 验证码)。 > > ⚠️ **状态约定**:本组接口的 `status` 字段统一约定 **`1`=禁用 `2`=启用**(S-6 修复后已与 `forwards` / `bwlist` / `schedules` 对齐)。`POST /status` 接口的 `oneof` 校验也是 `1|2`。 > > ⚠️ **路径 ID 取 `tag`**:`PUT/DELETE /api/guard/waf/rules/{id}` 中 `{id}` 是规则组的 `tag`(uint64 主键),**不是** `rule_id`(业务编号)。前端从列表的 `tag` 字段取值。 #### `GET /api/guard/waf/rules` — WAF 规则组列表 **鉴权**:`guard.waf.list` **输入参数**: | 字段 | 类型 | 说明 | |------|------|------| | `page` / `size` | int | 标准分页 | | `policy_id` | string | 按策略过滤(不传则返回当前用户可见的全部规则组) | **输出字段**(`data.list[]` = `WafGroupVO`): | 字段 | 类型 | 说明 | |------|------|------| | `tag` | uint64 | 规则组主键(路径 `{id}` 用) | | `rule_id` | int64 | 业务规则编号 | | `name` | string | 规则组名称 | | `describe` | string | 描述 | | `waf_type` | int32 | WAF 类型内部编码 | | `sub_rule_condition` | int32 | 子规则组合逻辑(`0`=AND / `1`=OR,按 model 实现为准) | | `scope` | string | 作用域(domain / path / 留空 = 全部) | | `action` | int32 | **`1`=block(拦截) `2`=log(记录) `3`=captcha(验证码)** | | `status` | int32 | `1`=禁用 `2`=启用 | | `policy_id` | string | 所属策略 ID | | `rules[]` | `WafRuleVO` | 子规则列表(每条含 zone / pattern / pattern_type / is_not) | | `ctime` / `utime` | int64 | 时间戳(毫秒) | **`WafRuleVO`(子规则)字段**: | 字段 | 类型 | 说明 | |------|------|------| | `tag` | uint64 | 子规则主键 | | `rule_id` | int64 | 业务规则编号 | | `group_id` | int64 | 所属规则组(关联 `WafGroupVO.tag` / `rule_id`) | | `zone` | string | 匹配区域(如 `URL` / `ARGS` / `HEADER` / `BODY`) | | `sub_field` | string | 区域内的子字段(如 header name) | | `pattern_type` | int32 | 匹配类型(精确 / 正则 / 包含等内部编码) | | `pattern` | string | 匹配表达式 | | `describe` | string | 描述 | | `is_not` | bool | true = 取反匹配 | | `ctime` / `utime` | int64 | 时间戳(毫秒) | **可视化建议**: - **推荐图表**:表格 + `action` / `status` 双 chip + `rules.length` 数字徽章 - **派生指标**:按 `action` 聚合的饼图(block / log / captcha 占比) --- #### `POST /api/guard/waf/rules` — 创建 WAF 规则组 **鉴权**:`guard.waf.create` **输入参数**(`WafGroupCreateReq`): | 字段 | 类型 | 必填 | 取值 | 说明 | |------|------|:---:|------|------| | `name` | string | 是 | `"SQL 注入防护"` | 规则组名称 | | `policy_id` | string | 是 | `"1"` | 挂载策略 | | `rule_id` | int64 | 否 | | 业务编号,不传由后端生成 | | `describe` | string | 否 | | 描述 | | `waf_type` | int32 | 否 | | WAF 类型内部编码 | | `sub_rule_condition` | int32 | 否 | | 子规则组合逻辑 | | `scope` | string | 否 | `"*.example.com"` | 作用域 | | `action` | int32 | 否 | `1` | 1=block / 2=log / 3=captcha | | `status` | int32 | 否 | `2` | 1=禁用 2=启用 | | `rules[]` | object[] | 否 | | 子规则列表(字段同 `WafRuleReq`,见下) | **`WafRuleReq`(子规则请求体)**: | 字段 | 类型 | 说明 | |------|------|------| | `rule_id` | int64 | 业务编号,不传由后端生成 | | `zone` | string | 匹配区域 | | `sub_field` | string | 区域内的子字段 | | `pattern_type` | int32 | 匹配类型 | | `pattern` | string | 匹配表达式 | | `describe` | string | 描述 | | `is_not` | bool | true = 取反 | **示例**: ```bash curl -X POST https://waf.example.com/api/guard/waf/rules \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "SQL 注入防护", "policy_id": "1", "describe": "OWASP Top 10 SQLi 黑名单", "action": 1, "status": 2, "rules": [ { "zone": "ARGS", "pattern_type": 2, "pattern": "union\\s+select", "describe": "SQLi: union select" } ] }' ``` **输出字段**:返回新建 `WafGroupVO`(含 `tag`、`rules[]` 已落库的子规则)。 > **不存在**字段:`enabled` 布尔(用 `status` 整数)/ `description`(用 `describe`)/ `domains[]`(作用域用 `scope` 字符串)。 --- #### `PUT /api/guard/waf/rules/{id}` — 更新 WAF 规则组 **鉴权**:`guard.waf.edit` **输入参数**(path `id` = 规则组 `tag`;body `WafGroupUpdateReq`,字段全部可选,按 flag 增量更新;`rules[]` 全量替换): | 字段 | 类型 | 说明 | |------|------|------| | `name` | string | 规则组名称 | | `describe` | string | 描述 | | `waf_type` | *int32 | 指针类型,未传不动 | | `sub_rule_condition` | *int32 | 指针类型 | | `scope` | string | 作用域 | | `action` | *int32 | 指针类型 | | `rules[]` | object[] | **全量替换**子规则列表 | > **本接口不更新 `status`**——启停切换走独立的 `PUT /api/guard/waf/rules/{id}/status`。 **输出字段**:返回更新后的 `WafGroupVO`。 --- #### `DELETE /api/guard/waf/rules/{id}` — 删除 WAF 规则组 **鉴权**:`guard.waf.delete` **输入参数**:path `id`(规则组 `tag`)。 **实现要点**:级联删除子规则(`waf_rules.group_id = tag`)。 **输出字段**:`data` 为 `null`。 --- #### `PUT /api/guard/waf/rules/{id}/status` — 切换 WAF 规则组启停 **鉴权**:`guard.waf.status` **输入参数**(path `id` = 规则组 `tag`;body `WafStatusReq`): | 字段 | 类型 | 必填 | 取值 | 说明 | |------|------|:---:|------|------| | `status` | int32 | 是 | `1` / `2` | 1=禁用 2=启用,`oneof=1 2` 校验 | **示例**: ```bash curl -X PUT https://waf.example.com/api/guard/waf/rules/42/status \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": 1 }' ``` **输出字段**:`data` 为 `null`。 --- ## §6 Analytics 统计分析 > 📊 **Analytics 统计分析** · 18 paths(含 80+ chart-key 单图接口)· 用于构建 WAF 监控大屏、运营报表、处置闭环 > 所有 `/api/analytics/*` 路径已对外,**只能增加字段或新增接口,不能修改或删除已发布路径**。 > 完整 chart-key → 数据形态映射见各小节"chart-key 索引表",每行都明确推荐图表。 ### 6.0 本章统一调用约定 Analytics 接口数量多但大多是同构的图表查询。本章用"统一约定 + 索引表 + 特殊接口展开"组织。 #### 鉴权与权限 **Header**:`Authorization: Bearer ` 或 `Authorization: ApiKey zck_...`,可附加 `Accept-Language: zh-CN` / `en-US`。 权限分三类: | 类型 | 判断方式 | 示例 | |------|----------|------| | 页面级只读 | `analytics..view` | `GET /api/analytics/overview/kpi` 需要 `analytics.overview.view` | | 特殊动作 | 表格或小节单独标出 | `POST /api/analytics/overview/export` 需要 `analytics.overview.export` | | 登录态 | 只要求认证,不要求具体业务权限 | `GET /api/analytics/glossary` | 使用 API Key 时,Key 的 `scopes` 必须覆盖接口所需权限。例如调用 `GET /api/analytics/access/status`,Key 至少包含 `analytics.access.view`。 #### 单图 GET 调用模板 ```bash curl -sS 'https://waf.example.com/api/analytics/access/status?window=last_24h&site_id=site-001' \ -H "Authorization: ApiKey $ZCLOUD_API_KEY" \ -H 'Accept-Language: zh-CN' ``` 返回统一 JSON 信封,图表 `data` 固定使用 **Chart 统一契约**([`docs/specs/chart-contract.md`](https://gitea.com/y2026/cloud/src/branch/main/docs/specs/chart-contract.md))。这是新系统对外唯一契约;老数据库表、聚合表和 ES 索引只作为内部数据源,不影响调用方入参或响应结构。 **图表 `data` 固定 5 字段,禁止 `series` / `totals` / `kpis` / `points` / `list` 作为对外顶层字段**: | 字段 | 类型 | 说明 | |------|------|------| | `chart_key` | string | 与请求 `` 完全一致 | | `render_hint` | enum | 8 词汇之一:`kpi` / `categorical_distribution` / `categorical_distribution_over_time` / `time_series_single` / `time_series_multi` / `topn` / `geo` / `table`,前端据此选渲染器 | | `schema` | object | 列元信息 `{dimensions:[{name,type,unit?,values?}], measures:[{name,type,unit?,format?}]}` | | `rows` | array | tidy 长表,一行一个观测(**禁止横铺**),空数据返回 `[]` | | `meta` | object | 调试字段 `{source, cache, latency_ms, partial?, available?, ...}` | > **window.granularity 不是入参**:客户传 `window=last_24h`,后端按时间窗大小**自动**选择 5m/1h/1d 聚合表,并在响应里回填实际命中的 `granularity`。 #### Batch 调用模板 适用于 `POST /api/analytics/batch` 与 `POST /api/analytics//batch`。 ```bash curl -sS -X POST https://waf.example.com/api/analytics/overview/batch \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "time_window": "last_24h", "site_id": "", "domain_id": "", "compare": false, "charts": [ { "key": "kpi" }, { "key": "bandwidth" } ] }' ``` Batch 响应的 `data.data` 以 chart-key 为键;`data.meta` 给出整体耗时、缓存命中率、失败图表数和实际时间窗。单个 chart 失败时优先查看该 chart 节点里的 `partial` / `error` 字段。 --- ### 6.1 术语表 #### `GET /api/analytics/glossary` — 统计术语表 **用途**:返回统计分析术语表,前端 tooltip 用此渲染"什么是 QPS / 拦截率"等说明。 **鉴权**:登录态 **输入参数**:无。 **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.terms` | map | key=术语短名,value=多语言解释 | **可视化建议**: - **推荐图表**:不直接做图;前端图表组件挂上 tooltip 时按 chart-key 在 terms map 里查询解释 **示例响应**: ```json { "code": 0, "data": { "terms": { "qps": "每秒请求数", "block_rate": "拦截率" } } } ``` --- ### 6.2 跨页 Batch #### `POST /api/analytics/batch` — 跨页通用批量查询 **用途**:在前端大屏页面初始化时一次性请求多个 chart-key,省去 N 次单图 GET 往返。后端并行执行各子查询并合并响应。 **鉴权**:按 `page` 字段映射到对应 `analytics..view` **输入参数**(请求体): | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `page` | string | 是 | `overview` | 必须是当前已支持页面之一:`overview` / `access` / `protect` / `ai` / `bot` / `alert` / `health` / `ops` / `closure` / `cache` | | `time_window` | string | 否 | `last_24h` | 同 `window` | | `stime` / `etime` | int64 | 否 | `1746748800000` | 自定义时间戳 | | `site_id` | string | 否 | | 站点过滤 | | `domain_id` | string | 否 | | 域名过滤 | | `target_user_id` | string | 否 | | 客户级切换被查看用户 | | `compare` | bool | 否 | `false` | 是否启用上一周期对比 | | `charts[]` | array | 是 | `[{key:"kpi"}]` | 至少 1 项 chart-key | > `logs`(Phase 1 原始日志)和 `reports`(Phase 4 报表中心)是独立 group,**不走 batch 模式**。 **输出字段**: | 字段 | 类型 | 说明 | |------|------|------| | `data.data` | map | key=chart-key;value 固定为 `{chart_key, render_hint, schema, rows, meta}` 5 字段 Chart 统一契约 | | `data.meta.elapsed_ms` | int | 整体耗时 | | `data.meta.cache_hit_ratio` | float | 缓存命中率(0-1) | | `data.meta.total_charts` | int | 请求 chart 总数 | | `data.meta.failed_charts` | int | 失败 chart 数 | | `data.meta.window` | object | 实际命中时间窗 | **可视化建议**: - **推荐图表**:本接口本身不直接做图,是数据装载层;前端拿到响应后按 chart-key 分发到各图表组件 - **派生指标**:`data.meta.cache_hit_ratio` 可在 dev 环境做 KPI 数字卡监控大屏的缓存效率 **示例请求/响应**: ```json // 请求 { "page": "overview", "time_window": "last_24h", "compare": false, "charts": [ { "key": "kpi" }, { "key": "bandwidth" } ] } ``` ```json // 响应 { "code": 0, "data": { "data": { "kpi": { "chart_key": "overview/kpi", "render_hint": "kpi", "schema": { "dimensions": [], "measures": [ { "name": "domain_count", "type": "integer", "unit": "" }, { "name": "requests", "type": "integer", "unit": "requests" }, { "name": "blocked", "type": "integer", "unit": "events" }, { "name": "block_rate", "type": "percent", "unit": "%" }, { "name": "qps", "type": "float", "unit": "qps" }, { "name": "ai_detect", "type": "integer", "unit": "events" } ] }, "rows": [ { "domain_count": 8, "requests": 12345, "blocked": 678, "block_rate": 5.49, "qps": 0.143, "ai_detect": 0 } ], "meta": { "source": "postgres", "cache": "miss", "latency_ms": 10 } }, "event-type": { "chart_key": "overview/event-type", "render_hint": "categorical_distribution", "schema": { "dimensions": [{ "name": "event_type", "type": "string" }], "measures": [{ "name": "count", "type": "integer", "unit": "events" }] }, "rows": [ { "event_type": "sql_injection", "count": 1234 }, { "event_type": "xss", "count": 567 } ], "meta": { "source": "elasticsearch", "cache": "miss", "latency_ms": 15, "partial": false } } }, "meta": { "elapsed_ms": 22, "cache_hit_ratio": 0, "total_charts": 2, "failed_charts": 0, "window": { "stime": 1746662400000, "etime": 1746748800000, "granularity": "1h" } } } } ``` > **注意**:上例中 `kpi` 的 measure 名 `requests` / `blocked` 是对外契约真值名(与 `pkg/chart/contract` 真值结构体对齐)。底层数据库字段名不对外暴露,前端严格按 `schema.measures[].name` 取值。 --- #### `POST /api/analytics/{page}/batch` — 页面级 Batch **用途**:与 `POST /api/analytics/batch` 等价,但 `page` 由 URL 决定(前端固定页面调用更直观)。 **鉴权**:根据 `{page}` 映射到对应 `analytics..view`。 **支持的 page 值**: | URL | 权限 | |-----|------| | `POST /api/analytics/overview/batch` | `analytics.overview.view` | | `POST /api/analytics/access/batch` | `analytics.access.view` | | `POST /api/analytics/protect/batch` | `analytics.protect.view` | | `POST /api/analytics/ai/batch` | `analytics.ai.view` | | `POST /api/analytics/bot/batch` | `analytics.bot.view` | | `POST /api/analytics/alert/batch` | `analytics.alert.view` | | `POST /api/analytics/health/batch` | `analytics.health.view` | | `POST /api/analytics/ops/batch` | `analytics.ops.view` | | `POST /api/analytics/closure/batch` | `analytics.closure.view` | | `POST /api/analytics/cache/batch` | `analytics.cache.view` | **输入/输出**:与 `POST /api/analytics/batch` 完全一致;调用方不需要在请求体里再传 `page`,即使传了也以路径中的页面名为准。 **可视化建议**:同上。 --- ### 6.3 单图 GET 调用入口 #### `GET /api/analytics/{page}/{chart}` — 单图通用入口 **用途**:拉取单个 chart-key 的数据。`{page}` 取值同 batch;`{chart}` 取值参见各页面小节的 chart-key 索引表。 **鉴权**:根据 `{page}` 映射到 `analytics..view`(少数特殊 chart 用独立权限,详见各小节)。 **输入参数**:见 [§A 通用查询参数](#a-analytics-通用查询参数)。 **输出字段**:统一信封 + `data`: - `data` 固定为 Chart 统一契约:`{chart_key, render_hint, schema, rows, meta}` - `schema` 声明 `rows` 的维度列与指标列;调用方不要从底层数据库字段名推断响应结构 **可视化建议**:前端按 `render_hint` 自动分发到对应图表组件;复杂图表的列定义以 `schema` 为准。 --- ### 6.4 总览页面(Overview) > 适用场景:WAF 防护监控大屏首页 KPI + 趋势 + 排行 + 地图。 下表所有 `GET` 接口都用 [§6.0 单图 GET 调用模板](#62-跨页-batch)、[§A 通用查询参数](#a-analytics-通用查询参数) 和 Chart 统一契约响应结构。 | API | chart-key | render_hint | 推荐图表 | 说明 | |-----|-----------|---|----------|------| | `GET /api/analytics/overview/kpi` | `kpi` | `kpi` | KpiGroupCard(6 measure) | 站点数 / requests / blocked / block_rate / qps / ai_detect | | `GET /api/analytics/overview/bandwidth` | `bandwidth` | `time_series_multi` | 折线图(双 Y 轴) | 总带宽与回源带宽时序 | | `GET /api/analytics/overview/request-attack` | `request-attack` | `time_series_multi` | 折线图(双系列) | 请求量 vs 攻击量对比 | | `GET /api/analytics/overview/event-type` | `event-type` | `categorical_distribution` | 饼图 / 环形图 | 事件类型分布(dim=event_type, measure=count) | | `GET /api/analytics/overview/waf-type` | `waf-type` | `categorical_distribution` | 饼图 / 环形图 | WAF 命中类型分布 | | `GET /api/analytics/overview/geo` | `geo` | `geo` | 中国/世界地图热力 | 攻击来源地理分布 | | `GET /api/analytics/overview/top-domains` | `top-domains` | `topn` | 横向 bar / 表格 | 被攻击域名 TOP 5 | | `GET /api/analytics/overview/recent-events` | `recent-events` | `table` | 时间线 / 表格(50 条) | 最近 WAF 事件流 | #### chart-key 数据形态详解 **`kpi`**(`render_hint = kpi`) `schema.measures` 共 6 项: | measure name | type | unit | 说明 | |---|---|---|---| | `domain_count` | integer | (空) | 站点数 | | `requests` | integer | requests | 时间窗内总请求量 | | `blocked` | integer | events | 时间窗内总拦截量 | | `block_rate` | percent | % | 拦截率 = blocked / requests | | `qps` | float | qps | QPS = requests / 窗口秒数 | | `ai_detect` | integer | events | AI 识别数(当前固定 0,后续接 ES 实数) | `rows` 单行:`[ { domain_count, requests, blocked, block_rate, qps, ai_detect } ]`。 > **对外字段名**:`requests` / `blocked` 是 API 契约字段名。底层数据库若仍使用 `request_today` / `attack_today` 等历史字段,由后端在服务层转换,不暴露给调用方。 **推荐图表**:`KpiGroupCard`(6 个 KPI 数字卡),`block_rate` 用百分比 + 进度条;`qps` 配迷你 sparkline。 --- **`bandwidth` / `request-attack`** — 时序双系列 输出 `[{ctime: int64ms, bandwidth: float, origin_bandwidth: float}, ...]` 或 `[{ctime, requests, attacks}, ...]`。 **推荐图表**:折线图,X 轴 `ctime`,Y 轴双系列。 --- **`event-type`**(`render_hint = categorical_distribution`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "event_type", type: "string" }]` | | `measures` | `[{ name: "count", type: "integer", unit: "events" }]` | `rows` 长表:`[ { event_type: "sql_injection", count: 1234 }, { event_type: "xss", count: 567 }, ... ]`。 **推荐图表**:`PieCard`(≤8 类自动饼图)/ `BarCard`(>8 类自动横向 bar);前端按 `categorical_distribution` 词汇分发。 --- **`waf-type`** — 维度分布 输出 `[{key: string, count: int}, ...]`。 **推荐图表**:饼图(≤ 8 类)或环形图。 --- **`geo`** — 地理热力 输出 `[{region: string, count: int}, ...]`,region 为国家/省份名。 **推荐图表**:地图热力(中国地图 + 世界地图叠加)。 --- **`top-domains`** — 域名排行 输出 `[{host: string, attack_count: int}, ...]`,按 attack_count DESC,最多 5 条。 **推荐图表**:横向 bar 图。 --- **`recent-events`** — 最近事件流 输出 `[{ctime, domain, attack_type, severity, ...}, ...]`,最多 50 条。 **推荐图表**:时间线 / 表格(按 ctime 倒序),可点击进入详情。 --- #### `POST /api/analytics/overview/export` — 总览页导出 **用途**:把 KPI 与图表快照导出为 CSV 或 JSON 文件,供线下分析或汇报。**返回原文文件流,不走信封**。 **鉴权**:`analytics.overview.export` **输入参数**(请求体): | 字段 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `format` | string | 是 | `csv` / `json` | 导出格式 | | `window` | string | 否 | `last_24h` | 时间窗 | | `charts[]` | array | 是 | `[{key:"kpi"}]` | 要导出的 chart-key 列表 | **输出**:直接返回文件流,`Content-Type: text/csv` 或 `application/json`,`Content-Disposition: attachment`。 **可视化建议**:不适合图表;触发后浏览器下载。 --- ### 6.5 访问分析页面(Access) > 适用场景:流量与质量分析大屏 — 看请求量、流量、缓存命中、状态码、耗时分布、运营商、TOP IP/URL、地域。 | API | chart-key | render_hint | 推荐图表 | 说明 | |-----|-----------|---|----------|------| | `GET /api/analytics/access/request-hm` | `request-hm` | `time_series_single`(无 compare)/ `time_series_multi`(compare) | LineCard | 请求量趋势;`compare=true` 走子形态 B(period 维度区分 current/previous) | | `GET /api/analytics/access/flow-hm` | `flow-hm` | `time_series_multi` | LineCard 多线 | 5 measure:`total_bytes` / `request_bytes` / `response_bytes` / `upstream_send` / `upstream_receive` | | `GET /api/analytics/access/cache-hm` | `cache-hm` | `time_series_multi` | 折线图(双 Y 轴) | 缓存命中次数 + 缓存字节趋势 | | `GET /api/analytics/access/bandwidth` | `bandwidth` | `time_series_multi` | LineCard 多线 | 4 measure:`bandwidth` / `origin_bandwidth` / `up_bandwidth` / `down_bandwidth` | | `GET /api/analytics/access/status` | `status` | `categorical_distribution_over_time` | StackedBarCard | 4 类 HTTP 状态码(dim=status_class enum["2xx","3xx","4xx","5xx"] + time)按时间堆叠 | | `GET /api/analytics/access/flow-duration` | `flow-duration` | `time_series_multi` | 折线图(3 分位) | 请求耗时 P50/P95/P99(D4:仅时间窗 ≤ 24h 走实时计算) | | `GET /api/analytics/access/isp` | `isp` | `categorical_distribution` | 饼图 | 运营商分布(移动/联通/电信/其它) | | `GET /api/analytics/access/top-ip` | `top-ip` | `topn` | 表格 / 横向 bar(含地理) | 访问 IP TOP(默认 10,可调 `top`) | | `GET /api/analytics/access/top-url` | `top-url` | `topn` | 表格 / 横向 bar | URL TOP,可按 `order=bytes_desc/cache_desc` 切换排序 | | `GET /api/analytics/access/geo` | `geo` | `geo` | 中国/世界地图热力 | 访问来源地理分布 | #### chart-key 数据形态详解 **`request-hm`** 无 compare(`render_hint = time_series_single`): | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "time", type: "timestamp", unit: "ms" }]` | | `measures` | `[{ name: "requests", type: "integer", unit: "requests" }]` | `rows`:`[ { time: 1746748800000, requests: 1234 }, ... ]`。 开 compare(`render_hint = time_series_multi` 子形态 B:`1 categorical + 1 ts + 1 measure`): | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "period", type: "enum", values: ["current","previous"] }, { name: "time", type: "timestamp", unit: "ms" }]` | | `measures` | `[{ name: "requests", type: "integer", unit: "requests" }]` | `rows`:`[ { period: "current", time: ..., requests: ... }, { period: "previous", time: ..., requests: ... }, ... ]` 长表,按 `period` pivot 成 2 条 series。 **推荐图表**:`LineCard`;compare 模式按 `period` pivot 双色实虚线,前端自动处理。 --- **`flow-hm`**(`render_hint = time_series_multi`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "time", type: "timestamp", unit: "ms" }]` | | `measures` | 5 项:`total_bytes` / `request_bytes` / `response_bytes` / `upstream_send` / `upstream_receive`(type=integer,unit=bytes,format=iec) | `rows`:`[ { time: ..., total_bytes: ..., request_bytes: ..., response_bytes: ..., upstream_send: ..., upstream_receive: ... }, ... ]`。 **推荐图表**:`LineCard` 多线(5 条)/ 堆叠面积。 --- **`cache-hm`** — 缓存趋势 输出 `[{ctime, cache_count, cache_bytes, cache_response}, ...]`。 > **真值字段**(D8):底层用 `total_cache_count` / `total_cache_bytes` / `total_cache_response_bytes`;返回时映射为 `cache_count` / `cache_bytes` / `cache_response`。 **推荐图表**:双 Y 轴折线(左轴次数,右轴字节)。 --- **`bandwidth`**(`render_hint = time_series_multi`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "time", type: "timestamp", unit: "ms" }]` | | `measures` | 4 项:`bandwidth` / `origin_bandwidth` / `up_bandwidth` / `down_bandwidth`(type=float,unit=bps) | `rows`:`[ { time: ..., bandwidth: ..., origin_bandwidth: ..., up_bandwidth: ..., down_bandwidth: ... }, ... ]`。 **推荐图表**:`LineCard` 多线(4 条)。 --- **`status`**(`render_hint = categorical_distribution_over_time`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "status_class", type: "enum", values: ["2xx","3xx","4xx","5xx"] }, { name: "time", type: "timestamp", unit: "ms" }]` | | `measures` | `[{ name: "count", type: "integer", unit: "requests" }]` | `rows` tidy 长表(**禁止横铺 c2xx/c3xx/c4xx/c5xx**): ```json [ { "status_class": "2xx", "time": 1746748800000, "count": 1200 }, { "status_class": "3xx", "time": 1746748800000, "count": 30 }, { "status_class": "4xx", "time": 1746748800000, "count": 8 }, { "status_class": "5xx", "time": 1746748800000, "count": 0 }, { "status_class": "2xx", "time": 1746752400000, "count": 1340 }, ... ] ``` 每个时间点必须覆盖 4 个 status_class(缺则补 `count: 0`,前端堆叠柱图依赖完整网格)。 **推荐图表**:`StackedBarCard`(按 `status_class` 堆叠时序)。前端按 `categorical_distribution_over_time` 词汇分发。 --- **`flow-duration`** — 耗时分位 输出 `{p50, p95, p99}` 或时序数组。 > **真值边界**(D4):百分位字段**仅时间窗 ≤ 24h** 时由 ES 实时计算;窗口更长时该接口返回 `available:false`。 **推荐图表**:折线图(3 分位曲线)或 KPI 数字卡(3 个)。 --- **`isp`** — 运营商分布 输出 `{mobile, unicom, telecom, other}`。 **推荐图表**:饼图(4 段)。 --- **`top-ip`** — IP TOP 输出 `[{remote_addr, count, country, region, isp}, ...]`,最多 `top` 条。 **推荐图表**:表格(含国旗 + 地区 + 运营商);或横向 bar 图。 --- **`top-url`** — URL TOP 输出 `[{url, request_count, request_bytes, cache_bytes}, ...]`。 **推荐图表**:表格(默认);或横向 bar(可按字节/缓存切换)。 --- **`geo`** — 地理热力 输出 `[{region, count}, ...]`。 **推荐图表**:地图热力。 --- ### 6.6 防护分析页面(Protect) > 适用场景:WAF / CC / DDoS 三大防护引擎的命中分析。 | API | chart-key | render_hint | 推荐图表 | 说明 | |-----|-----------|---|----------|------| | `GET /api/analytics/protect/overview` | `overview` | `kpi` | 多 KPI 卡 | WAF/CC/DDoS 总量汇总(时序由 statistics 系列独立 chart 提供) | | `GET /api/analytics/protect/waf/statistics` | `waf/statistics` | `time_series_multi` | 折线图 | WAF 命中趋势(waf 命中数 + 总攻击数) | | `GET /api/analytics/protect/waf/types` | `waf/types` | `categorical_distribution` | 饼图 / 横向 bar | WAF 命中类型分布(SQL 注入/XSS/扫描器等) | | `GET /api/analytics/protect/waf/top-ip` | `waf/top-ip` | `topn` | RankingCard / 横向 bar | dim=ip(string), measure=count(events),country/province 进 `meta.row_extras` | | `GET /api/analytics/protect/waf/geo` | `waf/geo` | `geo` | GeoHeatmapCard | dim=country(geo), measure=count(events);`provinces` 暂存 `meta.provinces` | | `GET /api/analytics/protect/cc/statistics` | `cc/statistics` | `time_series_single` | 折线图 | CC 命中趋势 | | `GET /api/analytics/protect/cc/top-ip` | `cc/top-ip` | `topn` | 表格 / 横向 bar | CC 攻击 IP TOP | | `GET /api/analytics/protect/cc/geo` | `cc/geo` | `geo` | 地图热力 | CC 攻击地域 | | `GET /api/analytics/protect/cc/top-url` | `cc/top-url` | `topn` | 表格 | CC 攻击 URL TOP | | `GET /api/analytics/protect/ddos/statistics` | `ddos/statistics` | `time_series_multi` | 折线图(双 Y 轴) | DDoS 事件数 + 峰值带宽时序 | | `GET /api/analytics/protect/ddos/types` | `ddos/types` | `categorical_distribution` | 饼图 / 横向 bar | DDoS 攻击类型分布(syn flood/udp flood 等) | | `GET /api/analytics/protect/ddos/top-ip` | `ddos/top-ip` | `topn` | 表格 | DDoS 源 IP TOP(含峰值带宽) | #### chart-key 数据形态详解(关键差异) **`overview`**:`render_hint = kpi`,`rows = [{waf, cc, ddos_bytes}]`(单行汇总,dimensions=[],measures=waf/cc/ddos_bytes)。推荐 3 KPI 卡;时序由 `protect/waf/statistics` 与 `protect/ddos/statistics` 独立 chart 提供。 **`waf/statistics`**:`render_hint = time_series_multi`,`rows = [{time, waf, attack_count}, ...]` tidy 长表(time=ms 时间戳,2 measure 双线)。推荐折线图(双系列)。 **`waf/types`** / **`ddos/types`**:`render_hint = categorical_distribution`,`rows = [{waf_type, count}, ...]` / `[{ddos_type, count}, ...]` tidy 长表,用饼图或横向 bar。 **`waf/top-ip`**(`render_hint = topn`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "ip", type: "string" }]` | | `measures` | `[{ name: "count", type: "integer", unit: "events" }]` | `rows`:`[ { ip: "1.2.3.4", count: 1234 }, { ip: "5.6.7.8", count: 567 }, ... ]`,已按 count DESC 排序。country / province 等附加列进 `meta.row_extras`(前端按需取,不入 schema 列)。 **推荐图表**:`RankingCard` / 横向 bar 图。 --- **`ddos/top-ip`**:含地理的 IP TOP,推荐含国旗的表格。 --- **`waf/geo`**(`render_hint = geo`) | schema 部分 | 内容 | |---|---| | `dimensions` | `[{ name: "country", type: "geo" }]` | | `measures` | `[{ name: "count", type: "integer", unit: "events" }]` | `rows`:`[ { country: "China", count: 1234 }, { country: "US", count: 567 }, ... ]`。 > **provinces 数据**暂存 `meta.provinces`(结构 `[{province, count}]`)。当前前端 GeoHeatmapCard 渲染主图用 `rows`,省级钻取用 `meta.provinces`;后续如需要省级独立图表,再新增 `protect/waf/geo-provinces` chart-key。 **推荐图表**:`GeoHeatmapCard`(中国 / 世界地图热力)。 --- **`cc/geo`**:`render_hint = geo`,`rows = [{country, count}, ...]`,地图热力。 **`ddos/statistics`**:`render_hint = time_series_multi`,`rows = [{time, events, bandwidth}, ...]` tidy 长表(events=integer events,bandwidth=float bytes/iec)。推荐双 Y 轴折线。 --- ### 6.7 AI 识别页面(AI) > 当前 AI 页面路径已发布。`/logs` 走独立 `analytics.ai.logs` 权限,其它走 `analytics.ai.view`。 > 第一版多数 chart 返回 BatchChartResult 占位(`rows: []`,`meta.available = false`,`meta.reason` 说明原因),路径与契约稳定,后端可在保持路径不变的前提下逐步升级真实数据。 | API | chart-key | 推荐图表 | 说明 | |-----|-----------|----------|------| | `GET /api/analytics/ai/attack-trend` | `attack-trend` | 折线图 | AI 攻击趋势 | | `GET /api/analytics/ai/top-ip` | `top-ip` | 表格 / 横向 bar | AI 命中 IP TOP | | `GET /api/analytics/ai/top-url` | `top-url` | 表格 | AI 命中 URL TOP | | `GET /api/analytics/ai/detection` | `detection` | KPI 数字卡 / 雷达图 | AI 检测能力面板 | | `GET /api/analytics/ai/test-results` | `test-results` | 表格 / 柱图 | AI 测试结果 | | `GET /api/analytics/ai/logs` | `logs` | 表格(明细,分页) | AI 命中日志明细(独立权限 `analytics.ai.logs`) | > **占位响应**:未升级的 chart 返回标准 BatchChartResult(5 字段齐全,`rows: []`,`meta.available = false`,`meta.reason` 说明原因),前端应渲染"暂无数据"占位卡片,不展示假数据。 --- ### 6.8 主动防护 / Bot 页面 > 适用场景:识别和分析爬虫/Bot 流量。 | API | chart-key | 推荐图表 | 说明 | |-----|-----------|----------|------| | `GET /api/analytics/bot/statistics` | `statistics` | KPI(6 个) | Bot 请求/会话/IP/已知未知 Bot 总览(趋势线由独立 chart 提供,未拆时不展示) | | `GET /api/analytics/bot/advance-warn` | `advance-warn` | 表格 | Bot 预警列表 | | `GET /api/analytics/bot/browser` | `browser` | 饼图 | 浏览器分布(chrome/safari/firefox/edge/wechat/other) | | `GET /api/analytics/bot/operating` | `operating` | 饼图 | 操作系统分布(android/ios/windows/mac/other) | | `GET /api/analytics/bot/geo` | `geo` | 地图热力 | Bot 地域分布 | | `GET /api/analytics/bot/top-agent` | `top-agent` | 表格 / 横向 bar | User-Agent TOP | | `GET /api/analytics/bot/top-ip` | `top-ip` | 表格(含地理) | Bot IP TOP | | `GET /api/analytics/bot/scatter` | `scatter` | 散点图 | Bot 预警散点(X=ctime / Y=top_visit_count,size=top_ip_count) | | `GET /api/analytics/bot/sessions` | `sessions` | 表格(分页) | Bot 会话列表(独立权限 `analytics.bot.session`) | | `GET /api/analytics/bot/sessions/{sid}` | - | 时间线 | 单个 Bot 会话时间线详情(独立权限 `analytics.bot.session`) | #### chart-key 数据形态详解 **`statistics`**:`render_hint = kpi`,`rows = [{requests, sessions, ips, known_bot, unknown_bot, req_per_session}]`(单行汇总,6 measure)。推荐 6 个 KPI 卡;请求/会话趋势由独立 chart 提供(当前未拆,需要时新增 `bot/sessions-trend`)。 **`browser`**:`render_hint = categorical_distribution`,`rows = [{browser, count}, ...]`(6 行 enum:chrome/safari/firefox/edge/wechat/other),饼图。 **`operating`**:`render_hint = categorical_distribution`,`rows = [{os, count}, ...]`(5 行 enum:android/ios/windows/mac/other),饼图。 **`scatter`**:`render_hint = table`(无 scatter hint,table 兜底),`rows = [{time, session_id, top_visit_count, top_ip_count, top_ua_count}, ...]`,由前端解析 X/Y/size 渲染散点图。 **`sessions/{sid}`**:`render_hint = table`,`rows = [{time, uri, remote_addr, method, status}, ...]`(5 字段裁剪),是该 session 的全量访问记录时间线;未传 session_id(通过 query `order` 参数)时返空。 --- ### 6.9 告警统计页面(Alert) > 适用场景:告警总览 + 列表 + 单条详情 + 确认。 | API | 方法 | chart-key | 推荐图表 | 说明 | |-----|------|-----------|----------|------| | `GET /api/analytics/alert/total` | GET | `total` | KPI 数字卡 | 告警总数 | | `GET /api/analytics/alert/hm` | GET | `hm` | 折线图 / 热力图 | 告警趋势(按 ctime 聚合 count) | | `GET /api/analytics/alert/types` | GET | `types` | 饼图 | 告警类型分布(按 policy_type) | | `GET /api/analytics/alert/domains` | GET | `domains` | 横向 bar / 表格 | 告警域名排行 | | `GET /api/analytics/alert/list` | GET | `list` | 表格(分页) | 告警列表 | | `GET /api/analytics/alert/{id}` | GET | - | 详情卡片 | 单条告警详情(含处置元信息) | | `PATCH /api/analytics/alert/{id}/ack` | PATCH | - | 不适合图表 | 确认指定告警 | #### `GET /api/analytics/alert/{id}` 输出字段 | 字段 | 类型 | 说明 | |------|------|------| | `id` | int | 告警 ID | | `uuid` | string | 告警 UUID | | `policy_type` | string | 告警类型 | | `title` / `body` | string | 告警标题 / 内容 | | `domain` / `domain_id` | string | 关联域名 | | `status` | int | 告警状态(0/1/2/3,参见 D10) | | `ctime` | int64 | 创建时间 | | `last_update_timestamp` | int64 | 最后更新时间 | | `process_uid` | string | 处置人(D10 真值) | | `process_time` | int64 | 处置时间(D10 真值) | | `user_id` | string | 归属用户 | > 非超管按 `user_id` 强制过滤;越权读取返回 404。所需权限 `analytics.alert.view`。 #### `PATCH /api/analytics/alert/{id}/ack` 请求 **鉴权**:`analytics.alert.ack` **输入**:path `id`,body 可空。 **输出**:`data` 为 `null`。 --- ### 6.10 业务健康(Health · Phase 3) > 适用场景:分析业务可用性 + 源站质量 + 慢 URI + 地域质量。 | API | chart-key | 推荐图表 | 说明 | |-----|-----------|----------|------| | `GET /api/analytics/health/summary` | `summary` | KPI 数字卡(6 个) | c2xx/c3xx/c4xx/c5xx/n4xx/n5xx 总数 | | `GET /api/analytics/health/status-breakdown` | `status-breakdown` | 堆叠柱图(时序) | 状态码三层 c/n/a 拆分(c=客户端、n=网关、a=应用) | | `GET /api/analytics/health/origin-errors` | `origin-errors` | 表格 / 横向 bar | 源站异常排行(ES upstream_addr terms,仅 upstream_status >= 500) | | `GET /api/analytics/health/origin-latency` | `origin-latency` | 折线图(3 系列) | 源站时延(移动/联通/电信 平均时延) | | `GET /api/analytics/health/slow-uri` | `slow-uri` | 表格 | 慢 URI TOP(第一版按命中次数 TOP;慢请求排序后续按真实耗时数据接入) | | `GET /api/analytics/health/availability` | `availability` | KPI / 多线折线 | HTTP/Ping/DNS/TCP/Page/IPv6 可用率 + 不可用时长 | | `GET /api/analytics/health/geo-isp-quality` | `geo-isp-quality` | 表格 / 地图 | 地域/运营商质量 | > **真值边界**:percentile / p50 / p95 / p99 在 chart 与 statistic 包**均无**字段;时间窗 ≤ 24h 才走 ES 实时 percentile(D4)。可用性表 `m_ava_domain.*AvailableDomain` 主键是 `domain_or_ip`(不是 `domain_id`)。 --- ### 6.11 平台运维(Ops · Phase 5) > 适用场景:平台级运营视角 — 看高流量/高错误用户和域名、源站异常、节点容量。 > **权限边界**:`analytics.ops.view` 只读全平台数据,**不允许**传 `target_user_id` 切换视角;`analytics.ops.admin` 才能切换。普通客户级账号即使有 view 也不能访问 ops。 | API | chart-key | 推荐图表 | 说明 | |-----|-----------|----------|------| | `GET /api/analytics/ops/summary` | `summary` | KPI 数字卡(6 个) | 全平台容量摘要(请求/字节/峰值带宽/源站带宽/活跃域名/活跃用户) | | `GET /api/analytics/ops/traffic-users` | `traffic-users` | 横向 bar / 表格 | 高流量用户 TOP(按 total_bytes DESC) | | `GET /api/analytics/ops/traffic-domains` | `traffic-domains` | 横向 bar / 表格 | 高流量域名 TOP | | `GET /api/analytics/ops/error-users` | `error-users` | 表格 | 高错误用户 TOP(按 c5xx DESC,含 c4xx/n5xx) | | `GET /api/analytics/ops/error-domains` | `error-domains` | 表格 | 高错误域名 TOP | | `GET /api/analytics/ops/origin-errors` | `origin-errors` | 表格 | 源站异常排行 | | `GET /api/analytics/ops/nodes` | `nodes` | 表格 / 拓扑图 | 节点/机房视图(RPC GetWafIpWithMachineRoom + ES `server_addr/bind_addr`) | | `GET /api/analytics/ops/query-pressure` | `query-pressure` | 折线图(占位) | ES 查询压力(依赖埋点,第一版返回 `available:false`) | > **真值边界**:节点系统指标(CPU/内存/磁盘)chart **不存**,需走外部 zabbix。本期不实现。`node_id` / `server_node` 是禁字段。 --- ### 6.12 处置闭环(Closure · Phase 6) > 适用场景:在一个页面同时处理告警和风险队列;支持批量确认。 > **真值字段**(D10):`process_uid` / `process_time` / `status` / `level`。**禁字段**:`handle_user` / `handle_time` / `risk_score` / `alert_status`。AlertRecord.status (0/1/2/3) 与 RiskRecord.status (1/2) 语义不同,前端 i18n key 不可共用。 | API | 方法 | chart-key | 推荐图表 | 说明 | |-----|------|-----------|----------|------| | `GET /api/analytics/closure/summary` | GET | `summary` | KPI 数字卡(5 个) | 待处理告警/风险数 + 已处置数 + 平均处置时长(ms) | | `GET /api/analytics/closure/alerts` | GET | `alerts` | 表格(分页) | 待处理告警队列(status=0) | | `GET /api/analytics/closure/risks` | GET | `risks` | 表格(分页) | 待处理风险队列(后续接 RiskRecord 表;当前无数据时返回空列表) | | `GET /api/analytics/closure/trend` | GET | `trend` | 折线图 / 堆叠柱 | 处置历史趋势(按 ctime + status 分组) | | `POST /api/analytics/closure/alerts/confirm` | POST | - | 不适合图表 | 批量确认告警(代理 `/api/alert/records/confirm`) | | `POST /api/analytics/closure/risks/confirm` | POST | - | 不适合图表 | 批量确认风险(代理 `/api/chart/risk/events/:event_id/confirm`) | #### `summary` 输出字段 ```json { "alerts_pending": 12, "alerts_handled_today": 8, "risks_pending": 0, "risks_handled_today": 0, "avg_handle_time_ms": 0 } ``` #### confirm 接口请求体 ```json { "ids": ["a1", "a2", "a3"], "remark": "已加黑名单" } ``` **鉴权**:`analytics.closure.confirm` **输出**:`data` 为 `null`。 --- ### 6.13 缓存收益(Cache · Phase 6) > 适用场景:分析 CDN/边缘缓存对回源带宽的节省价值。 > **真值字段**(D8):`total_cache_count` / `total_cache_bytes` / `total_cache_response_bytes`。**禁字段**:`cache_count` / `cache_bytes` / `cache_hit` 单字段命名(不存在)。 | API | chart-key | 推荐图表 | 说明 | |-----|-----------|----------|------| | `GET /api/analytics/cache/summary` | `summary` | KPI 数字卡(4 个) | 命中率 / 节省回源字节 / 命中次数 / 平均缓存对象大小 | | `GET /api/analytics/cache/trend` | `trend` | 折线图(双 Y 轴) | 命中率趋势(请求数 vs 缓存命中数) | | `GET /api/analytics/cache/top-uri` | `top-uri` | 表格 / 横向 bar | URI TOP(命中次数/命中率/节省字节) | | `GET /api/analytics/cache/content-types` | `content-types` | 饼图 | 内容类型分布(response_content_type 聚合) | #### `summary` 输出字段 | 字段 | 类型 | 说明 | |------|------|------| | `hit_rate` | float | 缓存命中率 = total_cache_count / request_count | | `saved_response_bytes` | int | 节省回源带宽(源站本应承担但缓存挡住的字节) | | `total_cache_count` | int | 命中次数 | | `total_cache_bytes` | int | 命中字节 | | `avg_object_bytes` | float | 平均缓存对象大小 = total_cache_bytes / total_cache_count | | `request_count` | int | 总请求数 | **业务化指标**: - 缓存命中率 = `total_cache_count / request_count` - 节省回源带宽 = `total_cache_response_bytes` - 平均缓存对象大小 = `total_cache_bytes / total_cache_count` --- ### 6.14 原始日志(Logs · Phase 1) > 原始日志是明细查询,不是图表单图接口;它使用统一鉴权方式,但请求/响应以列表、详情、导出为主。 | API | 方法 | 权限 | 推荐图表 | 说明 | |-----|------|------|----------|------| | `GET /api/analytics/logs` | GET | `analytics.logs.view` | 表格(分页 + 12 字段筛选) | 分页查询原始访问/攻击日志 | | `GET /api/analytics/logs/{uuid}` | GET | `analytics.logs.view` | 详情卡片(7 区块) | 单条日志详情(basic/request/response/upstream/protection/waf_detail/ai_detail) | | `POST /api/analytics/logs/export` | POST | `analytics.logs.export` | 不适合图表 | 字段白名单导出(csv/json,size ≤ 10000,超出走异步任务) | #### `GET /api/analytics/logs` 筛选字段 12 字段筛选:`uuid` / `session_id` / `remote_addr` / `host` / `uri` / `method` / `status` / `z_final_action` / `z_final_type` / `z_final_mod` / `z_final_action_type` / `z_white`。 > **真值边界**:`z_final_action` 整数枚举 `0=放行 / 1=拦截 / 2=验证码`;`z_white` 是独立 bool 字段(白名单命中),**不参与** action 枚举。**禁字段**:`match_content` / `match_area` / `hit_rule` / `rule_desc`(chart 与 statistic 包均无)。 #### 第一版兜底响应 第一版 stub 返回: ```json { "available": false, "reason": "原始日志 ES 查询待接入..." } ``` 列表/详情/导出契约稳定,后续接 ES `zcloud-access-*`。 --- ### 6.15 报表中心(Reports · Phase 4) > 报表中心是模板、生成、下载类接口,不使用单图响应结构。列表和详情仍使用统一 JSON 信封;下载接口返回文件流。 | API | 方法 | 权限 | 推荐图表 | 说明 | |-----|------|------|----------|------| | `GET /api/analytics/reports/templates` | GET | `analytics.reports.view` | 表格 / 卡片网格 | 模板列表(含 `platform_only` 标记) | | `GET /api/analytics/reports` | GET | `analytics.reports.view` | 表格(分页) | 报表历史列表 | | `GET /api/analytics/reports/{id}` | GET | `analytics.reports.view` | 详情卡片 | 单条报表详情(状态、参数、产物 URL) | | `POST /api/analytics/reports/generate` | POST | `analytics.reports.generate` | 不适合图表 | 触发生成(`platform-summary` 模板需 `analytics.reports.platform`) | | `GET /api/analytics/reports/{id}/download` | GET | `analytics.reports.download` | 不适合图表 | 下载产物(pdf/csv/json/html) | **模板枚举**(D7):`protection-value` / `asset-risk` / `attack-source` / `business-health` / `platform-summary`(**仅平台运维/超管**) / `raw-log-export`。 **异步阈值**:预估行数 ≤ 100k 走同步;超出强制异步返回 `task_id`,前端轮询 `/reports/:id` 获取状态 + 下载链接。第一版同步生成超时 30 秒,超时降级为异步。 #### generate 请求体 ```json { "template": "protection-value", "format": "pdf", "window": "last_30d", "stime": 1735660800000, "etime": 1738339200000, "filters": {} } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `template` | string | 是 | 模板名(D7 闭集) | | `format` | string | 是 | `pdf` / `csv` / `json` / `html` | | `window` | string | 否 | 时间窗别名 | | `stime` / `etime` | int64 | 否 | 自定义时间戳 | | `filters` | object | 否 | 模板专属筛选条件 | --- ### 6.16 CLI 对应关系 Analytics API 均由 `zcloud analytics` 命令适配: ```bash # 原 6 page zcloud analytics overview kpi --format json zcloud analytics access status --window last_24h --format json zcloud analytics protect waf/types --format json zcloud analytics ai logs --page 1 --size 20 --format json zcloud analytics bot session --format json zcloud analytics alert ack # 2026-04-30 chart-rebuild 6 phase 扩展(13 条新命令) zcloud analytics health summary --window last_24h --format json zcloud analytics ops traffic-users --top 20 --format json zcloud analytics closure summary --format json zcloud analytics cache summary --format json zcloud analytics logs list --window last_24h --status 403 --format json zcloud analytics logs detail req-abc123 --format json zcloud analytics logs export --format csv --fields ctime,uuid,host,uri,status > logs.csv zcloud analytics closure alerts confirm --ids a1,a2,a3 zcloud analytics closure risks confirm --ids ev_001,ev_002 zcloud analytics reports templates --format json zcloud analytics reports list --format json zcloud analytics reports describe r-001 --format json zcloud analytics reports generate --template protection-value --window last_30d --format pdf zcloud analytics reports download r-001 --output report.pdf ``` 后续如果新增 Analytics API,必须同步增加或确认已有 CLI 适配;如果只是新增 chart-key,至少要更新 CLI chart-key 清单与文档。 --- ## §7 套餐目录(Plan) > **对外开放范围说明**:仅以下两个只读接口对外开放,用于查询套餐目录。套餐的创建/编辑/删除、为用户开通、订阅查询,以及订单的**续费/变更/退款/审核**(`/api/plan/orders/*`),均属平台控制台管理操作,直接操作在线计费数据,**不在对外对接 API/CLI 范围**(仅平台运维经控制台 + RBAC 使用)。 ### `GET /api/plan/plans` — 套餐列表(分页) 查询套餐目录,支持按产品类型和关键词过滤,分页返回。 **所需权限**:`plan.plan.list` **Query 参数** | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|:---:|--------|------| | `page` | int | 否 | 1 | 页码(从 1 开始) | | `size` | int | 否 | 20 | 每页条数 | | `prod_type` | int | 否 | 0 (全部) | 产品类型过滤:2=WAF · 4=Monitor · 32=GFIP | | `keyword` | string | 否 | — | 套餐名称关键词模糊搜索 | **响应 `data` 字段** ```json { "list": [ { "plan_id": "...", "name": "基础版", "prod_type": 2, "price": 99.00, "valid": 365, "level": 1, "open_status": true, ... } ], "total": 10, "page": 1, "size": 20 } ``` **示例** ```bash # Bearer Session curl -H "Authorization: Bearer $TOKEN" \ "$API/api/plan/plans?prod_type=2&keyword=基础&page=1&size=20" # API Key curl -H "Authorization: ApiKey zck_prefix.secret" \ "$API/api/plan/plans?prod_type=2" ``` --- ### `GET /api/plan/plans/{id}` — 套餐详情 按套餐 ID 查询单个套餐的完整信息(含 `content` 配额 JSON)。 **所需权限**:`plan.plan.view` **Path 参数** | 参数 | 类型 | 必填 | 说明 | |------|------|:---:|------| | `id` | string | 是 | 套餐 ID(UUID) | **响应 `data` 字段(`PlanVO`)** | 字段 | 类型 | 说明 | |------|------|------| | `plan_id` | string | 套餐唯一 ID(UUID) | | `name` | string | 套餐名称 | | `content` | object | 套餐配额 JSON(各产品类型对应字段不同) | | `price` | float | 套餐价格(保留 2 位小数) | | `scene` | string | 适用场景描述 | | `comment` | string | 备注 | | `open_status` | bool | 是否公开售卖 | | `valid` | int64 | 有效期(天数) | | `effect` | int32 | 生效方式 | | `level` | int64 | 套餐等级 | | `creator_id` | string | 创建者 ID | | `ctime` | int64 | 创建时间(Unix 毫秒) | | `utime` | int64 | 更新时间(Unix 毫秒) | | `version` | string | 套餐来源版本(cloud / zmod) | | `prod_type` | int32 | 产品类型(2=WAF 4=Monitor 32=GFIP) | **示例** ```bash curl -H "Authorization: Bearer $TOKEN" \ "$API/api/plan/plans/550e8400-e29b-41d4-a716-446655440000" ``` **CLI 等价命令** ```bash zcloud plan list --prod-type 1 --keyword 基础版 zcloud plan describe ``` --- ## §8 节点安装 / 升级(Node Install) > 用途:在防护节点主机上一键安装 / 升级 skynet-node。链路分两侧:**管理面**(平台登录态 + RBAC)注册安装包、生成一次性命令、查询任务、撤销 token;**安装机侧**(仅认安装 token)拉脚本、下载包/env、回报结果。 > 后端实现:`src/backend/internal/node/{handler,service,repo}/install.go`、路由 `src/backend/internal/node/route.go`、回收任务 `src/backend/internal/app/install_reaper.go`。 ### 8.0 鉴权与状态码 | 维度 | 管理面接口 | 安装机侧接口 | |---|---|---| | 路径 | `/install/artifacts` `/commands` `/upgrades` `/jobs` `/tokens/:id/revoke` | `/install/script` `/package` `/env` `/report` | | 鉴权 | 平台统一登录态(Bearer Session / API Key)+ RBAC | **仅** `Authorization: Bearer ` | | RBAC action(`perms.NodeNode`) | `artifact` / `install` / `upgrade` / `job` / `revoke` | 无(token 自鉴权) | | 鉴权失败 | 401 / 403(平台统一信封) | **401**,并带 `WWW-Authenticate: Bearer realm="node-install"`(challenge) | 要点: - 安装 token 永远只在 `Authorization` 头里传递,**绝不进 URL query**。 - 安装机侧四个接口鉴权失败统一返回 `401` + challenge 头;`/env` 在 token 有效但 env 载荷已不可用时返回 `403`;`/report` 的 `job_id` 与 token 绑定 job 不一致返回 `403`。 - cloud 自有表口径固定 `node_install_*`(`node_install_artifacts` / `node_install_tokens` / `node_install_jobs` / `node_install_reports`),仅落 `guard_local`,不写老库。 ### 8.1 `POST /api/node/install/artifacts` — 注册本地安装包并预检 注册后端主机上已存在的安装包,扫描并计算 sha256、跑预检。 ```bash curl -sS -X POST https:///api/node/install/artifacts \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"name":"skynet-node-1.0.0.tar.gz","version":"1.0.0","package_path":"/data/artifacts/skynet-node-1.0.0.tar.gz"}' ``` 约束: - `package_path` 必须是后端主机**绝对路径**,后缀 `.tar.gz` / `.tgz`,且必须位于**允许的 artifact 根目录**之内。 - 默认允许根目录为 `/data/artifacts,/opt/cloud/artifacts`(可用环境变量 `NODE_INSTALL_ARTIFACT_ROOTS` 覆盖,逗号分隔)。**默认不含 `/tmp`** —— `/tmp` 全局可写,允许它会让任意本地进程投放 tarball 走注册流程;测试需要临时目录时必须显式设置 `NODE_INSTALL_ARTIFACT_ROOTS`。 - 预检阻断项(`severity=error`)未过 → `status=failed`;`warning` / `info` 不阻断 → `status=ready`。阻断项含:`VERSIONS` 清单、`env.conf` 存在且无生产标记、必需 env key、必需 topic 提示、spoa `sig-*` 规则完整。`binary_version_drift`、`pulsar_config_risk` 为 `warning`,不阻断。 响应(节选,`package_path` 仅回**文件名**,不暴露后端绝对路径): ```json {"code":0,"data":{ "artifact_id":"8f1c…","name":"skynet-node-1.0.0.tar.gz","version":"1.0.0", "sha256":"…64hex…","status":"ready", "precheck":{"passed":true,"checks":[ {"key":"required_env","severity":"error","passed":true,"message":"required env keys found"}, {"key":"binary_version_drift","severity":"warning","passed":true,"message":"…"} ]} }} ``` `GET /api/node/install/artifacts` 列出已注册包;列表里的 `package_path` 同样只展示文件名,前端/示例**不要展示后端真实绝对路径**。 ### 8.2 `POST /api/node/install/commands` · `POST /api/node/install/upgrades` · `POST /api/node/install/uninstalls` — 生成一次性命令 ```bash curl -sS -X POST https:///api/node/install/commands \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{ "artifact_id":"8f1c…", "node_id":"可选-目标节点UUID", "server_url":"https://", "ttl_seconds":3600, "max_uses":20, "env":{ "ZCLOUD_NGX_ACCESS_TOPIC":"cloud/ngx", "ZCLOUD_CC_TOPIC":"cc/sync", "ZCLOUD_SYNC_TOPIC":"zcloud-sync", "ZCLOUD_DELTA_TOPIC":"zcloud-delta", "ZCLOUD_BLOCK_TOPIC":"zcloud-block" } }' ``` 升级命令用 `/upgrades`,等价于 `action=upgrade`,请求体相同。 卸载命令用 `/uninstalls`,等价于 `action=uninstall`,请求体相同(同 install/upgrade 的 `artifact_id` 必填、`server_url` 必填、`env`/`ttl_seconds`/`max_uses` 可选)。与升级一样**作用于已有节点,`node_id` 必填**(缺省 → 400「卸载必须指定目标节点」)。响应同样是一次性 `command`(`curl … | sudo bash` 一行,明文 token 只出现一次)。权限:`node.node.uninstall`。 > ⚠️ **破坏性、不可恢复**:引导脚本据 `GET /api/node/install/package` 返回的服务端权威响应头 `X-Install-Action`(值取自 token 绑定 job 的动作,此处为 `uninstall`)决定跑包内 **`uninstall.sh`** 而非 `install.sh`,并用 here-string 自动应答其交互式 `[y/N]` 确认。`uninstall.sh` 会**停止并移除该节点上的全部防护服务(nginx / agent / waf-spoa 等)及其数据目录**,操作不可恢复。 > > **范围**:卸载只移除节点**主机上的服务**,**不删除云端节点列表里的节点记录**。如需同时清掉节点记录,运维另行调用 `DELETE /api/node/nodes/:id`。 响应: ```json {"code":0,"data":{ "job_id":"…","token_id":"…","token_prefix":"nit_xxxxxxx", "expires_at":1735900000000, "command":"curl -fsSL --connect-timeout 10 --max-time 60 -H 'Authorization: Bearer nit_…' 'https:///api/node/install/script' | sudo bash -s -- --token 'nit_…' --server 'https://'" }} ``` 约束 / 行为: - **`server_url` 必须匹配 cloud 配置的对外 base URL**(scheme + host[:port])。否则 400 —— 防止特权调用方把攻击者控制的主机塞进安装命令(安装机会用 token 信任该主机的 package/env/report)。本地测试例外:配置允许 localhost 时,loopback `server_url` 放行。 - `server_url` 必须是绝对 URL,**localhost 之外强制 https**;query / fragment 会被丢弃。 - `env` 是**注册时设定的节点变量**(env.conf 文本);留空则默认取包内 `env.conf`。值做 shell 单引号转义后下发。 - **env key 采用 deny-list(非 allow-list)**:包是变量无关的,变量由上层在注册时给,故接受任意业务键(如 `REG_URL` / `AGENT_PORT` / `PULSAR_ADDR` 等);仅当命中敏感词(`PASSWORD/PASSWD/SECRET/PRIVATE/CRED/APIKEY/API_KEY/AUTH_TOKEN/ACCESS_TOKEN/ACCESS_KEY/SECRET_KEY/SIGNING_KEY/BOOTSTRAP_TOKEN`,大小写不敏感)或疑似安装 token / Bearer 凭证时 → 400 且**不创建** token / job。env 文本持久化到 `node_install_jobs.env_payload`,以便后端重启 / 多副本时 `/install/env` 仍可重放。 - **`AGENT_PORT`(可选注册变量)**:设定后本次安装的 agent 以该端口监听 + 注册(默认 `33020`),用于与同机已有 agent 错开端口。端口由 cloud 在 sha256 校验后注入解压出的安装脚本,包文件不被改动。 - `ttl_seconds`:默认 `3600`,上限 `86400`(24h)。 - `max_uses`:**最低 5**(低于 5 自动抬到 5)、默认 `20`、上限 `100`。建议保留重试余量(脚本下载 package + env 至少各消耗 1 次,失败重试还会再消耗),**不要贴着最低值设**。 - `artifact.status != ready` → 400,不创建 token / job。 - **明文 token 只在 `command` 里出现一次**;库中 token 仅存 `sha256` 哈希 + 12 位 `prefix`。`node_install_jobs.command` 只存脱敏模板(``),不含明文 token 或 Bearer 头。 - `job.spec` 只保留白名单字段(`server_url` / `env_sha256` / `env_keys` / `artifact` / `token_prefix`),`env_payload` 在所有 job 响应里都被剥离。 ### 8.3 安装机侧:script / package / env / report `command` 拉取并执行的脚本(`GET /install/script`)会:`set -euo pipefail` + 退出清理临时目录 → 带 `--connect-timeout/--max-time/--retry` 下载 package、env → 校验 `X-Artifact-SHA256` 与本地 `sha256sum` 一致(不一致上报 `failed` 并退出)→ 用云端 env 覆盖包内 `env.conf` → **若注册变量含 `AGENT_PORT` 则注入解压出的 `install*.sh`**(把 agent 监听/注册端口从默认 `33020` 改为该值,仅改解压副本,不动已校验包文件)→ 执行包内 `install.sh` → 经 `/report` 回报 `running` / `success` / `failed`。 **token 配额语义(关键)**: | 接口 | 鉴权方式 | 是否消耗 `max_uses`(`use_count`) | 计数 / 副作用 | |---|---|---|---| | `GET /install/script` | 校验 token 有效性 | **否**(`ValidateBearerNoUse`) | 不消耗,便于脚本可被重复拉取 | | `GET /install/package` | 校验并占用 | **是**,`use_count+1` | 响应头 `X-Artifact-SHA256` / `X-Install-Job-ID`;job 置 `running` | | `GET /install/env` | 校验并占用 | **是**,`use_count+1` | 返回 env 文本;env 载荷不可用时 403 | | `POST /install/report` | `ValidateBearerForReport` | **否** | 独立 `report_count+1`;**不**消耗 `max_uses` | - `report` 用独立 `report_count` 计数,安装机多次回报(running→success/failed)不会耗尽 `max_uses`。 - **`failed` / `running` 回报不关闭 token**;只有 `success` 才把 token 置 `used`(终态,后续不可用)。 - `failed` 回报**允许在已 `revoked` 的 token 上提交**,以便被运维撤销的安装运行仍能把 job 收敛到 `failed`。 - `report` 若带 `job_id`,必须与 token 绑定 job 一致,否则 403。job 已终态(`success`/`failed`)时滞后回报不覆盖终态。 安装机侧手动联调: ```bash TOKEN=nit_xxx; BASE=https:// curl -fsSL -H "Authorization: Bearer $TOKEN" "$BASE/api/node/install/script" curl -fsSL -D - -o pkg.tar.gz -H "Authorization: Bearer $TOKEN" "$BASE/api/node/install/package" curl -fsSL -H "Authorization: Bearer $TOKEN" "$BASE/api/node/install/env" curl -fsS -X POST "$BASE/api/node/install/report" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"status":"success","message":"done","hostname":"node-1","node_version":"1.0.0"}' ``` ### 8.4 `GET /api/node/install/jobs` · `/jobs/:id` — 查询任务 ```bash curl -sS -H "Authorization: Bearer $TOKEN" https:///api/node/install/jobs # 最近 50 条 curl -sS -H "Authorization: Bearer $TOKEN" https:///api/node/install/jobs/ # 含 reports[] ``` `status`:`pending` → `running` → `success` / `failed`。`started_at` / `finished_at` 为毫秒时间戳,`0` 表示未发生。详情接口附最近 `reports`(最多 20 条)与 `recent_report`。 ### 8.5 `POST /api/node/install/tokens/:id/revoke` — 撤销 token ```bash curl -sS -X POST -H "Authorization: Bearer $TOKEN" \ https:///api/node/install/tokens//revoke ``` - 仅能撤销仍 `active` 的 token;已 `used` / `revoked` 返回 400。 - 撤销后安装机侧 script/package/env 立即 401;`failed` 回报仍可提交以收敛 job。 - token 不落单独的 `expired` 状态,过期统一由 `expires_at` 派生;`status=active` 仅表示未撤销/未使用,不等于"仍可用"。 ### 8.6 Stale job 回收(reaper) 后端单进程后台扫帚 `install_reaper.go`:启动时立即跑一次,之后每 `10m` 一次。把 `ctime` 早于 `now - 24h`(`StaleJobMaxAge`)且仍处于 `pending`/`running` 的 job 原子置为 `failed`(`finished_at=now`、`message="install job timed out without report"`),**并在同一事务内 `revoke` 指向这些 job 的仍 `active` 的 token** —— 防止被强杀的安装机事后再用 bearer token 复活已关闭的 job。终态 job 不会被改写;多副本下事务 WHERE 子句保证幂等(仅首个进程命中,其余 `RowsAffected=0` 静默退出)。 ### 8.7 前端展示约定 - 生成命令的明文 token **只展示一次**;UI 须提示"仅显示一次、请立即复制保存"。 - 列表/详情只显示 `token_prefix`(如 `nit_xxxxxxx`)与状态,绝不回显明文 token。 - 复制命令后**清空剪贴板**(设定超时后清除),避免 token 长期驻留剪贴板。 - artifact 列表不展示后端真实 `package_path` 绝对路径(后端已只回文件名)。 ### 8.8 `POST /api/node/reg` — 节点 agent 自注册 节点 agent 自注册端点。**公共端点**:不挂 RBAC、不需要 Bearer 鉴权,与 `POST /api/node/install/report` 同类(属 agent 基础设施,无 `zcloud` CLI 命令)。唯一访问门槛是请求体里的共享口令 `dummy_token`——必须等于 agent 端硬编码的固定常量值,不匹配即拒绝。 > 后端实现:`src/backend/internal/node/{handler,service}/reg.go`、路由 `src/backend/internal/node/route.go`(`rg.POST("/reg", h.RegNode)`)。 **请求体字段**(注意 `manger_addr` 是 agent 既有拼写,缺 `a`,不能改成 `manager_addr`): | 字段 | 类型 | 必填 | 说明 | |---|---|:---:|---| | `node_id` | string | 否 | 首次安装为空;非空表示 agent 已持有节点 ID(命中既有节点则复用,不新建) | | `manger_addr` | string | **是** | 管理地址 `host:port`,agent 用 `ip route get` 自动探测后上报。端口缺失时回落默认端口 `33020` | | `node_type` | string | 否 | proto 枚举名字符串,如 `"NODE_1_WAF"`。**仅支持 WAF 防护节点**(空 / `"NODE_1_WAF"` / `"waf"` / `"1"` 均映射为 WAF,其它值返回非零 `code`) | | `extend_config` | string | 否 | url-escape 后的扩展配置,自注册暂不消费 | | `dummy_token` | string | **是** | 共享口令,必须等于 agent 硬编码的固定常量值,否则拒绝 | | `plugin` | string | 否 | 插件列表,如 `"waf,detect,agent,ebpf"` | | `only_acl` | bool | 否 | 仅 ACL 模式标记,自注册节点不走该路径 | | `ip` | string | 否 | `only_acl` 关联参数,标准注册为空 | | `acl_tags` | string | 否 | `only_acl` 关联参数 | | `ip_groups` | string | 否 | `only_acl` 关联参数 | ```bash curl -sS -X POST https:///api/node/reg \ -H 'Content-Type: application/json' \ -d '{ "node_id":"", "manger_addr":"192.168.14.171:33020", "node_type":"NODE_1_WAF", "dummy_token":"", "plugin":"waf,detect,agent,ebpf" }' ``` **响应**:标准信封 `{code, message, data}`,`data` 为 `RegNodeResponse`: ```json {"code":0,"message":"success","data":{ "node_id":"3f2c…", "listen_addr":":33020", "tls":false, "cert":"", "key":"", "settings":{}, "plugin":{} }} ``` | 响应字段 | 类型 | 说明 | |---|---|---| | `node_id` | string | 节点 ID。幂等命中既有节点时返回既有 ID;首次注册返回新建 ID | | `listen_addr` | string | 监听地址,形如 `":33020"`,取自管理地址端口(缺失回落默认 `33020`) | | `tls` | bool | 新平台(NSQ)固定 `false`(不再用 etcd 下发每节点证书) | | `cert` | string | 新平台返回空串 | | `key` | string | 新平台返回空串 | | `settings` | object | 下发配置。新平台配置走 NSQ 发布订阅,固定返回空映射 `{}` | | `plugin` | object | 插件配置。新平台固定返回空映射 `{}` | **语义要点**: - **幂等(按管理地址 upsert)**:同一 `manger_addr` 重复注册返回**相同** `node_id`,绝不新建重复节点;新老 agent 共存时**保留既有节点**,不覆盖其字段。 - **首次注册**:创建一条节点记录(`machine_room` 默认值为 `default`,运维可在节点列表里改归到真实机房),并以管理 IP 派生落一条 `ip_addr` 记录——于是节点**自动出现在节点列表**,**管理地址与业务 IP 都已自动填好**。落 `ip_addr` 为尽力而为:IP 已被占用等非致命错误不影响注册成功。 - **失败处理**:始终返回 HTTP `200`,业务错误通过响应体**非零 `code`** 表达。`dummy_token` 不匹配、`manger_addr` 缺失或非法、`node_type` 非 WAF 类型均返回非零 `code` 并附带错误消息。 --- ## §A Analytics 通用查询参数 适用于所有单图 GET 接口(`GET /api/analytics//`)。未传参数时后端使用默认值;传入无权限的 `target_user_id` 或跨 OEM 资源时返回 403。 | 参数 | 类型 | 必填 | 取值/示例 | 说明 | |------|------|:---:|----------|------| | `window` | string | 否 | `last_1h` / `last_24h` / `last_7d` | 时间窗口别名;为空时后端默认 `last_24h` | | `stime` | int64 | 否 | `1746748800000` | 自定义起始时间 Unix 毫秒(与 `etime` 配对,比 `window` 优先级高) | | `etime` | int64 | 否 | `1746835200000` | 自定义结束时间 Unix 毫秒 | | `site_id` | string | 否 | `site-001` | 站点过滤 | | `domain_id` | string | 否 | `d_8a3b1c` | 域名过滤 | | `target_user_id` | string | 否 | `u-tenant-001` | 客户级切换被查看用户;后端统一做越权校验 | | `compare` | bool | 否 | `false` | 是否启用上一周期对比(仅部分 chart 支持) | | `top` | int | 否 | `10` | TopN,默认 10,最大 100 | | `order` | string | 否 | `bytes_desc` | 排序方式,具体含义由图表定义(如 `top-url` 支持 `request_count_desc`/`bytes_desc`/`cache_desc`) | | `page` | int | 否 | `1` | 列表类图表分页 | | `size` | int | 否 | `20` | 列表类分页每页条数,最大 100 | **单图响应骨架**: ```json { "code": 0, "message": "ok", "data": { "chart_key": "access/status", "render_hint": "categorical_distribution_over_time", "schema": { "dimensions": [ { "name": "status_class", "type": "enum", "values": ["2xx", "3xx", "4xx", "5xx"] }, { "name": "time", "type": "timestamp", "unit": "ms" } ], "measures": [ { "name": "count", "type": "integer", "unit": "requests" } ] }, "rows": [], "meta": { "cache": "miss", "source": "postgres", "latency_ms": 12, "window": { "stime": 1777526400000, "etime": 1777530000000, "granularity": "5m", "bucket_table": "tfs_flow_domains" } } } } ``` > `window.granularity` 是**响应字段**,描述实际命中的聚合粒度(5m / 1h / 1d),**不是用户输入参数**。客户传 `window=last_24h`,后端按窗口大小自动选表。 --- ## §B 可视化建议总表 ### B.1 Chart 统一契约 — `render_hint` 速查 所有 chart-key 由前端按 `render_hint` 自动分发到 6 个 chart 组件之一。这是契约真值([`docs/specs/chart-contract.md`](https://gitea.com/y2026/cloud/src/branch/main/docs/specs/chart-contract.md) §2),不可自创新词。 | `render_hint` | schema 形状 | 推荐前端组件 | 典型场景 | |---|---|---|---| | `kpi` | 0~1 dim + 1+ measure | `KpiGroupCard` | 多指标数字卡组(如 overview/kpi 的 6 测度) | | `categorical_distribution` | 1 categorical dim + 1 measure | `PieCard`(≤8 类)/ `BarCard`(>8 类) | 一维占比(如 overview/event-type) | | `categorical_distribution_over_time` | 1 categorical + 1 timestamp + 1 measure | `StackedBarCard` / `LineCard` 多 series | 多分类按时间堆叠(如 access/status) | | `time_series_single` | 1 timestamp + 1 measure | `LineCard` | 单测度时序(如 access/request-hm 无 compare) | | `time_series_multi` | 1 timestamp + ≥2 measures,**或** 1 categorical + 1 timestamp + 1 measure | `LineCard` 多线 | 多测度时序(如 access/flow-hm 5 测度);compare 走子形态 B | | `topn` | 1 string dim + 1 measure | `BarCard` 横向 / `RankingCard` | 已排序 TOP-N(如 protect/waf/top-ip) | | `geo` | 1 geo dim + 1 measure | `GeoHeatmapCard` | 地理分布(如 protect/waf/geo) | | `table` | 任意 | `TableCard` | 不适合可视化的兜底 | > **新增 hint 词汇必须双方评审通过**(cloud + Aegeon),不可单边扩词汇表。 ### B.2 数据形态速查 下表把"输出数据形态 → 推荐图表"的映射汇总在一起,对接前端时可作为速查表。实际响应仍以 Chart 统一契约的 `schema` + `rows` 为准。 | 数据形态 | 典型字段示例 | 推荐图表 | 不推荐 | |---|---|---|---| | 单值(标量) | `{count: 12345}` | KPI 数字卡 | 折线 / 饼图 | | 多 KPI(4-6 个标量) | `{domain_count, requests, blocked, block_rate, qps}` | 多 KPI 卡阵 / 雷达图 | 单饼图 | | 时序单系列 | `[{ctime, value}, ...]` | 折线图 / 面积图 | 饼图 | | 时序多系列 | `[{ctime, requests, attacks}, ...]` | 多线折线 / 堆叠面积 | 饼图 | | 时序对比(compare) | `{current:[...], previous:[...]}` | 双线对比折线(实虚线) | 单折线 | | 维度分布(少类 ≤ 8) | `[{key, count}, ...]` | 饼图 / 环形图 | 表格 | | 维度分布(多类 > 8) | `[{key, count}, ...]` | 横向 bar / 柱图 | 饼图(碎片化) | | 地理分布 | `[{region, count}, ...]` | 中国/世界地图热力 | 表格 | | 排行 TOP | `[{key, count, ...}, ...]` | 横向 bar / 表格(含明细列) | 折线 / 饼图 | | 二维矩阵 | `[[v11, v12], [v21, v22]]` | 热力图 | 折线 | | 散点 | `[{x, y, size, ...}, ...]` | 散点图 / 气泡图 | 饼图 | | 列表分页 | `{list, total, page, size}` | 表格(分页) | 任何图表 | | 时间线明细 | `{items: [{ctime, ...}]}` | 时间线(vertical timeline) | 饼图 | | 占位 | `{available: false, ...}` | 不渲染图表,渲染 `n-empty` 占位卡片 | 假数据 | **配色建议**: - 防护类(攻击/拦截):红色系(#ff4d4f / #ff7875) - 流量类(请求/带宽):蓝色系(#2563eb / #60a5fa) - 缓存/AI 类(增益):绿色系(#10b981 / #34d399) - 中性指标:灰/紫色系(#6b7280 / #8b5cf6) --- ## §C 相关文档 - [认证说明](/docs/auth) — Bearer 会话、API Key 与 401 重试 - [示例代码](/docs/examples) — 三语言调用示例(curl / Python / Go) - [权限矩阵](/docs/permissions) — 接口对应的权限 key 与 OEM 隔离规则 - [CLI 工具](/docs/cli) — 等价的 CLI 操作 - [错误码](/docs/errors) — 完整业务错误码表 - [快速上手](/docs/quickstart) — 5 分钟跑通第一次调用 - [/api/openapi.json](/api/openapi.json) — OpenAPI v3 完整 schema(机器读取) - [/llms.txt](/llms.txt) / [/llms-full.txt](/llms-full.txt) — AI agent 速读入口 --- ## 完整 API 索引 REST 全集导出为 OpenAPI v3: ``` GET /api/openapi.json ``` 可直接导入 Postman / Insomnia / Swagger UI。所有路由含完整 schema、参数、响应示例与所需权限 key。 --- *Cloud WAF · REST API Documentation · 完整索引以 `/api/openapi.json` 为准* --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 5 大模块:guard / sys / analytics / cli_release / auth > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # 认证说明 ## 概述 Cloud WAF 当前支持两种认证方式。所有受保护接口都要求请求头携带 `Authorization`,同一请求只能选择其中一种通道: | 场景 | 请求头 | 说明 | |------|--------|------| | 人工登录 / Web 控制台 / CLI 交互登录 | `Authorization: Bearer ` | token 由 `POST /api/auth/login` 颁发,通常有效期为数小时(受系统安全策略约束) | | 脚本 / CI / 第三方系统集成 | `Authorization: ApiKey zck_.` | API Key 是机器调用凭证,明文仅签发时返回一次,适合长期自动化对接 | > **推荐**:人工操作使用 `Bearer` 会话;机器对接优先使用 API Key。历史的“账号密码换短期 token + 401 自动重登”仍可用,但只建议作为兼容方案。 ## API Key 机器调用凭证 API Key 是发给脚本、CI、第三方系统使用的机器调用凭证。它代表某个用户在某个 OEM 下发起请求,并受到两层限制: 1. 签发用户当前拥有的 RBAC 权限; 2. API Key 自身的 `scopes` 列表。 最终有效权限为:`effective_perms = user.RBAC ∩ key.scope`。`scope` 只能收窄,不能放大;如果 `scopes` 为空数组,则完整继承签发用户当前 RBAC。 ### 签发 API Key ```bash curl -sS -X POST https://waf.example.com/api/sys/api-keys \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "name": "生产对接", "scopes": ["guard.domain.list", "guard.domain.view"], "expires_in_days": 90 }' ``` 响应中的 `data.api_key` 是完整明文: ```json { "code": 0, "message": "ok", "data": { "key_id": "8f21c0c5-55ae-4cbd-a60a-8e64a6e2b1d0", "api_key": "zck_abc12345.A1b2C3d4E5f6G7h8I9j0K1L2M3n4O5p6Q7r8S9t0", "prefix": "zck_abc12345", "last4": "5t0", "expires_at": 1732982400000 } } ``` > ⚠ `api_key` 明文仅此一次返回。后续列表接口只返回 `prefix` 和 `last4`,无法找回完整 secret;丢失后请吊销并重新签发。 ### 使用 API Key 调接口 ```bash curl -sS https://waf.example.com/api/guard/domains \ -H "Authorization: ApiKey zck_abc12345.A1b2C3d4..." \ -H 'Accept-Language: zh-CN' ``` ### API Key 与 Bearer token 的区别 | 维度 | Bearer token | API Key | |------|--------------|---------| | 主要用途 | 人登录后的短期会话 | 机器、脚本、CI、第三方系统长期调用 | | 获取方式 | `POST /api/auth/login` | `POST /api/sys/api-keys` | | 请求头 | `Authorization: Bearer ` | `Authorization: ApiKey zck_.` | | 生命周期 | 通常数小时,受自动登出策略影响 | 默认 90 天,最大 365 天,可主动吊销 | | 权限边界 | 当前用户 RBAC | 当前用户 RBAC ∩ Key scopes | | 明文保存 | 客户端保存 token | 只在签发响应返回一次,服务端不保存明文 | ### Scope 匹配规则(重要) API Key 的 `scopes` 校验语义与用户角色 RBAC **刻意分离**,请务必先理解后再签发: - **精确字符串相等**(大小写敏感):`scope` 列表中的元素必须与接口要求的权限 key 字面量完全一致才算命中。 - **不展开通配符**:写 `guard.*` 或 `guard.domain.*` 进 `scopes` **不会命中任何接口**——服务端不做前缀 / glob / 正则展开。例如接口要求 `guard.domain.list`,scope 里只有 `guard.domain.*` 时仍然返回 `code=1053`。 - **与用户 RBAC 独立判定**:用户角色权限可以是 `guard.domain.*` 简化分配,但 API Key scope 必须列举到叶子 perm key。两者按 `effective_perms = user.RBAC ∩ key.scope` 串行判断,user 通配符匹配的接口若不在 scope 列表里仍然被拦截。 - **签发时如需 guard 全模块权限**:请逐条列举具体 perm key(例如 `guard.domain.list`、`guard.domain.view`、`guard.domain.create` …),可通过 `GET /api/sys/permissions/tree` 获取叶子权限清单。 - **失败错码**:scope 不命中本次接口返回 `code=1053`(HTTP 403);与 1056 (`apikey forbidden`,越 OEM 边界) 是不同语义,排查时可按错码 1:1 区分。 ### 安全建议 - 给 CI / 第三方系统创建专用低权限用户,再由该用户签发 API Key。 - `scopes` 只授予必需权限,例如只读域名列表时只给 `guard.domain.list` / `guard.domain.view`。 - 不要把完整 API Key 写进 git、日志或工单;只展示 `prefix` / `last4`。 - 定期查询 `GET /api/sys/api-keys`,审计 `last_used_at` / `last_used_ip`。 - 发现泄露或不再使用时,立即调用 `DELETE /api/sys/api-keys/:id` 吊销。 ## 登录链路图(文字版) ``` ┌─────────────┐ ┌────────────────┐ │ 客户端 / CLI │ ──── POST /api/auth/login ───▶ │ Cloud WAF 后端 │ └─────────────┘ {username, password} └────────────────┘ │ │ │ ◀────── 200 OK {data: {token, user}} ───────────│ │ │ │ ──── GET /api/sys/users (Bearer token) ────────▶│ │ ◀────── 200 OK {code:0, data:[...]} ────────────│ │ │ │ ──── (token 过期) GET /api/... ────────────────▶│ │ ◀────── 401 Unauthorized ───────────────────────│ │ │ │ ──── POST /api/auth/login (重新登录) ──────────▶│ │ ◀────── 200 OK {data: {token: <新>}} ──────────│ ``` ## 完整 curl 示例 ### 1) 登录 ```bash curl -sS -X POST https://waf.example.com/api/auth/login \ -H 'Content-Type: application/json' \ -d '{ "username": "admin", "password": "YOUR_PASSWORD" }' ``` 返回: ```json { "code": 0, "message": "ok", "data": { "token": "eyJhbGciOi...", "user": { "uuid": "abc-123", "username": "admin", "role_id": 1 } } } ``` ### 2) 携带 token 调用接口 ```bash TOKEN='eyJhbGciOi...' curl -sS https://waf.example.com/api/sys/users \ -H "Authorization: Bearer $TOKEN" \ -H 'Accept-Language: zh-CN' ``` ### 3) 注销 ```bash curl -sS -X POST https://waf.example.com/api/auth/logout \ -H "Authorization: Bearer $TOKEN" ``` 注销后该 token 立刻失效,后续使用返回 401。 ## CLI 多 profile 机制 CLI 把凭据写到 `~/.zcloud/credentials.toml`,结构如下: ```toml active = "default" [profiles.default] api_url = "https://waf.example.com" username = "admin" token = "eyJhbGciOi..." oem_id = "default" [profiles.prod] api_url = "https://prod.example.com" username = "ops" token = "eyJhbGciOi..." ``` 切换 profile: ```bash zcloud config profiles list zcloud config profiles activate prod # 注意:profiles create 只接受位置参数 ,不支持任何 flag zcloud config profiles create staging zcloud --profile staging config set api_url https://staging.example.com ``` 每次 `zcloud auth login` 会更新当前 profile 的 token;不同 profile 互不干扰。 ## 401 自动重试模式 token 过期是常态,正确做法是把"401 → 重登 → 重试"封装成 wrapper。 ### Python 伪代码 ```python import requests class CloudWAF: def __init__(self, base_url, username, password): self.base = base_url.rstrip('/') self.username = username self.password = password self.token = None self._login() def _login(self): r = requests.post(f"{self.base}/api/auth/login", json={ "username": self.username, "password": self.password, }) r.raise_for_status() self.token = r.json()["data"]["token"] def call(self, method, path, **kwargs): headers = kwargs.pop("headers", {}) headers["Authorization"] = f"Bearer {self.token}" url = f"{self.base}{path}" r = requests.request(method, url, headers=headers, **kwargs) if r.status_code == 401: self._login() # 重登 headers["Authorization"] = f"Bearer {self.token}" r = requests.request(method, url, headers=headers, **kwargs) r.raise_for_status() return r.json() ``` ### Go 伪代码 ```go type Client struct { BaseURL string User string Password string Token string HTTP *http.Client } func (c *Client) login() error { body, _ := json.Marshal(map[string]string{"username": c.User, "password": c.Password}) resp, err := c.HTTP.Post(c.BaseURL+"/api/auth/login", "application/json", bytes.NewReader(body)) if err != nil { return err } defer resp.Body.Close() var out struct{ Data struct{ Token string } } json.NewDecoder(resp.Body).Decode(&out) c.Token = out.Data.Token return nil } func (c *Client) Do(req *http.Request) (*http.Response, error) { req.Header.Set("Authorization", "Bearer "+c.Token) resp, err := c.HTTP.Do(req) if err != nil { return nil, err } if resp.StatusCode == 401 { resp.Body.Close() if err := c.login(); err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+c.Token) return c.HTTP.Do(req) } return resp, nil } ``` ## 机器对接模式(兼容方案) 机器对接推荐使用上文 API Key。只有在暂时无法签发 API Key、或需要兼容旧流水线时,才使用“账号密码换短期 token + 401 自动重登”模式。 `zcloud auth login` 是交互式命令(用 `util.ReadPassword` 读不回显的密码),不支持 `--username/--password` 之类的 flag。CI Pipeline / 第三方系统应直接调用登录 REST API 拿到 token,然后通过 `ZCLOUD_TOKEN` 环境变量注入 CLI。 推荐做法: 1. **优先评估 API Key**:如果只是调用 REST API,直接签发 API Key;如果必须复用旧 CLI 流程,再继续下面步骤 2. **创建专用账号**:在 Web 控制台为 CI 创建 `ci-bot` 用户,分配最小必要权限 3. **凭据保存到 secrets**:`CLOUDWAF_USERNAME` / `CLOUDWAF_PASSWORD` 存到 GitHub Actions / GitLab CI secrets 4. **任务开始时调 REST 拿 token**:`POST /api/auth/login`,密码用 base64 编码后传 5. **后续 CLI 命令读取 `ZCLOUD_TOKEN` 环境变量**,无需写入本地 credentials 文件 6. **永远启用 401 重试 wrapper**:长任务中 token 可能过期,wrapper 拿到 401 时重新登录刷新 token ```yaml # Gitea Actions 示例 jobs: publish-cert: steps: - run: | curl -fsSL ${{ vars.WAF_URL }}/api/cli/install.sh | sh zcloud config set api_url ${{ vars.WAF_URL }} # 1) 通过 REST 接口换 token(密码 base64 编码) PWD_B64=$(printf '%s' "${{ secrets.WAF_PASS }}" | base64 -w0) TOKEN=$(curl -fsS "${{ vars.WAF_URL }}/api/auth/login" \ -H 'Content-Type: application/json' \ -d "{\"username\":\"${{ secrets.WAF_USER }}\",\"password\":\"$PWD_B64\"}" \ | jq -r '.data.token') # 2) 注入环境变量后跑业务命令;CLI 自动读取 ZCLOUD_TOKEN export ZCLOUD_TOKEN="$TOKEN" zcloud guard certs upload --name star --cert ./fullchain.pem --key ./private.key # 3) 任务结束撤销 token zcloud auth logout ``` ## 安全建议 - ✅ 始终走 HTTPS;HTTP 仅限本地开发 - ✅ token 不要写进 git,不要打日志 - ✅ API Key 明文仅保存到密钥管理系统,不要写入配置仓库或普通日志 - ✅ CI 用专用账号 + 最小权限,不要复用管理员 - ✅ 定期审计 `GET /api/sys/sessions` 在线会话,发现异常立即 `kill` - ✅ 定期审计 `GET /api/sys/api-keys`,发现异常立即吊销 - ❌ 禁止把 token 嵌入前端代码(前端拿 token 应通过登录接口) - ❌ 禁止把 API Key 嵌入前端代码或公开客户端 - ❌ 禁止跨账号共用 profile(多人共用 = 审计无法定责) ## 常见问题 | 现象 | 处理 | |------|------| | 登录返回 `code` 非 0 | 检查 `message`,常见原因:账号锁定、密码错、密码到期 | | 调接口反复 401 | 检查 token 是否粘贴完整、Authorization 头格式是否正确 | | API Key 调接口返回 401 | 检查请求头是否为 `Authorization: ApiKey zck_.`,确认 key 未过期或吊销 | | API Key 调接口返回 `code=1053` (HTTP 403) | `scopes` 未覆盖接口所需权限。注意 scope 是精确字符串相等,写 `guard.*` 不会命中任何接口,需逐条列举叶子 perm key | | API Key 调接口返回 `code=1056` (HTTP 403) | 跨 OEM 越权:请求 hostname 与签发 API Key 时绑定的 OEM 不一致,请用同 OEM hostname 重试 | | 登录返回 200 但 `data.token` 为空 | 服务端配置异常,联系运维 | | 多个 CLI 进程互相挤掉对方 token | 用不同 profile 隔离 | ## 相关文档 - [API 文档](/docs/api) — `/api/auth/*` 与 `/api/sys/api-keys` 完整 schema - [示例代码](/docs/examples) — 三语言 401 重试实现 - [权限矩阵](/docs/permissions) — 给 CI 账号最小权限的依据 --- *Cloud WAF · 支持 Bearer 会话与 API Key 机器调用凭证双通道认证* --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 5 大模块:guard / sys / analytics / cli_release / auth > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # 示例代码 ## 总览 本页给出 5 个高频场景的完整调用示例,每个场景都用 **curl / Python / Go** 三种语言实现,可直接复制运行。所有示例假设: ``` api_url = https://waf.example.com username = admin password = YOUR_PASSWORD ``` > 替换为你自己的 api_url 和凭据后即可使用。 --- ## 场景 1:查 CLI 版本(公开接口) 无需鉴权,最简单的存活检查 / 联通性测试。 ### curl ```bash curl -sS https://waf.example.com/api/cli/version ``` ### Python ```python import requests resp = requests.get("https://waf.example.com/api/cli/version", timeout=5) resp.raise_for_status() print(resp.json()["data"]["version"]) ``` ### Go ```go package main import ( "encoding/json" "fmt" "net/http" "time" ) func main() { client := &http.Client{Timeout: 5 * time.Second} resp, err := client.Get("https://waf.example.com/api/cli/version") if err != nil { panic(err) } defer resp.Body.Close() var out struct { Data struct{ Version string `json:"version"` } `json:"data"` } json.NewDecoder(resp.Body).Decode(&out) fmt.Println(out.Data.Version) } ``` --- ## 场景 2:登录拿 token ### curl ```bash TOKEN=$(curl -sS -X POST https://waf.example.com/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"YOUR_PASSWORD"}' \ | jq -r '.data.token') echo "token=$TOKEN" ``` ### Python ```python import requests resp = requests.post( "https://waf.example.com/api/auth/login", json={"username": "admin", "password": "YOUR_PASSWORD"}, timeout=5, ) resp.raise_for_status() token = resp.json()["data"]["token"] print("token =", token) ``` ### Go ```go package main import ( "bytes" "encoding/json" "fmt" "net/http" ) func main() { body, _ := json.Marshal(map[string]string{ "username": "admin", "password": "YOUR_PASSWORD", }) resp, err := http.Post( "https://waf.example.com/api/auth/login", "application/json", bytes.NewReader(body), ) if err != nil { panic(err) } defer resp.Body.Close() var out struct { Code int `json:"code"` Data struct{ Token string `json:"token"` } `json:"data"` } json.NewDecoder(resp.Body).Decode(&out) fmt.Println("token =", out.Data.Token) } ``` --- ## 场景 3:列出域名(鉴权) ### curl ```bash curl -sS https://waf.example.com/api/guard/domains \ -H "Authorization: Bearer $TOKEN" \ | jq '.data.list[] | {id, name, origin, status}' ``` ### Python ```python import requests headers = {"Authorization": f"Bearer {token}"} resp = requests.get( "https://waf.example.com/api/guard/domains", headers=headers, timeout=5, ) resp.raise_for_status() for d in resp.json()["data"]["list"]: print(f"{d['id']:>4} {d['name']:<40} {d['origin']}") ``` ### Go ```go req, _ := http.NewRequest("GET", "https://waf.example.com/api/guard/domains", nil) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var out struct { Data struct { List []struct { ID int `json:"id"` Name string `json:"name"` Origin string `json:"origin"` } `json:"list"` } `json:"data"` } json.NewDecoder(resp.Body).Decode(&out) for _, d := range out.Data.List { fmt.Printf("%4d %-40s %s\n", d.ID, d.Name, d.Origin) } ``` --- ## 场景 4:分页查用户列表(鉴权 + 分页) ### curl ```bash curl -sS "https://waf.example.com/api/sys/users?page=1&size=20&keyword=ops" \ -H "Authorization: Bearer $TOKEN" \ | jq '.data | {total, page, size, count: (.list | length)}' ``` ### Python ```python import requests resp = requests.get( "https://waf.example.com/api/sys/users", params={"page": 1, "size": 20, "keyword": "ops"}, headers={"Authorization": f"Bearer {token}"}, timeout=5, ) resp.raise_for_status() data = resp.json()["data"] print(f"total={data['total']} page={data['page']} size={data['size']}") for u in data["list"]: print(f" - {u['username']} ({u['display_name']})") ``` ### Go ```go req, _ := http.NewRequest( "GET", "https://waf.example.com/api/sys/users?page=1&size=20&keyword=ops", nil, ) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() var out struct { Data struct { Total int `json:"total"` Page int `json:"page"` Size int `json:"size"` List []struct { Username string `json:"username"` DisplayName string `json:"display_name"` } `json:"list"` } `json:"data"` } json.NewDecoder(resp.Body).Decode(&out) fmt.Printf("total=%d page=%d size=%d\n", out.Data.Total, out.Data.Page, out.Data.Size) for _, u := range out.Data.List { fmt.Printf(" - %s (%s)\n", u.Username, u.DisplayName) } ``` --- ## 场景 5:401 自动重试 Wrapper(生产推荐) 把"登录 + 401 自动重登 + 重试"封装到客户端,业务层只管调用 API。 ### Python(完整可运行) ```python import requests class CloudWAF: def __init__(self, base_url: str, username: str, password: str, timeout: float = 10): self.base = base_url.rstrip("/") self.username = username self.password = password self.timeout = timeout self.session = requests.Session() self.token = None self._login() def _login(self): r = self.session.post( f"{self.base}/api/auth/login", json={"username": self.username, "password": self.password}, timeout=self.timeout, ) r.raise_for_status() self.token = r.json()["data"]["token"] def call(self, method: str, path: str, **kwargs): headers = kwargs.pop("headers", {}) headers["Authorization"] = f"Bearer {self.token}" url = f"{self.base}{path}" r = self.session.request(method, url, headers=headers, timeout=self.timeout, **kwargs) if r.status_code == 401: self._login() headers["Authorization"] = f"Bearer {self.token}" r = self.session.request(method, url, headers=headers, timeout=self.timeout, **kwargs) r.raise_for_status() return r.json() # 使用示例 client = CloudWAF("https://waf.example.com", "admin", "YOUR_PASSWORD") domains = client.call("GET", "/api/guard/domains")["data"]["list"] print(f"got {len(domains)} domains") # 创建一个域名 new_domain = client.call("POST", "/api/guard/domains", json={ "name": "test.example.com", "origin": "10.0.0.99", }) print("created id =", new_domain["data"]["id"]) ``` ### Go(完整可运行) ```go package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) type Client struct { BaseURL string Username string Password string Token string HTTP *http.Client } func New(baseURL, user, pass string) (*Client, error) { c := &Client{ BaseURL: baseURL, Username: user, Password: pass, HTTP: &http.Client{Timeout: 10 * time.Second}, } return c, c.login() } func (c *Client) login() error { body, _ := json.Marshal(map[string]string{ "username": c.Username, "password": c.Password, }) resp, err := c.HTTP.Post(c.BaseURL+"/api/auth/login", "application/json", bytes.NewReader(body)) if err != nil { return err } defer resp.Body.Close() var out struct { Code int `json:"code"` Data struct{ Token string `json:"token"` } `json:"data"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return err } c.Token = out.Data.Token return nil } func (c *Client) Call(method, path string, body any) ([]byte, error) { var rdr io.Reader if body != nil { b, _ := json.Marshal(body) rdr = bytes.NewReader(b) } do := func() (*http.Response, error) { req, _ := http.NewRequest(method, c.BaseURL+path, rdr) req.Header.Set("Authorization", "Bearer "+c.Token) if body != nil { req.Header.Set("Content-Type", "application/json") } return c.HTTP.Do(req) } resp, err := do() if err != nil { return nil, err } if resp.StatusCode == 401 { resp.Body.Close() if err := c.login(); err != nil { return nil, err } resp, err = do() if err != nil { return nil, err } } defer resp.Body.Close() return io.ReadAll(resp.Body) } func main() { c, err := New("https://waf.example.com", "admin", "YOUR_PASSWORD") if err != nil { panic(err) } raw, err := c.Call("GET", "/api/guard/domains", nil) if err != nil { panic(err) } fmt.Println(string(raw)) } ``` ## 相关文档 - [API 文档](/docs/api) — 完整接口列表 - [认证说明](/docs/auth) — token 生命周期 - [CLI 工具](/docs/cli) — CLI 等价命令 --- *Cloud WAF · 示例覆盖 curl / Python / Go 三语言,可直接复制运行* --- > **本文档属于 Cloud WAF — 企业 Web 防护管理平台** > CLI 工具:`zcloud` · 5 大模块:guard / sys / analytics / cli_release / auth > 完整 API 索引:[/api/openapi.json](/api/openapi.json) · 文档地图:[/sitemap.xml](/sitemap.xml) · AI 速读:[/llms.txt](/llms.txt) --- # 权限矩阵 ## RBAC 模型 Cloud WAF 采用经典 **Resource × Action × Role** 三元 RBAC 模型: - **Resource(资源)**:业务实体的逻辑分组,如 `guard.domain`(域名管理)、`sys.user`(用户管理),用 `Prefix` 唯一标识 - **Action(动作)**:在该资源上的操作,如 `list`(列表)、`create`(新建)、`delete`(删除)等 - **完整权限 Key**:`.`,例如 `guard.domain.list`,是给角色授权的最小单位 - **Role(角色)**:权限 key 的集合;用户绑定角色后获得对应权限 权限矩阵由服务端权限注册表统一维护,并通过 `GET /api/sys/permissions/tree` 对外提供带 i18n 名称的完整权限树。 ## 完整资源 / 动作清单 下表按模块汇总所有可授权的 **Resource × Action** 组合,可直接用作 `PUT /api/sys/roles/{id}/permissions` 的 `keys` 参数(逗号分隔)。 ### sys 模块(系统管理) | 模块 | 资源 (Resource Prefix) | 动作 (Action Key) | 完整权限 Key | |------|----------------------|------------------|-------------| | sys | sys.user | list | sys.user.list | | sys | sys.user | view | sys.user.view | | sys | sys.user | create | sys.user.create | | sys | sys.user | edit | sys.user.edit | | sys | sys.user | delete | sys.user.delete | | sys | sys.user | resetpwd | sys.user.resetpwd | | sys | sys.user | lock | sys.user.lock | | sys | sys.user | assign | sys.user.assign | | sys | sys.role | list | sys.role.list | | sys | sys.role | view | sys.role.view | | sys | sys.role | create | sys.role.create | | sys | sys.role | edit | sys.role.edit | | sys | sys.role | delete | sys.role.delete | | sys | sys.role | perm | sys.role.perm | | sys | sys.oem | view | sys.oem.view | | sys | sys.oem | create | sys.oem.create | | sys | sys.oem | edit | sys.oem.edit | | sys | sys.oem | delete | sys.oem.delete | | sys | sys.security | view | sys.security.view | | sys | sys.security | edit | sys.security.edit | | sys | sys.session | view | sys.session.view | | sys | sys.session | kick | sys.session.kick | | sys | sys.audit | view | sys.audit.view | ### guard 模块(Web 防护) | 模块 | 资源 (Resource Prefix) | 动作 (Action Key) | 完整权限 Key | |------|----------------------|------------------|-------------| | guard | guard.domain | list | guard.domain.list | | guard | guard.domain | view | guard.domain.view | | guard | guard.domain | create | guard.domain.create | | guard | guard.domain | edit | guard.domain.edit | | guard | guard.domain | delete | guard.domain.delete | | guard | guard.domain | settings | guard.domain.settings | | guard | guard.cert | list | guard.cert.list | | guard | guard.cert | view | guard.cert.view | | guard | guard.cert | upload | guard.cert.upload | | guard | guard.cert | edit | guard.cert.edit | | guard | guard.cert | delete | guard.cert.delete | | guard | guard.cert | bind | guard.cert.bind | | guard | guard.policy | list | guard.policy.list | | guard | guard.policy | view | guard.policy.view | | guard | guard.policy | create | guard.policy.create | | guard | guard.policy | edit | guard.policy.edit | | guard | guard.policy | delete | guard.policy.delete | | guard | guard.bwlist | list | guard.bwlist.list | | guard | guard.bwlist | create | guard.bwlist.create | | guard | guard.bwlist | edit | guard.bwlist.edit | | guard | guard.bwlist | delete | guard.bwlist.delete | | guard | guard.bwlist | ip_list | guard.bwlist.ip_list | | guard | guard.bwlist | ip_add | guard.bwlist.ip_add | | guard | guard.bwlist | ip_delete | guard.bwlist.ip_delete | | guard | guard.waf | list | guard.waf.list | | guard | guard.waf | create | guard.waf.create | | guard | guard.waf | edit | guard.waf.edit | | guard | guard.waf | delete | guard.waf.delete | | guard | guard.waf | status | guard.waf.status | | guard | guard.cc | list | guard.cc.list | | guard | guard.cc | create | guard.cc.create | | guard | guard.cc | edit | guard.cc.edit | | guard | guard.cc | delete | guard.cc.delete | | guard | guard.cc | status | guard.cc.status | | guard | guard.acl | list | guard.acl.list | | guard | guard.acl | create | guard.acl.create | | guard | guard.acl | edit | guard.acl.edit | | guard | guard.acl | delete | guard.acl.delete | | guard | guard.acl | status | guard.acl.status | | guard | guard.forward | list | guard.forward.list | | guard | guard.forward | create | guard.forward.create | | guard | guard.forward | edit | guard.forward.edit | | guard | guard.forward | delete | guard.forward.delete | | guard | guard.schedule | list | guard.schedule.list | | guard | guard.schedule | view | guard.schedule.view | | guard | guard.schedule | switch | guard.schedule.switch | | guard | guard.schedule | init | guard.schedule.init | | guard | guard.schedule | reset | guard.schedule.reset | | guard | guard.schedule | records | guard.schedule.records | | guard | guard.schedule | batch | guard.schedule.batch | | guard | guard.schedule | affairs | guard.schedule.affairs | | guard | guard.apply | list | guard.apply.list | | guard | guard.apply | view | guard.apply.view | | guard | guard.apply | create | guard.apply.create | | guard | guard.apply | retry | guard.apply.retry | | guard | guard.apply | quit | guard.apply.quit | ### analytics 模块(统计大屏) | 模块 | 资源 (Resource Prefix) | 动作 (Action Key) | 完整权限 Key | |------|----------------------|------------------|-------------| | analytics | analytics.overview | view | analytics.overview.view | | analytics | analytics.overview | export | analytics.overview.export | | analytics | analytics.access | view | analytics.access.view | | analytics | analytics.protect | view | analytics.protect.view | | analytics | analytics.ai | view | analytics.ai.view | | analytics | analytics.ai | logs | analytics.ai.logs | | analytics | analytics.bot | view | analytics.bot.view | | analytics | analytics.bot | session | analytics.bot.session | | analytics | analytics.alert | view | analytics.alert.view | | analytics | analytics.alert | ack | analytics.alert.ack | ## 动作语义约定 为保持跨模块一致,动作命名遵循以下约定: | Action Key | 语义 | 典型 HTTP 方法 | |-----------|------|--------------| | `list` | 列表查询(含分页) | GET (collection) | | `view` | 详情查询 | GET (one) | | `create` | 新建 | POST | | `edit` | 编辑(修改既有资源) | PUT / PATCH | | `delete` | 删除 | DELETE | | `resetpwd` | 重置密码(sys.user 专属) | PUT | | `lock` | 锁定/解锁(sys.user 专属) | PUT | | `assign` | 分配角色(sys.user 专属) | PUT | | `perm` | 角色权限读写(sys.role 专属) | GET / PUT | | `settings` | 详细配置读写(guard.domain 专属) | GET / PUT | | `upload` | 上传(证书等) | POST | | `bind` | 绑定/解绑关系(证书↔域名) | POST / DELETE | | `ip_list` / `ip_add` / `ip_delete` | bwlist 下 IP 子资源 | GET / POST / DELETE | | `status` | 启停(waf/cc/acl 规则) | PUT | | `retry` / `quit` | 任务重试/取消(guard.apply) | POST / PATCH | | `export` | 报表导出(analytics.overview) | POST | | `logs` | 明细日志(analytics.ai) | GET | | `session` | 会话详情(analytics.bot) | GET | | `ack` | 告警确认(analytics.alert) | PATCH | ## 使用示例:给角色授权 通过 CLI 把若干权限 key 一次性绑定到角色: ```bash # 给"运维员"角色授予域名 + 证书的所有读权限 # 注意:CLI flag 名为 --permissions(必填),不是 --keys zcloud sys roles set-permissions 5 \ --permissions guard.domain.list,guard.domain.view,guard.cert.list,guard.cert.view ``` 通过 API: ```bash curl -sS -X PUT https://waf.example.com/api/sys/roles/5/permissions \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"keys": ["guard.domain.list", "guard.domain.view"]}' ``` ## 拿到带 i18n 的完整权限树 前端权限页用此接口渲染(含中英文显示名): ```bash curl -sS https://waf.example.com/api/sys/permissions/tree \ -H "Authorization: Bearer $TOKEN" \ -H "Accept-Language: zh-CN" \ | jq ``` 返回结构含 `module / resources[] / actions[]`,每条带 `key` 和 i18n `name`。 ## 给 CI 账号最小权限的实践 例 1:CI 只需要发证 → 授予 ``` guard.cert.list, guard.cert.view, guard.cert.upload, guard.cert.delete, guard.cert.bind ``` 例 2:CI 只读统计大屏 → 授予 ``` analytics.overview.view, analytics.access.view, analytics.protect.view ``` 切忌给 CI 账号管理员级权限;最小权限是减小爆炸半径的第一道闸门。 ## 相关文档 - [认证说明](/docs/auth) — 角色与会话的关系 - [API 文档](/docs/api) — 接口对应的权限 key - [CLI 工具](/docs/cli) — 等价的命令行操作 --- *Cloud WAF · 权限矩阵由服务端权限注册表生成* --- # 错误码总览 > 业务码 = 0 表示成功;非 0 由各模块自定义。所有错误响应 HTTP 体形如: > `{ "code": <业务码>, "message": "<人读消息>" }` 本页列出 Cloud WAF 平台**全部业务错误码**(与 `pkg/errors/codes.go` 真值源一一对账)。第三方接入方应该按 `code` 分支决策,不要解析 `message` 文本——文本随多语言/版本可能变化。 ## HTTP 状态码 vs 业务错误码 平台两层错误模型: | 层 | 字段 | 用途 | |---|---|---| | HTTP | status code | 协议层(4xx 客户端错、5xx 服务端错),适合负载均衡/网关层判断 | | 业务 | `code` (json body) | 应用层细分错误,唯一可靠的"错在哪"信号 | **约定**: - 业务码 = 0 时 HTTP 必为 200。 - 业务码 ≠ 0 时 HTTP 一般 4xx/5xx,但少数幂等接口(如重复吊销 API Key)即使业务码非 0 也返回 200。 - HTTP 401(凭据失效)特意合并多种"未通过认证"成单一返回,避免侧信道暴露内部状态——业务层不再细分。 - HTTP 403(凭据有效但权限不足)会带具体业务码(1011 / 1052 / 1053 / 1056 等),客户端按业务码区分处置。 ## 通用 HTTP 错误码(非业务码) 仅在请求未到达业务逻辑时返回(参数校验失败、协议错误、未授权)。 | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 400 | 400 | 请求格式错误(json 解析失败、必填字段缺失) | 修正请求体后重试 | | 401 | 401 | 未认证(token 缺失/无效/过期) | 重新登录或更新 API Key | | 403 | 403 | 权限不足(已认证但无对应 perm) | 申请权限或换账号 | | 404 | 404 | 资源不存在 | 检查路径与 ID | | 409 | 409 | 冲突(如重复创建) | 改名或先删除已有资源 | | 500 | 500 | 服务器内部错误 | 重试一次;持续失败联系运维 | ## 模块码段分配 | 模块 | 范围 | 备注 | |---|---|---| | sys | 1000–1999 | 用户/角色/OEM/会话/API Key | | guard | 2000–2999 | 域名/证书/策略/规则/IP 集 | | node | 3000–3999 | 节点/节点组/IP/ACL 策略 | | chart | 4000–4999 | 统计图表查询/导出 | | alert | 5000–5999 | 告警任务/记录/联系人 | | zdns | 6000–6999 | DNS 域名/记录 | | notify | 7000–7999 | 通知通道/模板 | | report | 8000–8999 | 报表任务/文件 | | assistant | 9000–9999 | AI 助手/会话/知识库/MCP 工具 | | geoip | 10000–10999 | GeoIP 查询 | | channel | 11000–11999 | 渠道伙伴接入/onboard | ## sys 模块(1000–1999) ### 用户与认证(1001–1011) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 1001 | 404 | 用户不存在 | 检查用户名 / id | | 1002 | 401 | 密码错误 | 重新输入;多次错误账号会被锁定 | | 1003 | 403 | 用户被锁定 | 联系管理员解锁 | | 1004 | 400 | 验证码错误或过期 | 刷新验证码重试 | | 1005 | 401 | 会话已过期 | 重新登录 | | 1006 | 409 | 用户名已存在 | 改名 | | 1007 | 404 | 角色不存在 | 检查角色 id | | 1008 | 409 | 角色名已存在 | 改名 | | 1009 | 403 | 不允许删除该角色(系统内置/正在使用) | 先解绑后删 | | 1010 | 400 | 旧密码错误(修改密码场景) | 重新输入旧密码 | | 1011 | 403 | 权限不足 | 申请对应 perm | ### 设置与 OEM(1012, 1020–1023) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 1012 | 403 | 该设置不允许 API 修改(仅命令行/配置文件) | 走运维通道 | | 1020 | 404 | OEM 不存在 | 检查 OEM id | | 1021 | 409 | OEM 域名已存在 | 改 hostname | | 1022 | 403 | 不允许删除默认 OEM | 该 OEM 受系统保护 | | 1023 | 400 | Logo 文件非法(格式 / 尺寸 / 大小) | 重新上传符合规格的图片 | ### 订单与套餐(1030–1033, 1040) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 1030 | 404 | 订单不存在 | 检查订单 id | | 1031 | 400 | 订单状态切换不合法 | 按状态机重试 | | 1032 | 404 | 套餐不存在 | 检查套餐 id | | 1033 | 409 | 套餐名已存在 | 改名 | | 1040 | 404 | 公告不存在 | 检查公告 id | ### API Key 子模块(1050–1059) | code | HTTP | 含义 | 是否可重试 | 是否需重新签发 | |---|---|---|---|---| | 1050 | 401 | API Key 无效/过期/吊销 | 否 | 是 | | 1051 | 404 | API Key 不存在或不在可见范围 | 否 | N/A | | 1052 | 400 | scope 超出当前用户 RBAC 边界 | 否 | 是(缩窄 scope 重签) | | 1053 | 403 | API Key scope 不足,无法访问目标接口 | 否 | 视情况(可能需扩大 scope 重签,或换 user 通道) | | 1054 | 400 | expires_in_days 超出上限 365 | 否 | N/A | | 1055 | 400 | API Key name 必填 | 否 | N/A | | 1056 | 403 | 跨 OEM/越权操作他人 API Key | 否 | N/A | > **scope 匹配规则**:精确字符串相等,**不展开通配符**(写 `guard.*` 进 scope 不会匹配任何接口)。需要某模块全部权限时请逐条列叶子 perm key。 ## guard 模块(2000–2999) ### 域名与策略(2001–2010) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 2001 | 404 | 域名不存在 | 检查 domain id | | 2002 | 409 | 域名已存在 | 改名或先删除已有 | | 2003 | 404 | 策略不存在 | 检查 policy id | | 2004 | 409 | 策略名已存在 | 改名 | | 2005 | 404 | 证书不存在 | 检查 cert id | | 2006 | 409 | 证书名已存在 | 改名 | | 2007 | 400 | 证书 PEM 格式非法 | 重新粘贴完整 PEM(含 BEGIN/END) | | 2008 | 409 | 证书已绑定到该域名 | 跳过本次绑定 | | 2009 | 403 | 策略正在被使用,不能删除 | 先解绑相关域名 | | 2010 | 403 | 域名仍有活动节点,不能删除 | 先停用域名再删 | ### IP 集与规则(2011–2019) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 2011 | 404 | IP 集不存在 | 检查 set id | | 2012 | 409 | IP 集名已存在 | 改名 | | 2013 | 409 | IP 已在该集合中 | 跳过本次添加 | | 2014 | 404 | WAF 规则组不存在 | 检查 group id | | 2015 | 404 | 转发规则不存在 | 检查 forward id | | 2016 | 404 | 调度组不存在 | 检查 schedule id | | 2017 | 403 | 不允许删除默认调度 | 系统保护 | | 2018 | 404 | CC 规则不存在 | 检查 rule id | | 2019 | 404 | ACL 规则不存在 | 检查 rule id | ## node 模块(3000–3999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 3001 | 404 | 节点不存在 | 检查 node id | | 3002 | 409 | 节点名已存在 | 改名 | | 3003 | 404 | IP 地址不存在 | 检查 ip id | | 3004 | 409 | IP 地址已存在 | 跳过 | | 3005 | 404 | 节点组不存在 | 检查 group id | | 3006 | 409 | 节点组名已存在 | 改名 | | 3007 | 404 | ACL 策略不存在 | 检查 policy id | | 3008 | 409 | ACL 策略名已存在 | 改名 | | 3009 | 404 | ACL 规则不存在 | 检查 rule id | | 3010 | 403 | ACL 策略仍有规则,不能删除 | 先清空规则 | ## chart 模块(4000–4999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 4001 | 500 | 图表查询失败(PG/ES 异常) | 重试一次;持续失败联系运维 | | 4002 | 400 | 时间范围非法(stime > etime / 跨度过大) | 收窄时间范围 | | 4003 | 400 | 字段非法(不在白名单 / 拼错) | 检查字段名拼写 | | 4004 | 500 | 导出失败 | 减小数据量重试 | | 4005 | 429 | 触发限流 | 退避后重试 | ## alert 模块(5000–5999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 5001 | 404 | 告警任务不存在 | 检查 task id | | 5002 | 409 | 告警任务名已存在 | 改名 | | 5003 | 404 | 告警记录不存在 | 检查 record id | | 5004 | 404 | 告警联系人不存在 | 检查 contact id | | 5005 | 409 | 告警联系人已存在 | 跳过 | | 5006 | 404 | 告警配置不存在 | 检查 config id | | 5007 | 400 | 告警类型非法 | 用枚举内的合法值 | | 5008 | 429 | 告警冷却中(防抖) | 等冷却结束 | | 5009 | 429 | 告警限流(达到上限) | 调高上限或等周期重置 | | 5010 | 500 | 告警开关切换失败 | 重试 | ## zdns 模块(6000–6999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 6001 | 404 | DNS 域名不存在 | 检查 domain id | | 6002 | 409 | DNS 域名已存在 | 改名 | | 6003 | 404 | DNS 记录不存在 | 检查 record id | | 6004 | 404 | DNS 操作不存在 | 检查 op id | | 6005 | 400 | DNS 记录类型非法 | 用 A/AAAA/CNAME/MX/TXT 等 | ## notify 模块(7000–7999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 7001 | 404 | 通知通道不存在 | 检查 channel id | | 7002 | 409 | 通知通道名已存在 | 改名 | | 7003 | 404 | 通知模板不存在 | 检查 template id | | 7004 | 409 | 通知模板名已存在 | 改名 | | 7005 | 500 | 通知发送失败 | 检查通道连通性后重试 | ## report 模块(8000–8999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 8001 | 404 | 报表任务不存在 | 检查 task id | | 8002 | 409 | 报表任务名已存在 | 改名 | | 8003 | 404 | 报表文件不存在或已过期 | 重新生成 | | 8004 | 404 | 报表记录不存在 | 检查 record id | | 8005 | 500 | 报表生成失败 | 减小时间范围或导出字段后重试 | ## assistant 模块(9000–9999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 9001 | 404 | AI 会话不存在 | 检查 session id | | 9002 | 403 | AI 会话已归档 | 解档或新开会话 | | 9003 | 500 | 消息操作失败 | 重试 | | 9004 | 404 | MCP 工具不存在 | 检查 tool id | | 9005 | 409 | 工具名已存在 | 改名 | | 9006 | 404 | 知识库不存在 | 检查 kb id | | 9007 | 404 | 文档不存在 | 检查 doc id | | 9008 | 404 | AI 配置不存在 | 检查 config id | | 9009 | 409 | 知识库名已存在 | 改名 | | 9010 | 500 | 文档建索引失败 | 检查文档格式后重试 | ## geoip 模块(10000–10999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 10001 | 500 | GeoIP 查询失败 | 重试一次 | | 10002 | 404 | IP 不在 GeoIP 数据库内(私网/未收录) | 用公网 IP 查询 | ## channel 模块(11000–11999) | code | HTTP | 含义 | 处置建议 | |---|---|---|---| | 11001 | 404 | Onboard 流程不存在 | 检查 onboard id | | 11002 | 410 | 链接已过期 | 重新申请链接 | | 11003 | 400 | 链接 token 非法 | 重新申请链接 | | 11004 | 409 | 域名已存在 | 改名或先删除已有 | | 11005 | 500 | 域名探测失败 | 检查域名 DNS 解析后重试 | | 11006 | 400 | 证书非法 | 重新粘贴完整 PEM | | 11007 | 400 | 域名尚未就绪,不能确认 | 等域名探测/证书安装完成 | ## 跨模块通用约定 - HTTP 401 → 凭据失效(统一码,故意合并避免侧信道) - HTTP 403 → 凭据有效但权限/归属不匹配,业务码用于细分(1011 / 1052 / 1053 / 1056 / 1009 / 1022 / 2017 / 3010 等) - HTTP 404 → 资源不存在或不在可见范围(典型业务码 1001 / 1020 / 2001 / 3001 …) - HTTP 409 → 唯一性冲突(典型业务码 1006 / 1021 / 2002 / 3002 …) - HTTP 429 → 限流,带 `Retry-After` 头(4005 / 5008 / 5009) - HTTP 5xx → 服务端故障,建议指数退避重试(10ms → 100ms → 1s → 10s) ## 客户端处置建议 ```python # Python 伪代码:基于业务码而非 HTTP 状态决策 import requests from time import sleep resp = requests.get(api_url, headers={"Authorization": f"ApiKey {key}"}) body = resp.json() code = body.get("code", -1) if code == 0: return body["data"] elif code == 1050: raise Exception("API Key 失效,请重新签发") elif code == 1052: raise Exception(f"scope 越权:{body['message']}") elif code in (4005, 5008, 5009): sleep(int(resp.headers.get("Retry-After", "60"))) return retry() elif code >= 5000 and resp.status_code >= 500: return exponential_backoff_retry() else: raise Exception(f"业务错误 {code}: {body['message']}") ``` > 永远基于 `code` 字段决策,不要解析 `message` 文本——文本随多语言/版本变化。