2026-06-03 09:37:07 +08:00
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using MiGu.Server.Configs;
|
|
|
|
|
|
using MiGu.Server.Launcher;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MiGu.Server.Controllers;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置向导 API。登录后若 <c>deployment.Configured=false</c>(见 <c>LoginResponse.NeedsWizard</c>),
|
|
|
|
|
|
/// 前端进入向导:
|
|
|
|
|
|
/// <list type="bullet">
|
|
|
|
|
|
/// <item><c>GET /api/wizard/options</c>:可选项目录(导航方式 / 模块 / 业务场景模板)。</item>
|
|
|
|
|
|
/// <item><c>GET /api/wizard/profile</c>:回显当前部署画像(含由导航选型推导的激活场景 id)。</item>
|
2026-06-12 23:00:47 +08:00
|
|
|
|
/// <item><c>PUT /api/wizard/profile</c>:保存并置 <c>Configured=true</c>(仅 Platform scope)。</item>
|
|
|
|
|
|
/// <item><c>POST /api/wizard/reset</c>:把 <c>Configured</c> 置回 false 以重新引导(仅 Platform scope)。</item>
|
2026-06-03 09:37:07 +08:00
|
|
|
|
/// </list>
|
|
|
|
|
|
/// 说明:保存时即把选型固化为单一事实来源 <c>deployment</c> section,并同步联动 Launcher ——
|
|
|
|
|
|
/// <see cref="SaveProfile"/> 调 <c>WriteActiveScenes</c> 写 <c>plugins/active-scenes.json</c> / 透传 <c>--scenes</c>,
|
|
|
|
|
|
/// 驱动 SimpleLite 下次启动选择性加载选定的导航场景插件。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[ApiController]
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
|
[Route("api/wizard")]
|
|
|
|
|
|
public class WizardController : ControllerBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ConfigStore _store;
|
|
|
|
|
|
private readonly SimpleLiteLauncher _launcher;
|
|
|
|
|
|
private readonly ILogger<WizardController> _log;
|
|
|
|
|
|
|
|
|
|
|
|
public WizardController(ConfigStore store, SimpleLiteLauncher launcher, ILogger<WizardController> 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()));
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前部署画像对菜单的裁剪结果(供前端做面板/能力级裁剪与排查)。</summary>
|
|
|
|
|
|
[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")]
|
2026-06-12 23:00:47 +08:00
|
|
|
|
[Authorize(Policy = "PlatformScope")]
|
2026-06-03 09:37:07 +08:00
|
|
|
|
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)。
|
2026-06-23 13:47:28 +08:00
|
|
|
|
// scene.device(门/充电桩/按钮盒驱动)为各类项目通用能力,向导暂无独立选项,固定并入激活集合;
|
2026-08-24 16:16:58 +08:00
|
|
|
|
// 磁导航选型时 ToLauncherSceneIds 会并入 scene.signal(PLC 握手 + 磁条交管)。
|
|
|
|
|
|
var sceneIds = profile.ToLauncherSceneIds();
|
2026-06-03 09:37:07 +08:00
|
|
|
|
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")]
|
2026-06-12 23:00:47 +08:00
|
|
|
|
[Authorize(Policy = "PlatformScope")]
|
2026-06-03 09:37:07 +08:00
|
|
|
|
public IActionResult Reset()
|
|
|
|
|
|
{
|
|
|
|
|
|
var reset = _store.GetDeployment() with { Configured = false };
|
|
|
|
|
|
_store.PutDeployment(reset);
|
|
|
|
|
|
return Ok(Project(reset));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 统一对外投影:camelCase 字段 + 推导出的 activeSceneIds(导航选型 → 内核场景 id)+
|
|
|
|
|
|
/// hiddenPages(被裁剪的菜单页)+ 可选的 activeScenesWrite(保存时写 active-scenes.json 的结果)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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,
|
2026-08-24 16:16:58 +08:00
|
|
|
|
activeSceneIds = dp.ToLauncherSceneIds(),
|
2026-06-03 09:37:07 +08:00
|
|
|
|
hiddenPages = DeploymentCatalog.HiddenPages(dp),
|
|
|
|
|
|
activeScenesWrite = write == null ? null : new
|
|
|
|
|
|
{
|
|
|
|
|
|
ok = write.Value.Ok,
|
|
|
|
|
|
path = write.Value.Path,
|
|
|
|
|
|
error = write.Value.Error
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>去空白、去重(大小写不敏感)、保持顺序。</summary>
|
|
|
|
|
|
private static List<string> Clean(List<string>? items)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new List<string>();
|
|
|
|
|
|
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<string>? Modules,
|
|
|
|
|
|
List<string>? NavigationKinds,
|
|
|
|
|
|
List<string>? Scenarios);
|
|
|
|
|
|
}
|