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 ——
/// 调 WriteActiveScenes 写 plugins/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);
}