Files
nexus/Project/fonrey/TECH_STACK/API_CONTRACT.md
2026-05-02 16:21:46 +08:00

26 KiB
Raw Blame History

For AI assistants: Read this entire file before designing or implementing any API. Contract rules here are mandatory. Do not invent per-module variants unless explicitly allowed.

Fonrey API 契约规范(API_CONTRACT)

版本: 1.2
适用范围: 全模块(account / permission / property / client / complex / org / setting)+ 平台管理后台(admin)
关联总纲: TECH_STACK/TECH_STACK.md
最后更新: 2026-05-02


变更历史

日期 变更人 变更内容
2026-04-30 Atlas 补充"变更历史"章节(文档治理)
2026-05-02 Atlas 升至 v1.2:新增 §12 平台管理后台端点附录(A-B-1);适用范围扩展至 admin 模块

1. 文档定位与原则

本文件定义 Fonrey 全局 API 契约标准,解决跨模块接口风格漂移问题。模块技术方案中的 API 章节必须遵循本文件,不得各自定义冲突规则。

1.1 强制级别

  • MUST:必须遵守,违反视为缺陷
  • SHOULD:建议遵守,若不遵守需在模块文档注明原因
  • MAY:可选能力

1.2 适用接口类型

  • JSON API(/api/**)
  • HTMX 片段端点(HTML response)
  • 文件上传/下载端点

2. 请求 / 响应格式规范

2.1 请求体规范(JSON API)

  • Content-Type MUST 为 application/json
  • charset=utf-8 SHOULD 显式声明
  • 写操作(POST/PUT/PATCH)MUST 传业务 payload;禁止空对象写入

推荐请求结构:

{
  "data": {
    "...": "业务字段"
  },
  "meta": {
    "request_id": "可选,客户端透传"
  }
}

兼容说明:历史端点已存在 filters/sort/pagination 平铺结构时可继续使用,但新接口 SHOULD 迁移到 data 容器。

2.2 成功响应规范(JSON API)

成功响应 MUST 使用统一 envelope:

{
  "ok": true,
  "data": {},
  "meta": {
    "request_id": "uuid",
    "timestamp": "2026-04-27T16:30:00+08:00"
  }
}

说明:

  • ok MUST 为 true
  • data MUST 存在(可为空对象 {} 或空数组 [])
  • meta SHOULD 包含 request_id 与服务端时间

2.3 失败响应规范(JSON API)

失败响应 MUST 使用统一 envelope:

{
  "ok": false,
  "error": "权限不足",
  "code": "PROPERTY_PERMISSION_DENIED",
  "details": {},
  "meta": {
    "request_id": "uuid",
    "timestamp": "2026-04-27T16:30:00+08:00"
  }
}

说明:

  • error MUST 为面向用户/调用方可读消息
  • code MUST 为稳定机器可读码(大写下划线)
  • details MAY 提供字段级错误(如校验失败)

2.4 HTMX 响应规范

  • 成功:返回 HTML 片段;必要时通过 HX-Trigger 触发前端事件
  • 失败:
    • 状态码 MUST 正确(4xx/5xx)
    • SHOULD 在响应头返回 HX-Trigger,例如:
      • {"toast:error":"权限不足"}
      • {"toast:error":"请求失败,请重试"}

3. 错误码规范

3.1 命名规则

  • 错误码 MUST 为 UPPER_SNAKE_CASE
  • 推荐前缀:<MODULE>_(如 PROPERTY_ / CLIENT_ / ORG_)

3.2 HTTP 状态码基线

HTTP 使用场景 示例 code
400 参数错误、业务前置条件不满足 PROPERTY_VALIDATION_ERROR
401 仅用于纯 API Token 鉴权失败(当前 Web 会话模式一般不用) AUTH_UNAUTHORIZED
403 已登录但无权限 *_PERMISSION_DENIED
404 资源不存在或不可见 *_NOT_FOUND
409 状态冲突、任务未就绪 *_STATE_CONFLICT / *_JOB_NOT_READY
422 字段级校验错误(可选) *_VALIDATION_FAILED
429 频控触发 RATE_LIMITED
500 未预期异常 INTERNAL_ERROR

3.3 稳定性要求

  • code MUST 可稳定依赖,不得频繁改名
  • 错误文案 error 可优化,但不应影响调用方流程判断

4. 分页规范

Fonrey 列表查询 MUST 使用 Keyset 分页;禁止 OFFSET 深分页。

4.1 请求格式

{
  "filters": {},
  "sort": {"field": "updated_at", "order": "desc"},
  "pagination": {"mode": "keyset", "cursor": null, "limit": 20}
}

4.2 响应格式

{
  "ok": true,
  "data": {
    "items": [],
    "next_cursor": "opaque_cursor_2"
  },
  "meta": {
    "pagination": {"mode": "keyset", "cursor": "opaque_cursor", "limit": 20}
  }
}

4.3 约束

  • limit MUST 有上限(建议 ≤ 100)
  • cursor MUST 为不透明字符串,禁止暴露内部排序字段组合
  • 排序字段 MUST 来自白名单,防止 SQL 注入与慢查询

5. 搜索 / 筛选规范

5.1 推荐请求结构

{
  "filters": {
    "keyword": "保利",
    "status": ["active", "pending"],
    "district_id": "uuid"
  },
  "sort": {"field": "updated_at", "order": "desc"},
  "pagination": {"mode": "keyset", "cursor": null, "limit": 20}
}

5.2 语义规范

  • keyword:模糊检索词(服务端统一做 trim)
  • 多选条件 MUST 使用数组(如 status: [])
  • 空数组 [] 语义:不限制该条件
  • null 语义:由模块文档明确(默认建议等同“不传”)

5.3 安全与性能

  • 仅允许白名单字段参与筛选和排序
  • LIKE/全文检索字段 SHOULD 建立索引或搜索策略
  • 查询快照哈希(用于缓存/导出)SHOULD 对 filters+sort+scope 进行规范化后计算

6. 上传规范

Fonrey 优先采用“预签名上传 + 回执提交(commit)”两段式。

6.1 标准流程

  1. 客户端请求 upload-token(业务 API)
  2. 客户端直传对象存储(R2)
  3. 客户端调用 commit API 回写元数据

6.2 合约要求

  • upload-token MUST 短时有效(建议 5~15 分钟)
  • commit MUST 幂等(建议支持 idempotency_key)
  • 上传白名单与大小限制 MUST 在模块文档声明并在服务端校验
  • SHOULD 校验 content_type 与 size
  • MAY 增加 sha256 校验确保完整性

6.3 错误码建议

  • *_UPLOAD_TOKEN_EXPIRED (409/400)
  • *_UPLOAD_FILE_TOO_LARGE (400)
  • *_UPLOAD_FILE_TYPE_NOT_ALLOWED (400)
  • *_UPLOAD_COMMIT_CONFLICT (409)

7. 文件下载规范

下载统一采用“导出任务 + 状态查询 + download endpoint”。

7.1 标准流程

  1. 创建导出任务 POST /api/**/export/jobs/
  2. 轮询任务状态 GET /api/**/export/jobs/{job_id}/
  3. 下载结果 GET /api/**/export/jobs/{job_id}/download/

7.2 合约要求

  • 任务未完成下载 MUST 返回 409 + *_EXPORT_JOB_NOT_READY
  • 下载链接 SHOULD 为一次性或短时有效 URL
  • 响应 SHOULD 设置 Content-Disposition(附件下载)
  • 文件名 SHOULD 带模块与日期,便于审计

8. 权限拒绝返回规范

8.1 JSON API

  • 未登录:MUST 返回 302(Web Session 场景)或 401(纯 API 场景)
  • 已登录无权限:MUST 返回 403
  • 失败体 MUST 使用统一错误 envelope,code 为 *_PERMISSION_DENIED

8.2 页面路由(SSR/HTMX)

  • 未登录:302 跳转登录页
  • 已登录无权限:403 页面(或 HTMX 403 片段)
  • HTMX 拒绝 SHOULD 触发 HX-Trigger toast 事件

8.3 测试强约束

每个受保护端点 MUST 覆盖三态:

  • 200(有权限)
  • 403(已登录无权限)
  • 302/401(未登录,视端点类型)

9. 乐观锁(Optimistic Locking)规范

9.1 适用场景

properties、clients、complexes 三张高竞争表的更新操作(PUT/PATCH)MUST 使用乐观锁并发控制,防止"后写覆盖先写"数据丢失。

9.2 请求规范

客户端发起更新时,MUST 在请求体中携带当前资源版本号:

{
  "data": {
    "sale_price": 180,
    "version": 3
  }
}

兼容说明:当前 Fonrey 为内部 Web / Electron 客户端,采用请求体传递 version 字段,无需 If-Match Header(避免 HTMX 额外配置复杂度)。未来若提供对外开放 REST API,可补充支持 If-Match: <version> Header 形式。

9.3 服务端执行规范

服务端执行 UPDATE 时 MUST 同时匹配 version,并将 version +1:

UPDATE properties
SET sale_price = :sale_price,
    version    = version + 1,
    updated_at = NOW(),
    updated_by = :operator_id
WHERE id      = :id
  AND version = :client_version   -- 乐观锁匹配
  AND deleted_at IS NULL;
  • 若受影响行数 = 1:更新成功,返回 200
  • 若受影响行数 = 0:抛 ConflictError,返回 409 + code *_VERSION_CONFLICT

9.4 冲突响应规范

{
  "ok": false,
  "error": "已被他人修改,请刷新重试",
  "code": "PROPERTY_VERSION_CONFLICT",
  "details": {
    "field": "version",
    "your_version": 3,
    "hint": "请重新获取最新数据后再提交"
  },
  "meta": {
    "request_id": "uuid",
    "timestamp": "2026-04-28T10:00:00+08:00"
  }
}
  • HTTP 状态码 MUST 为 409
  • code 格式:<MODULE>_VERSION_CONFLICT(如 PROPERTY_VERSION_CONFLICT / CLIENT_VERSION_CONFLICT / COMPLEX_VERSION_CONFLICT)
  • 前端 SHOULD 展示 Toast:「已被他人修改,请刷新重试」,并自动触发资源重新加载

9.5 Check List

  • version 字段在 GET 响应中 MUST 返回(供后续 PUT/PATCH 携带)
  • 服务层 update 方法 MUST 校验受影响行数,0 行时抛 ConflictError
  • 前端表单 MUST 在隐藏域中保存 version,随 PUT/PATCH 提交
  • HTMX 场景:冲突时后端 MUST 返回 HX-Trigger: {"toast:error":"已被他人修改,请刷新重试"}
  • 测试 MUST 覆盖:并发两次更新同版本,第二次 MUST 返回 409

10. 与模块文档的衔接规则

  • 各模块技术方案中的“四、API 设计原则”“六、关键 API 规范”“十二、错误码建议”必须引用本文件
  • 模块文档可补充模块特有 code 与字段,但不得与本规范冲突
  • 冲突时以本文件为准;若需例外,必须在模块文档显式记录 ADR 链接

10. 落地检查清单(Review Checklist)

  • 是否使用统一成功/失败 envelope
  • 错误码是否为稳定 UPPER_SNAKE_CASE
  • 列表接口是否全部 Keyset 分页
  • filters/sort 字段是否白名单化
  • 上传是否采用 token+commit 且具备幂等保障
  • 下载是否采用 job 流程并处理未就绪 409
  • 权限拒绝是否遵循 200/403/302(401) 三态
  • 测试是否覆盖契约关键路径
  • 所有视图是否附加 @extend_schema(或 @extend_schema_view)注解
  • 枚举字段是否通过 OpenApiTypes 或 ChoiceField 在 Schema 中完整暴露所有值
  • 生成的 openapi.json 是否已提交 / 与代码同步更新
  • schemathesis 契约测试是否纳入 CI(至少覆盖 Positive 用例)

11. OpenAPI 落地规范(机器可读契约)

For AI assistants: §11 是实现层强约定。生成视图代码时必须同步写 @extend_schema;生成测试代码时必须包含契约断言。

11.1 工具链(MUST)

角色 工具 说明
Schema 生成 drf-spectacular 唯一授权的 OpenAPI 生成库;禁止 drf-yasg
Schema 文件 openapi.json(根目录) 每次 CI 必须重新生成并 diff 检查
契约测试 schemathesis 基于生成 Schema 做 Positive + Negative 测试
文档 UI Swagger UI(/api/docs/) 开发环境默认开启,生产环境按需

安装:

pip install drf-spectacular schemathesis

settings.py 最低配置:

INSTALLED_APPS += ["drf_spectacular"]

SPECTACULAR_SETTINGS = {
    "TITLE": "Fonrey API",
    "VERSION": "1.1.0",
    "SERVE_INCLUDE_SCHEMA": False,
    "SCHEMA_PATH_PREFIX": r"/api/",
    # 枚举值直接展开(不折叠成 $ref),便于 AI agent 直接读值
    "ENUM_GENERATE_CHOICE_DESCRIPTION": True,
    "COMPONENT_SPLIT_REQUEST": True,
}

urls.py:

from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns += [
    path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
    path("api/docs/",   SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
]

11.2 视图注解规范(MUST)

每个 APIView / ViewSet 动作 MUST 携带 @extend_schema,最低包含:

from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample
from drf_spectacular.types import OpenApiTypes

@extend_schema(
    summary="获取房源详情",           # 简短操作名(中文 OK)
    tags=["property"],              # 模块 tag,与路由前缀一致
    responses={200: PropertyDetailSerializer},
    # 失败响应也要声明,给 AI agent 提供完整错误路径
    responses={
        200: PropertyDetailSerializer,
        403: OpenApiTypes.OBJECT,   # 统一 error envelope
        404: OpenApiTypes.OBJECT,
    },
)
def retrieve(self, request, pk=None):
    ...

枚举字段 MUST 在 Serializer 中使用 ChoiceField 并指定 choices,drf-spectacular 会自动生成 enum 约束:

from ENUMS import PropertyType  # 取自项目枚举常量

class PropertySerializer(serializers.Serializer):
    property_type = serializers.ChoiceField(
        choices=PropertyType.choices,
        help_text="房源类型(详见 DATA_MODEL/ENUMS.md § property.property_type)"
    )

分页/筛选端点额外声明 parameters:

@extend_schema(
    parameters=[
        OpenApiParameter("cursor",   OpenApiTypes.STR,  description="Keyset 游标,首页传 null"),
        OpenApiParameter("limit",    OpenApiTypes.INT,  description="每页条数,最大 100"),
        OpenApiParameter("status",   OpenApiTypes.STR,  description="状态筛选,多选用逗号分隔"),
    ]
)

11.3 Schema 文件管理(MUST)

CI pipeline MUST 包含以下步骤,防止 Schema 与代码漂移:

# 生成最新 Schema
python manage.py spectacular --color --file openapi.json

# diff 检查(有变更时 CI 提醒,但不阻断 — 由开发者 review 后提交)
git diff --exit-code openapi.json || echo "⚠️  openapi.json has changed — please review and commit"
  • openapi.json MUST 纳入版本控制(不加入 .gitignore)
  • 合并 PR 时若 openapi.json 有非预期变更,MUST 作为 Review 阻断项

11.4 契约测试规范(MUST)

使用 schemathesis 对每个模块做 Positive 路径覆盖,最低要求:

# 对本地运行的 dev server 跑契约测试
schemathesis run openapi.json \
  --base-url http://localhost:8000 \
  --auth-header "Authorization: Bearer *** \
  --checks status_code_conformance response_schema_conformance \
  --tag property   # 可按模块 tag 分批跑

CI 集成示例(GitHub Actions):

- name: Contract Tests
  run: |
    python manage.py spectacular --file openapi.json
    schemathesis run openapi.json \
      --base-url http://localhost:8000 \
      --checks status_code_conformance response_schema_conformance \
      --exitfirst   # 首个失败即停止

AI Agent 验收词(Acceptance Criteria):实现任意 API 端点后,须能通过以下验证:

GIVEN  openapi.json 已重新生成
WHEN   schemathesis 对该端点执行 Positive 测试
THEN   status_code_conformance PASS(响应码与 Schema 声明一致)
  AND  response_schema_conformance PASS(响应体结构与 Serializer 一致)
  AND  所有枚举字段值落在 ENUMS.md 定义的合法值集合内

11.5 枚举值契约一致性(MUST)

ENUMS.md 是枚举的单一事实来源(Source of Truth)。项目 MUST 维护一个 enums.py(或按模块拆分),与 ENUMS.md 保持同步:

# fonrey/core/enums.py — 机器可读枚举常量,与 ENUMS.md 严格对齐
from django.db import models

class PropertyType(models.TextChoices):
    RESIDENTIAL = "residential", "住宅"
    COMMERCIAL  = "commercial",  "商业"
    OFFICE      = "office",      "办公"
    INDUSTRIAL  = "industrial",  "工业"
    LAND        = "land",        "土地"
    OTHER       = "other",       "其他"

AI agent 实现时验证词:

GIVEN  enums.py 中某枚举类的所有 value
WHEN   与 ENUMS.md 对应域的值列表对比
THEN   两侧完全一致(无多余值,无缺失值,大小写相同)

11.6 分阶段落地路线(参考)

阶段 目标 完成标志
P0 接入 安装 drf-spectacular,生成首版 openapi.json,Swagger UI 可访问 /api/docs/ 正常渲染,无 import 报错
P1 注解补全 所有现有视图加 @extend_schema,枚举字段用 ChoiceField openapi.json 无 {} 空 Schema;所有端点有 summary 和 tags
P2 契约测试 schemathesis 纳入 CI,Positive 用例全绿 CI status_code + response_schema 两项检查全 PASS
P3 持续守护 openapi.json diff 纳入 PR Review;枚举值变更同步 enums.py PR checklist 自动提醒 Schema 变更

12. 平台管理后台端点附录(A-B-1 回应)

背景:审核报告 A-B-1 指出 API_CONTRACT 缺少 /admin/... 命名空间端点规范,导致 Backend 实现缺乏契约约束。本章补齐平台管理后台(admin.fonrey.com)所有端点清单,作为机器可读约束附录。

适用规则:本章端点遵循第 2–10 章全局规范;以下特例优先级高于全局规范:

  • 认证:Django Session(adminSessionId Cookie)+ CSRF(X-CSRFToken)+ 必要时 MFA Step-up;不使用 JWT / Bearer Token。
  • 响应类型:GET 页面视图返回 text/html;POST/PATCH 写操作返回 JSON 或 HTMX partial(在端点备注中明示)。
  • 版本控制:/admin/... 无版本号前缀;破坏性变更通过 Django View Mixin 灰度。
  • 非法权限访问:统一返回 403(不暴露资源是否存在)。

12.1 认证端点

端点 方法 鉴权 请求 成功响应 失败响应 备注
/admin/login/ GET 公开 — 200 HTML(登录表单) — 已登录时 302 → /admin/
/admin/login/ POST 公开 username, password 302 → /admin/mfa/verify/ 200 HTML(表单 + 错误) 失败计数写 Redis;5 次锁 15 min
/admin/login/mfa/verify/ GET session 中有 pre_mfa_admin_id — 200 HTML(TOTP 输入框) 302 → /admin/login/ 超时 5 min 未提交则清 pre_mfa
/admin/login/mfa/verify/ POST session 中有 pre_mfa_admin_id otp_code 302 → /admin/ 200 HTML(错误)/ 429(OTP 失败 ≥3) 成功后写 admin_sessions,清 pre_mfa_admin_id
/admin/login/mfa/step-up/ POST AdminSession otp_code 200 JSON {"ok": true} 400 JSON {"error": "invalid_otp"} 写 session.mfa_confirmed_at + Redis pub:mfa:stepup:*
/admin/logout/ POST AdminSession — 302 → /admin/login/ — 清 admin_sessions,flush session

12.2 租户管理端点

端点 方法 鉴权 请求要点 成功响应 失败响应 备注
/admin/tenants/ GET AdminSession(任意角色) ?status=, ?q=, ?page_cursor= 200 HTML(租户列表页) — Keyset 分页;禁止 OFFSET
/admin/tenants/create/ GET AdminSession(SUPER/OPS) — 200 HTML(创建表单) — —
/admin/tenants/create/ POST AdminSession(SUPER/OPS)+ CSRF tenant_name, contact_phone, contact_email, license_user_limit, paid_until 302 → /admin/tenants/<id>/ 200 HTML(表单 + 错误)/ 409(tenant_code 冲突) 写 tenants(status='provisioning')后入队 provision_tenant Saga
/admin/tenants/<uuid:id>/ GET AdminSession — 200 HTML(详情页) 404 —
/admin/tenants/<uuid:id>/edit/ GET AdminSession(SUPER/OPS) — 200 HTML(编辑表单) 404 —
/admin/tenants/<uuid:id>/edit/ POST AdminSession(SUPER/OPS)+ CSRF 可编辑字段(name / contact_* / license_user_limit / paid_until) 302 → 详情页 200 HTML(表单 + 错误) 写 platform_audit_logs(UPDATE_TENANT_INFO)
/admin/tenants/<uuid:id>/suspend/ POST AdminSession(SUPER)+ CSRF + MFA Step-up suspended_reason, suspended_until(可选) 200 JSON {"ok": true} + HTMX partial 400 / 409(已暂停) suspended_reason 枚举见 ENUMS.md;写审计
/admin/tenants/<uuid:id>/resume/ POST AdminSession(SUPER)+ CSRF + MFA Step-up — 200 JSON {"ok": true} + HTMX partial 409(非 suspended 状态) 写审计
/admin/tenants/<uuid:id>/cancel/ POST AdminSession(SUPER)+ CSRF + MFA Step-up reason 200 JSON {"ok": true} 409(非 active 状态) 进入 pending_cancellation;冷静期 30 天
/admin/tenants/<uuid:id>/hard-delete/ POST AdminSession(SUPER)+ CSRF + MFA Step-up(5 min 内)+ 双人复核 token confirm_tenant_name, reviewer_token 200 JSON {"ok": true} 400(名称不符)/ 403(token 无效)/ 409(非 pending_cancellation + 30d 状态) 入队 hard_delete_tenant;写审计 HARD_DELETE_TENANT
/admin/tenants/<uuid:id>/update-license/ POST AdminSession(SUPER/OPS)+ CSRF paid_until, license_user_limit 200 JSON {"ok": true} 400(参数非法) 写审计 UPDATE_LICENSE / UPDATE_LICENSE_USER_LIMIT
/admin/tenants/<uuid:id>/export/ POST AdminSession(SUPER/OPS)+ CSRF export_format(csv/json) 202 JSON {"task_id": "..."} — 入队 run_export;写 export_tasks(status='pending')
/admin/tenants/<uuid:id>/export/<uuid:task_id>/status/ GET AdminSession — 200 JSON {"status": "pending"|"in_progress"|"success"|"failed", "download_url": null|"..."} 404 HTMX 轮询端点;终态时移除 hx-trigger

12.3 平台管理员账号端点

端点 方法 鉴权 请求要点 成功响应 备注
/admin/admins/ GET AdminSession(SUPER) — 200 HTML(管理员列表) —
/admin/admins/create/ POST AdminSession(SUPER)+ CSRF username, email, role 302 → 列表页 写审计 CREATE_PLATFORM_ADMIN
/admin/admins/<uuid:id>/edit/ POST AdminSession(SUPER)+ CSRF role, is_active 302 → 列表页 写审计 UPDATE_PLATFORM_ADMIN_ROLE
/admin/admins/<uuid:id>/deactivate/ POST AdminSession(SUPER)+ CSRF + MFA Step-up — 302 → 列表页 清对应 admin_sessions;写审计 DEACTIVATE_PLATFORM_ADMIN
/admin/admins/<uuid:id>/reset-password/ POST AdminSession(SUPER)+ CSRF — 200 JSON {"ok": true} 发初始密码邮件;写审计

12.4 IP 白名单端点

端点 方法 鉴权 请求要点 成功响应 备注
/admin/ip-whitelist/ GET AdminSession(SUPER) — 200 HTML(白名单列表) —
/admin/ip-whitelist/add/ POST AdminSession(SUPER)+ CSRF cidr, label, expires_at(可选) 302 → 列表页 清 Redis pub:ipwl:active
/admin/ip-whitelist/<uuid:id>/toggle/ POST AdminSession(SUPER)+ CSRF is_active 200 JSON {"ok": true} 清 Redis pub:ipwl:active
/admin/ip-whitelist/<uuid:id>/delete/ POST AdminSession(SUPER)+ CSRF + MFA Step-up — 302 → 列表页 清 Redis pub:ipwl:active;写审计

12.5 客户端版本发布端点

端点 方法 鉴权 请求要点 成功响应 备注
/admin/releases/ GET AdminSession — 200 HTML(版本列表) —
/admin/api/client-releases/ POST AdminSession(SUPER)+ CSRF + MFA Step-up version, platform, arch, release_type, artifact(文件上传) 201 JSON(版本对象) 校验 SemVer;入队 checksum + scan
/admin/api/client-releases/<uuid:pk>/ PATCH AdminSession(SUPER)+ CSRF status, release_notes, force_update, min_required_version 200 JSON(更新后对象) 状态机约束见 平台管理后台技术方案.md §5.2
/admin/api/client-releases/<uuid:pk>/rollback/ POST AdminSession(SUPER)+ CSRF + MFA Step-up — 200 JSON {"ok": true} 原子切换 published;写审计 ROLLBACK_CLIENT_RELEASE

12.6 备份与恢复端点

端点 方法 鉴权 请求要点 成功响应 备注
/admin/backups/ GET AdminSession(SUPER/OPS) — 200 HTML(备份列表) —
/admin/backups/trigger/ POST AdminSession(SUPER)+ CSRF backup_type(full/incremental) 202 JSON {"task_id": "..."} 入队 run_backup
/admin/backups/<uuid:id>/status/ GET AdminSession — 200 JSON {"status": "...", "size_bytes": ...} HTMX 轮询端点
/admin/backups/<uuid:id>/restore/ POST AdminSession(SUPER)+ CSRF + MFA Step-up(5 min 内)+ 双人复核 token confirm_text, reviewer_token 202 JSON {"task_id": "..."} 高危操作;入队 run_restore;写审计 RESTORE_BACKUP

12.7 平台审计日志端点

端点 方法 鉴权 请求要点 成功响应 备注
/admin/audit-logs/ GET AdminSession(SUPER/OPS) ?action_type=, ?operator_id=, ?target_id=, ?from=, ?to=, ?page_cursor= 200 HTML(审计列表) Keyset 分页;只读,无写操作
/admin/audit-logs/<uuid:id>/ GET AdminSession(SUPER/OPS) — 200 HTML(详情) —

12.8 仪表盘端点(统计只读)

端点 方法 鉴权 说明
/admin/dashboard/ GET AdminSession(任意角色) 总览页(读 Redis 缓存)
/admin/dashboard/stats/ GET AdminSession HTMX 局刷统计块;读 pub:dashboard:stats 缓存
/admin/dashboard/version-distribution/ GET AdminSession 版本分布图;转发至 /api/release/v1/metrics/version-distribution/

12.9 端点完整性守护规则

本节端点清单必须与 平台管理后台技术方案.md §4 保持同步。新增端点必须同时更新本表和技术方案;删除/重命名端点必须先创建 ADR,格式 ADR-YYYYMMDD-XXX,本文件"变更历史"行追加记录。

AI agent 实现时验证词:

GIVEN  本附录第 12.1–12.8 节的端点清单
WHEN   开发者实现或修改任意 /admin/... 端点
THEN   端点鉴权方式、请求字段、成功状态码、失败状态码与本附录完全一致;
       任何缺失端点视为 A-B-1 级 Blocker