# MiGu.Server

> 「**迷毂 · 智能调度平台**」的后端骨架（ASP.NET Core 8 + YARP）。
> 对应 [ARCHITECTURE.md](ARCHITECTURE.md) v1.6 §1.1 中提到的「Platform 管理端」后端可执行文件。
>
> **会话 N+1（启动反转）：现在 `MiGu.Server.exe` 是主入口**。
> 用户先启动 MiGu.Server，浏览器登录页选择「启动模式」(本地+Web / 仅Web)，
> 后端 `AuthController.Login` 调用 `SimpleLiteLauncher.MaybeStart(launchMode)` 按所选模式拉起 `SimpleLite.exe`，
> 通过 `--display-mode=web|web+local` 透传给 SimpleLite 的 `Configuration.displayMode`。
>
> 工程名 `MiGu.Server` / 程序集 `MiGu.Server.dll` 保持稳定，仅用户可见前端文案改为「迷毂」。

## 克隆后首次部署速查（必读）

本仓库为 **Migu2.0**，已包含预构建的 `wwwroot/` 前端，克隆后可直接编译运行 MiGu.Server。

```pwsh
# 1) 编译 MiGu.Server
cd MiGu.Server
dotnet build MiGu.Server.csproj -c Debug

# 2) 启动（登录后按所选模式拉起 SimpleLite）
dotnet run
# 或：.\bin\Debug\net8.0\MiGu.Server.exe
```

**更新前端**：在 Simple 仓库执行 `build-platform-frontend.bat`，将 `frontends/apps/simple-platform-vue/dist/` 同步到本目录 `wwwroot/`。

## 配置 SimpleLite 路径（必读）

> **配置文件位置（无 Web 界面）：**  
> **`MiGu.Server/appsettings.json`** → 搜索 **`"SimpleLite"`** 节点。  
> 登录页选「本地 + Web / 仅 Web」后，后端在此配置的路径拉起 `SimpleLite.exe`。  
> 启动后可在浏览器打开 **`http://localhost:8080/api/health/simplelite`** 查看路径是否解析成功。

登录成功后，MiGu.Server 会按所选启动模式拉起 **SimpleLite.exe**。默认已在 `appsettings.json` 写好与 Simple 仓库并列的相对路径；开发机可用 `appsettings.Development.json` 覆盖为绝对路径。

### 本机开发（与 Simple 仓库并列）

仓库默认已在 `appsettings.Development.json` 中写好：

```json
"SimpleLite": {
  "Enabled": true,
  "ExecutablePath": "E:\\Work\\Core\\Simple-FR\\Simple\\SimpleLite\\bin\\Debug\\SimpleLite.exe",
  "WorkingDirectory": "E:\\Work\\Core\\Simple-FR\\Simple\\SimpleLite\\bin\\Debug",
  "ProjectionPort": 8222,
  "ReadinessTimeoutMs": 8000,
  "FollowParent": false
}
```

请先编译 SimpleLite（在 Simple 仓库）：

```pwsh
cd ..\Simple
dotnet build SimpleLite\SimpleLite.csproj -c Debug
```

若你的 Simple 不在上述绝对路径，可改为**相对路径**（相对 `MiGu.Server` 工作目录）：

```json
"ExecutablePath": "..\\..\\Simple\\SimpleLite\\bin\\Debug\\SimpleLite.exe",
"WorkingDirectory": "..\\..\\Simple\\SimpleLite\\bin\\Debug"
```

### 配置项说明

| 键 | 含义 |
|----|------|
| `Enabled` | `false` 时永不拉起 SimpleLite（只调试平台 UI） |
| `ExecutablePath` | `SimpleLite.exe` 绝对或相对路径；留空则自动探测（见 `Launcher/SimpleLiteOptions.cs`） |
| `WorkingDirectory` | 子进程工作目录；留空则用 exe 所在目录（读写 `simple.json` / `imgui.ini`） |
| `ProjectionPort` | 就绪检测端口，默认 `8222` |
| `ReadinessTimeoutMs` | 登录后等待 SimpleLite WebAPI 就绪的最长时间（毫秒） |
| `FollowParent` | `true` 时 MiGu.Server 退出会结束 SimpleLite；默认 `false`（两进程独立） |

### 环境变量覆盖

```pwsh
$env:SimpleLite__ExecutablePath = "D:\apps\SimpleLite.exe"
$env:SimpleLite__WorkingDirectory = "D:\apps"
$env:SimpleLite__Enabled = "true"
```

### 生产 / 合并发布

将 `SimpleLite.exe` 与依赖 DLL 放到 `MiGu.Server` 同目录，并清空 `ExecutablePath`（走自动探测 `./SimpleLite.exe`），或在 `appsettings.Production.json` 写死部署路径。

