using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MiGu.Server.Configs; using MiGu.Server.Launcher; namespace MiGu.Server.Controllers; /// /// 配置向导 API。登录后若 deployment.Configured=false(见 LoginResponse.NeedsWizard), /// 前端进入向导: /// /// GET /api/wizard/options:可选项目录(导航方式 / 模块 / 业务场景模板)。 /// GET /api/wizard/profile:回显当前部署画像(含由导航选型推导的激活场景 id)。 /// PUT /api/wizard/profile:保存并置 Configured=true /// POST /api/wizard/reset:把 Configured 置回 false 以重新引导(保留草稿)。 /// /// 说明:保存时即把选型固化为单一事实来源 deployment section,并同步联动 Launcher —— /// WriteActiveScenesplugins/active-scenes.json / 透传 --scenes, /// 驱动 SimpleLite 下次启动选择性加载选定的导航场景插件。 /// [ApiController] [Authorize] [Route("api/wizard")] public class WizardController : ControllerBase { private readonly ConfigStore _store; private readonly SimpleLiteLauncher _launcher; private readonly ILogger _log; public WizardController(ConfigStore store, SimpleLiteLauncher launcher, ILogger log) { _store = store; _launcher = launcher; _log = log; } [HttpGet("options")] public IActionResult Options() { return Ok(new { navigationKinds = DeploymentCatalog.NavigationKinds, modules = DeploymentCatalog.Modules, scenarios = _store.Get("scenario").Payload }); } [HttpGet("profile")] public IActionResult GetProfile() => Ok(Project(_store.GetDeployment())); /// 当前部署画像对菜单的裁剪结果(供前端做面板/能力级裁剪与排查)。 [HttpGet("effective-pages")] public IActionResult EffectivePages() { var dp = _store.GetDeployment(); return Ok(new { configured = dp.Configured, tailorablePages = DeploymentCatalog.TailorablePages(), enabledPages = DeploymentCatalog.EnabledPages(dp), hiddenPages = DeploymentCatalog.HiddenPages(dp) }); } [HttpPut("profile")] public IActionResult SaveProfile([FromBody] SaveWizardRequest req) { if (req == null) return BadRequest(new { message = "请求体不能为空" }); var user = User.Identity?.Name ?? User.FindFirstValue(ClaimTypes.NameIdentifier) ?? ""; var profile = new DeploymentProfile( Configured: true, PlatformType: string.IsNullOrWhiteSpace(req.PlatformType) ? "standard" : req.PlatformType.Trim(), Modules: Clean(req.Modules), NavigationKinds: Clean(req.NavigationKinds), Scenarios: Clean(req.Scenarios), UpdatedBy: user); _store.PutDeployment(profile); // 平台 → 内核联动:把导航选型写入 SimpleLite 的 plugins/active-scenes.json(下次启动选择性加载; // 已运行实例可由前端再调 POST /api/sl/projection/scenes/apply 触发增量 reload)。 var sceneIds = profile.ToActiveSceneIds(); var write = _launcher.WriteActiveScenes(sceneIds, alwaysLoad: null, source: "deployment-profile"); _log.LogInformation("部署向导已保存 by={User} nav=[{Nav}] scenes=[{Scenes}] activeScenesWritten={Ok}", user, string.Join(",", profile.NavigationKinds), string.Join(",", sceneIds), write.Ok); return Ok(Project(profile, write)); } [HttpPost("reset")] public IActionResult Reset() { var reset = _store.GetDeployment() with { Configured = false }; _store.PutDeployment(reset); return Ok(Project(reset)); } /// /// 统一对外投影:camelCase 字段 + 推导出的 activeSceneIds(导航选型 → 内核场景 id)+ /// hiddenPages(被裁剪的菜单页)+ 可选的 activeScenesWrite(保存时写 active-scenes.json 的结果)。 /// private static object Project(DeploymentProfile dp, SimpleLiteLauncher.ActiveScenesWriteResult? write = null) => new { configured = dp.Configured, platformType = dp.PlatformType, modules = dp.Modules, navigationKinds = dp.NavigationKinds, scenarios = dp.Scenarios, updatedBy = dp.UpdatedBy, activeSceneIds = dp.ToActiveSceneIds(), hiddenPages = DeploymentCatalog.HiddenPages(dp), activeScenesWrite = write == null ? null : new { ok = write.Value.Ok, path = write.Value.Path, error = write.Value.Error } }; /// 去空白、去重(大小写不敏感)、保持顺序。 private static List Clean(List? items) { var result = new List(); if (items == null) return result; foreach (var s in items) { var t = s?.Trim(); if (!string.IsNullOrEmpty(t) && !result.Contains(t, StringComparer.OrdinalIgnoreCase)) result.Add(t!); } return result; } public record SaveWizardRequest( string? PlatformType, List? Modules, List? NavigationKinds, List? Scenarios); }