using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MiGu.Server.Auth; using MiGu.Server.Configs; namespace MiGu.Server.Controllers; /// /// RBAC 管理端:用户 / 角色 / 权限页面分配。整个控制器要求 RbacAdmin 策略 /// (JWT 的 ops claim 含 *auth.manage),即只有「超级管理员」类账号可访问。 /// /// 对应前端「平台配置中心 → 权限与角色」页(/admin/config/auth)。 /// [ApiController] [Authorize(Policy = "RbacAdmin")] [Route("api/rbac")] public class RbacController : ControllerBase { public sealed record OpDef(string Code, string Label); public sealed record WidgetDef(string Id, string Label); /// 可分配的操作码候选(管理端配置角色时下拉/勾选用)。 private static readonly OpDef[] KnownOps = { new("*", "全部操作(通配)"), new("ops.car.pause", "车辆 · 暂停"), new("ops.car.resume", "车辆 · 恢复"), new("ops.car.gohome", "车辆 · 回库"), new("ops.car.resetSession", "车辆 · 重置会话"), new("ops.car.manualCharge", "车辆 · 手动充电"), new("ops.task.pause", "任务 · 暂停"), new("ops.task.cancel", "任务 · 取消"), new("ops.task.reassign", "任务 · 改派"), new("ops.task.boostPriority", "任务 · 提升优先级"), new("monitor.note.write", "监控 · 写运营备注"), new("auth.manage", "系统 · 权限与角色管理"), }; /// 可配置可见性的控件候选。 private static readonly WidgetDef[] KnownWidgets = { new("MapEditor", "地图编辑器"), new("CadToolbar", "CAD 工具栏"), new("CarPanel", "车辆面板"), new("MissionEditor", "任务编辑器"), new("OpsActionPanel", "运维操作面板"), new("ConfigCenter", "配置中心"), }; private readonly RbacStore _store; private readonly ILogger _log; public RbacController(RbacStore store, ILogger log) { _store = store; _log = log; } /// 权限「字典」:页面清单 + 可选操作码 + 可选控件 + scope 选项。前端角色编辑器据此渲染勾选项。 [HttpGet("catalog")] public IActionResult Catalog() => Ok(new { pages = PageCatalog.All, ops = KnownOps, widgets = KnownWidgets, scopes = new[] { new { value = PageCatalog.ScopePlatform, label = "管理端 (Platform)" }, new { value = PageCatalog.ScopeMonitor, label = "运营端 (RCSMonitor)" }, new { value = PageCatalog.Wildcard, label = "通用 (全部域)" }, } }); // ───────────────────────── 角色 ───────────────────────── [HttpGet("roles")] public IActionResult ListRoles() => Ok(_store.ListRoles()); [HttpPost("roles")] public IActionResult CreateRole([FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.CreateRole(req))); [HttpPut("roles/{id}")] public IActionResult UpdateRole(string id, [FromBody] SaveRoleRequest req) => Guard(() => Ok(_store.UpdateRole(id, req))); [HttpDelete("roles/{id}")] public IActionResult DeleteRole(string id) => Guard(() => { _store.DeleteRole(id); return Ok(new { ok = true }); }); // ───────────────────────── 用户 ───────────────────────── [HttpGet("users")] public IActionResult ListUsers() => Ok(_store.ListUsers()); [HttpPost("users")] public IActionResult CreateUser([FromBody] CreateUserRequest req) => Guard(() => Ok(_store.CreateUser(req))); [HttpPut("users/{id}")] public IActionResult UpdateUser(string id, [FromBody] UpdateUserRequest req) => Guard(() => { // 自我保护:禁止把当前登录账号自己停用,避免管理员把自己锁在门外。 if (id == CurrentUserId() && req.Enabled == false) return (IActionResult)BadRequest(new { message = "不能停用当前登录的账号" }); return Ok(_store.UpdateUser(id, req)); }); [HttpPut("users/{id}/password")] public IActionResult SetPassword(string id, [FromBody] SetPasswordRequest req) => Guard(() => { _store.SetPassword(id, req.Password); return Ok(new { ok = true }); }); [HttpDelete("users/{id}")] public IActionResult DeleteUser(string id) => Guard(() => { if (id == CurrentUserId()) return (IActionResult)BadRequest(new { message = "不能删除当前登录的账号" }); _store.DeleteUser(id); return Ok(new { ok = true }); }); // ───────────────────────── 工具 ───────────────────────── /// 统一把 翻译成 400 + message,其余异常向上抛。 private IActionResult Guard(Func action) { try { return action(); } catch (RbacException ex) { return BadRequest(new { message = ex.Message }); } } private string? CurrentUserId() => User.FindFirstValue("sub") ?? User.FindFirstValue(ClaimTypes.NameIdentifier); }