浏览器访问 `http://localhost:8080/login`：
1. 用户名 / 密码（`appsettings.json:Auth.Users` 默认 admin/admin、ops/ops）；
2. 选 scope（管理员 / 运营）；
3. 选 **SimpleLite 启动模式**：
   - **本地 + Web** → 透传 `--display-mode=web+local`，桌面 ImGui 窗口 + 浏览器同时启动；
   - **仅 Web** → 透传 `--display-mode=web`，只起 WebTerminal，不弹本地窗口；
4. 点登录。`AuthController.Login` 调用 `SimpleLiteLauncher.MaybeStart(...)` 阻塞等 Projection (`:8222`) 就绪后返回 LoginResponse。

> 如果只想跑 MiGu.Server 单进程调试（不拉 SimpleLite），把 `appsettings.json:SimpleLite.Enabled` 改为 `false` 即可。

## 数据库启动流程

持久层在独立工程 **[MiGu.DB](../MiGu.DB/README.md)**；Server 通过 `PlatformPersistence` 接入。

### 启动顺序（`Program.cs`）

```text
AddPlatformPersistence(configuration)     # 注册 AddMiGuDb + Wms/SimpleFields 等业务服务
        │
        ▼
builder.Build()
        │
        ▼
EnsurePlatformDatabaseAsync()             # → MiGu.DB.MigrateMiGuDbAsync()
        │                                 #    Schema 初始化 + 可选 IDataMigrator
        ▼
… 其余中间件 …
UseMiddleware<HttpActorContextMiddleware> # 请求级写入 IActorContext（审计戳）
```

### 配置（`appsettings*.json`）

```json
"Database": {
  "Provider": "sqlite",
  "SchemaMode": "Migrate",
  "ApplyDataMigratorsOnStartup": true
},
"ConnectionStrings": {
  "Platform": "Data Source=data/platform.db"
}
```

| 配置项 | 含义 | 取值 |
|--------|------|------|
| `Provider` | 数据库种类 | `sqlite`（默认）/ `mysql` / `npgsql` / `sqlserver` |
| `SchemaMode` | 启动时如何初始化 Schema | 见下表 |
| `ApplyDataMigratorsOnStartup` | Schema 完成后是否跑数据修补 | `true`（默认）/ `false` |

当前仓库约定：

- `appsettings.json` → `"SchemaMode": "Migrate"`（发版/默认）
- `appsettings.Development.json` → `"SchemaMode": "EnsureCreated"`（本地开发）

也可用环境变量覆盖，例如：`$env:Database__SchemaMode = "Migrate"`。

### SchemaMode 行为

| 值 | 行为 | 适用 |
|----|------|------|
| **`Migrate`** | 旧库无 `__EFMigrationsHistory` 时先写 Initial 基线 → `MigrateAsync` →（可选）DataMigrator | 测试 / 预发 / 生产 |
| **`EnsureCreated`** | 仅 `EnsureCreated`（按**当前模型**建空库），**不**跑 Migrations →（可选）DataMigrator | 开发期改实体、暂不生成迁移 |

流程图：

```text
SchemaMode = EnsureCreated
  → EnsureCreatedAsync（库已存在则不改结构）
  → IDataMigrator（ApplyDataMigratorsOnStartup=true 时）

SchemaMode = Migrate
  → 程序集无 Migration → EnsureCreated 兜底
  → 否则：基线（如需）→ MigrateAsync
  → IDataMigrator（可选）
```

**开发注意（EnsureCreated）：**

- 改实体后结构不会自动升级；请删除 `MiGu.Server/data/platform.db*` 再启动。
- 不会写入迁移历史；以后改回 `Migrate` 时，对已有库会走基线再应用 Migrations。

**发版注意：**

- 现场务必使用 `Migrate`，并随包带上 `MiGu.DB/Migrations/Sqlite/`。
- 开发期若长期 `EnsureCreated` 改模型，发版前必须对**当前模型**执行 `dotnet ef migrations add`，再切回 `Migrate` 验证；否则空库只会落到旧 Migration，结构落后于代码。
- 从 EnsureCreated 库切到 `Migrate`：若无 History，启动会把**全部** pending Migration 写入基线（假定库已对齐模型 tip）。结构不对齐时应删库重建或手工处理。

### 相关文档

- 持久层总览与 SchemaMode 摘要：[MiGu.DB/README.md](../MiGu.DB/README.md)
- 如何生成/管理 Migration：[MiGu.DB/MIGRATIONS.md](../MiGu.DB/MIGRATIONS.md)

## 安全须知（生产部署必读）

