# Feature Spec: 运营后台 RBAC(Ops · ECR-013A) > Status: `Active`(**Closed** · Feature Complete)· Map: `§7 运营后台` · Phase: `Ops-D` > Parent: **WAVE0-FROZEN** (`27f27a1`) > ESS: `docs/ECR/ECR-013A-admin-rbac.md`(**Closed**) > Capability: `AdminGovernance` · BC: `Admin_RBAC` > 模板:`docs/WAVE0/contracts/OPS-CONTRACT-TEMPLATE.md` --- ## ESS 门禁 1. Change Level = **L2** → 已 Approved + Coding auth + Implemented + **Closed** 2. ~~Contract Definition only~~ — 已完成 3. 实现证据:`docs/TEST_REPORT/ECR-013A.md` · `docs/CODE_REVIEW/ECR-013A.md` --- ## L0 Capability | 字段 | 内容 | |------|------| | Capability ID | `AdminGovernance` | | Purpose | 以最小权限原则约束管理员可执行的运营动作 | | Why now | Ops-A 全员等价权限;进入 M1+ 前必须有权限面,否则越权不可治理 | | Non-goals | 用户账户启停(ECR-013B);兑换码;CMS;Ask Prompt;真支付;UGC | --- ## L1 Bounded Context | 字段 | 内容 | |------|------| | Primary BC | `Admin_RBAC` | | owns | `AdminRole` · `AdminPermission` | | does_not_own | `User` · `Profile` · `UserStatus` · `Membership` · `Payment` | | allowed | `Admin_Auth_Audit.write_audit` | | forbidden | `Payment` · `Membership.write` · `DeviceAuth.user_token_issue` | 权威:`.ai/domain/boundary-rules.md` --- ## 1. 功能定义 | 字段 | 内容 | |------|------| | Name | Admin RBAC | | Purpose | 角色与权限点绑定;API 级强制鉴权;变更可审计 | | Business Goal | 支撑多运营角色,而不扩大 C 端攻击面 | | In | Out | |---|---| | Role CRUD(最小:list/get/update permissions) | UserStatus / 封禁(013B) | | `GET /admin/me` 返回 permissions | 细粒度数据行级 ACL | | 既有 admin 写操作挂 permission 检查 | 新业务模块权限爆炸式新增(另 ECR) | --- ## L2 Domain | Entity | 不变式 | |--------|--------| | `AdminRole` | `name` 唯一;系统角色 `super_admin` 不可删除 | | `AdminPermission` | 稳定字符串码(见下表);只增不改语义 | | `AdminAccount.role_id` | 每个账号恰好一个角色(本切片);无角色视为拒绝写操作 | ### Permission catalog(本切片冻结) | Code | 覆盖既有能力 | |------|----------------| | `admin.users.read` | GET users / users/:id / stats | | `admin.users.membership.grant` | POST membership/grant | | `admin.users.ask_quota.grant` | POST ask-quota/grant | | `admin.orders.read` | GET orders | | `admin.audit.read` | GET audit-logs | | `admin.analytics.read` | GET analytics/*(Ops-B) | | `admin.content.write` | home tools / scales publish(Ops-C) | | `admin.roles.read` | GET roles | | `admin.roles.write` | 变更角色权限 | `super_admin` 种子拥有上表全部。 --- ## L3 API Contract(意图 · 实现轮同步 OpenAPI) 前缀:`/api/v1/admin` · AdminAuth · 信封 `{code,message,data}` | Method | Path | 权限 | 语义 | |--------|------|------|------| | GET | `/roles` | `admin.roles.read` | 角色列表 | | GET | `/roles/:id` | `admin.roles.read` | 角色 + permissions[] | | PUT | `/roles/:id/permissions` | `admin.roles.write` | 全量替换权限集;写 AuditLog | | GET | `/me` | (已登录) | **扩展**返回 `role` + `permissions[]` | 既有写接口:在实现轮挂上表 permission;缺权 → **403** + AuditLog(尝试记录)。 `contract_diff`:`docs/CONTRACT_DIFF/ECR-013A.yaml` --- ## L4 Acceptance Criteria ### Functional | ID | Given | When | Then | |----|-------|------|------| | AC-F-01 | super_admin 已登录 | GET `/roles` | 200,含 `super_admin` | | AC-F-02 | 角色存在 | PUT permissions 合法集合 | 200;再次 GET 一致 | | AC-F-03 | GET `/me` | — | data 含 `permissions` 数组 | ### Security | ID | Given | When | Then | |----|-------|------|------| | AC-S-01 | Admin 无 `admin.roles.write` | PUT `/roles/:id/permissions` | **403**;AuditLog 有拒绝或尝试记录 | | AC-S-02 | Admin 无 `admin.users.membership.grant` | POST membership/grant | **403** | | AC-S-03 | 仅 DeviceAuth | 访问 `/admin/roles` | **401** | | AC-S-04 | 删除 `super_admin` 角色 | 任意 API | **拒绝**(4xx) | ### Performance | ID | Given | When | Then | |----|-------|------|------| | AC-P-01 | 角色数 N≤100 | GET `/roles` | 本机 integration 环境下 P95 **< 500ms** | ### Observability | ID | Given | When | Then | |----|-------|------|------| | AC-O-01 | PUT permissions 成功 | — | AuditLog action 含 `roles.permissions.update` | | AC-O-02 | AC-S-01 触发 | — | AuditLog 可追溯 admin_id + path | --- ## Forbidden(本切片) - UserStatus / AccountLifecycle(**ECR-013B**) - 真支付 · UGC · Prompt/RAG · 兑换码 · CMS Banner 新表 - 改 DeviceAuth / C 端五 Tab --- ## Implementation Notes(实现轮才执行) | 项 | 内容 | |----|------| | Migration | YES(roles / permissions 关联;accounts.role_id;种子 super_admin) | | Packages | `service/admin` · repo · middleware permission check · admin-h5 只读展示权限(最小) | | Parent | WAVE0-FROZEN |