Files
Migu2.0/MiGu.Server/README.md
T

329 lines
15 KiB
Markdown
Raw Normal View History

# 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()
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(库已存在则不改结构)
→ IDataMigratorApplyDataMigratorsOnStartup=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 内部 APIRV-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/.../distassets/ 与 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