> **重要：以下默认值仅供本地开发，切勿原样用于生产环境。**
>
> - `appsettings.json:Auth.Users` 内置 `admin/admin`、`ops/ops` 为弱口令演示账号；
> - `appsettings.json:Jwt.Secret`、`Internal.Token` 为占位符 `REPLACE_ME`。
>
> 生产部署务必通过**环境变量**或 `appsettings.Production.json` / 密钥管理覆盖
> （ASP.NET Core 配置优先级：环境变量 > `appsettings.{Environment}.json` > `appsettings.json`）。
> 环境变量示例（`__` 双下划线表示配置层级）：
>
> ```pwsh
> $env:Auth__Users__admin__Password = "<强密码>"
> $env:Jwt__Secret      = "<不少于 32 字节的随机串>"
> $env:Internal__Token  = "<随机串>"
> ```
>
> 详见代码审核 ISSUE-03（`Doc/CODE_REVIEW_ISSUES_2026-05-29.md`）。
>
> **SimpleLite 8222 内部 API（RV-04）**：默认 `simple.json:platform.allowLoopbackBypass=true`，本机回环请求免 token；**多租户 / 共享主机的生产环境建议设为 `false`**，强制所有请求携带 `X-Platform-Internal-Token`（配合 `platform.internalToken` 或 `MiGu.Server/data/.internal-token`）。详见复审 `Doc/CODE_REVIEW_WEEK_2026-05-29.md` RV-04。

## 能力清单（本轮）

- Kestrel 监听 `:8080`（HTTP）；
- 静态托管：启动时自动探测 WebRoot ——
  - 开发模式：仓库内 `frontends/apps/simple-platform-vue/dist/` 存在则直接挂为 WebRoot（`pnpm build` 后立即生效，无需 robocopy 到 wwwroot）；
  - 兜底/部署模式：找不到 dist 时回退到 `wwwroot/`（由 `build-platform-frontend.bat` 的 robocopy /MIR 填充）；
  - SPA fallback 让 `/admin/*` `/monitor/*` `/login` `/status` 等所有非 API 路径都回退到 `index.html`，由 vue-router 接管；
- YARP 反向代理：
  - `/api/sl/{**catch-all}` → `http://127.0.0.1:8222/`（SimpleLite WebAPI；当前 SimpleLite 未启 WebAPI 时返回 502）；
  - `/vr/{**catch-all}` → `http://127.0.0.1:8223/`（webVRender；可用于同源 iframe 解决 X-Frame-Options 限制）；
- 14 维度配置中心占位（对齐 §9）：内存 + `data/config-{section}.json` 持久化；
- Mock 鉴权：`/api/auth/login` 返回 Mock JWT + `EffectivePermissions`；LoginRequest 新增 `launchMode: "WebOnly" | "DesktopAndWeb"` 字段，登录成功后 `SimpleLiteLauncher` 据此拉起子进程；
- SimpleLite 子进程编排：`Launcher/SimpleLiteLauncher.cs` 幂等 / Stdout 转发 / Windows JobObject 父子绑定 / 端口就绪等待，配置见 `appsettings.json:SimpleLite`；
- 运维白名单网关占位：`/api/sl/ops/execute`、`/api/sl/ops/audits`；
- 投影 API 占位：`/api/projection/{sites,tracks,cars,missions}`；
- 健康检查：`/api/health`；
- Swagger：开发环境下 `/swagger`。

> 能力补充：平台库由 **MiGu.DB**（EF Core）承载，启动流程见「[数据库启动流程](#数据库启动流程)」。

## 目录结构

```
MiGu.Server/
├── MiGu.Server.csproj
├── Program.cs                     # Kestrel + YARP + CORS + StaticFiles + SPA fallback
├── appsettings.json               # YARP 路由 + CORS 白名单
├── appsettings.Development.json
├── Properties/launchSettings.json
├── Controllers/
│   ├── AuthController.cs          # /api/auth/login | /api/auth/logout
│   ├── ConfigController.cs        # GET/PUT /api/config[/{section}]
│   ├── ProjectionController.cs    # /api/projection/{sites,tracks,cars,missions}
│   ├── OpsController.cs           # /api/sl/ops/{execute,audits}
│   └── HealthController.cs        # /api/health
├── Configs/                       # 与 ARCHITECTURE.md §9 一一对齐的强类型 record
│   ├── SystemConfig.cs
│   ├── ExternalIntegrations.cs
│   ├── RoutingPolicy.cs
│   ├── VehicleMaintenancePolicy.cs
│   ├── ChargePolicy.cs
│   ├── TaskAllocationPolicy.cs
│   ├── TrafficRule.cs
│   ├── DeviceManagementConfig.cs
│   ├── FleetLifecycleConfig.cs
│   ├── ScenarioTemplateConfig.cs
│   ├── LocationManagement.cs
│   ├── OpsConfig.cs
│   ├── CustomWidget.cs
│   ├── EffectivePermissions.cs
│   └── ConfigStore.cs             # 内存 + JSON 文件持久化（占位）
├── data/                          # 运行时生成的 config-{section}.json
└── wwwroot/                       # 部署兜底（dev 模式优先用 frontends/.../dist；assets/ 与 index.html 已 ignore）
```

## 独立启动（仅当你不通过 SimpleLite 自启时）

```pwsh
# 1) 还原 + 编译 + 运行
cd MiGu.Server
dotnet run

# 等价的 build + run 一键脚本：
.\build-and-run.bat            # Debug
.\build-and-run.bat release    # Release

# 2) 验证
#    打开 http://localhost:8080/                欢迎页 / 平台前端
#    打开 http://localhost:8080/swagger         API 文档
#    打开 http://localhost:8080/api/health      健康检查
#    打开 http://localhost:8080/api/config/system 系统级配置
```

或在 IDE 中以 `MiGu.Server` 作为启动项目。

> 启动时控制台会打印 `[MiGu.Server] WebRoot -> dist: ...` 或 `WebRoot -> wwwroot ...`，提示当前使用哪一份产物。
> 单独启动且既没有跑过 `pnpm build` 也没有跑过 `build-platform-frontend.bat` 时，会看到 SPA fallback 找不到 `index.html`。
> 仓库根 `build-platform-frontend.bat` 仍可用于「打部署包」场景（把 dist robocopy /MIR 同步到 wwwroot）。
> SimpleLite 自启 MiGu.Server 时，Program.cs 会从 `bin\<Config>\net8.0\` 沿目录向上找 dist；
> 找到则使用源码区 dist，找不到则使用 bin 同级的 wwwroot——两种启动方式共用同一份前端产物。

## 与前端联调

- 开发模式（前端在 Simple 仓库）：
  - 前端：`cd ../Simple/frontends && pnpm dev`（`:5173`），Vite 代理 `/api` 至 `http://127.0.0.1:8080`；
  - 后端：`cd MiGu.Server && dotnet run`（`:8080`）；
  - 浏览器访问 `http://localhost:5173/login`。
- 生产/同源模式（本仓库 `wwwroot/`）：
  - 默认使用 `MiGu.Server/wwwroot/`（已随仓库提交构建产物）；
  - 若上级目录存在 `frontends/apps/simple-platform-vue/dist/`，Program.cs 会优先挂 dist；
  - 浏览器访问 `http://localhost:8080/login` 等路径由 vue-router 解析。

## YARP 与 SimpleLite

YARP 路由配置见 `appsettings.json`：

```json
{
  "ReverseProxy": {
    "Routes": {
      "sl-route":      { "Match": { "Path": "/api/sl/{**catch-all}" }, "ClusterId": "sl-cluster" },
      "vrender-route": { "Match": { "Path": "/vr/{**catch-all}" },      "ClusterId": "vrender-cluster" }
    },
    "Clusters": {
      "sl-cluster":      { "Destinations": { "sl1": { "Address": "http://127.0.0.1:8222/" } } },
      "vrender-cluster": { "Destinations": { "vr1": { "Address": "http://127.0.0.1:8223/" } } }
    }
  }
}
```

- `/api/sl/ops/{execute,audits}` 在本工程被 `OpsController` 显式接住（更具体的路由），用于占位测试；
- 其他 `/api/sl/*` 由 YARP 透传到 SimpleLite `:8222`，SimpleLite 未启时会得到 502/连接被拒。

## iframe 嵌入 webVRender 的两种方式

1. **直连**（默认）：前端 iframe 指向 `http://localhost:8223/?scope=...&token=...&ro=...`，跨源；
2. **同源代理**（可选）：iframe 指向 `http://localhost:8080/vr/?scope=...`，由本工程 YARP 反代到 8223，
   可规避 X-Frame-Options 等限制。前端可通过 `Workspace3D` 组件的 `host` prop 传 `localhost:8080/vr` 切换。

## 相关文档

- [ARCHITECTURE.md](ARCHITECTURE.md) — v1.6 总体架构（§4 进程拓扑、§6.3 YARP 配置、§9 配置中心、§10 交互序列、§17 视觉规范）
- [（见 Simple 仓库）frontends/README.md](（见 Simple 仓库）frontends/README.md) — 前端启动与联调说明（含「迷毂」品牌与紫色主题约定）
- [MiGu.DB/README.md](../MiGu.DB/README.md) — 持久层框架；[MIGRATIONS.md](../MiGu.DB/MIGRATIONS.md) — 生成 Migration 手册
- 本文「[数据库启动流程](#数据库启动流程)」— SchemaMode / EnsureCreated / Migrate
