将调度内核标识从 SimpleLite 全面重命名为 Simple3。
配置段/环境变量、Launcher、健康检查 API、OpenAPI 与前后端文案同步;兼容探测旧 SimpleLite 进程名。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+116
-116
@@ -1,10 +1,10 @@
|
||||
# SimpleLite 三端系统架构设计
|
||||
# Simple3 三端系统架构设计
|
||||
|
||||
> **Migu2.0 仓库说明**:平台后端已拆至本仓库,工程名为 **`MiGu.Server`**(原 `Platform.Server`)。SimpleLite 与前端源码仍在兄弟仓库 `Simple`。下文中的 `MiGu.Server` 即指本仓库 `MiGu.Server/` 目录。
|
||||
> **Migu2.0 仓库说明**:平台后端已拆至本仓库,工程名为 **`MiGu.Server`**(原 `Platform.Server`)。Simple3 与前端源码仍在兄弟仓库 `Simple`。下文中的 `MiGu.Server` 即指本仓库 `MiGu.Server/` 目录。
|
||||
|
||||
> 文档版本:v1.7.0
|
||||
> 更新日期:2026-05-20
|
||||
> 范围:SimpleLite(主程序 / 设计端 / 调度引擎宿主 / 进程编排器)+ Platform(业务平台 · 同时承载管理员前端 platform-vue 与运营前端 rcsmonitor-vue · 共用一套 WebAPI · **主入口为 MiGu.Server,登录后拉起 SimpleLite**)
|
||||
> 范围:Simple3(主程序 / 设计端 / 调度引擎宿主 / 进程编排器)+ Platform(业务平台 · 同时承载管理员前端 platform-vue 与运营前端 rcsmonitor-vue · 共用一套 WebAPI · **主入口为 MiGu.Server,登录后拉起 Simple3**)
|
||||
|
||||
> v1.7.0 变更(多主题色板 · 工业紫默认):
|
||||
> - **6 套可切换主题**:`themes.ts` 定义色板(工业紫、深邃蓝、翡翠绿、熔铁赤、石墨钢、琥珀金);默认 **工业紫色** `#6a1b9a`(比 v1.6.2 的 `#641393` 更深更饱和,解决登录后内容区配色过淡)。
|
||||
@@ -37,13 +37,13 @@
|
||||
> - 新增 §7.4–§7.7 四类 DB 部署形态详述、选型矩阵、连接串与配置示例、迁移注意事项。
|
||||
|
||||
> v1.4 变更(保留):
|
||||
> - **明确进程编排归属**:Platform 后端进程 `MiGu.Server.exe` 不再被视为"独立的业务平台服务",而是 SimpleLite 内置 `SystemMission`(`StartPlatformMission`)拉起、被 SimpleLite Bootstrapper 守护的**受控子进程**;RCSMonitor 不存在独立后端进程,复用 MiGu.Server。
|
||||
> - **明确进程编排归属**:Platform 后端进程 `MiGu.Server.exe` 不再被视为"独立的业务平台服务",而是 Simple3 内置 `SystemMission`(`StartPlatformMission`)拉起、被 Simple3 Bootstrapper 守护的**受控子进程**;RCSMonitor 不存在独立后端进程,复用 MiGu.Server。
|
||||
> - **整体定位章节重写**:明确"两类后端可执行文件 + 一个进程编排者"的关系,避免读者误以为 Platform 是用 Windows Service / systemd 独立部署的服务。
|
||||
|
||||
> v1.3 变更(保留):
|
||||
> 1. **取消 RCSMonitor.Server**:`rcsmonitor-vue` 不再有独立后端进程,直接连 MiGu.Server;由 JWT 中的 `scope=RCSMonitor` + 角色权限码 + `WidgetGrant` 三层限制可见控件与可调用 API。
|
||||
> 2. **3D 渲染基座定为 webVRender**:复用 SimpleLite WebTerminal 已经提供的 webVRender,以 Web Component / iframe 嵌入到 platform-vue 与 rcsmonitor-vue,两端共享同一份 Workspace 协议(PutModel / PutPointCloud / SetCamera 等 DTO)。
|
||||
> 3. **反向代理选定 YARP**:MiGu.Server 内置 [YARP](https://microsoft.github.io/reverse-proxy/) 做 `/api/sl/*` → SimpleLite WebAPI 的反代;带鉴权与审计中间件。
|
||||
> 2. **3D 渲染基座定为 webVRender**:复用 Simple3 WebTerminal 已经提供的 webVRender,以 Web Component / iframe 嵌入到 platform-vue 与 rcsmonitor-vue,两端共享同一份 Workspace 协议(PutModel / PutPointCloud / SetCamera 等 DTO)。
|
||||
> 3. **反向代理选定 YARP**:MiGu.Server 内置 [YARP](https://microsoft.github.io/reverse-proxy/) 做 `/api/sl/*` → Simple3 WebAPI 的反代;带鉴权与审计中间件。
|
||||
> 4. **运行模式持久化到 `simple.json`**:登录窗"记住模式"勾选后写入 `simple.json.runMode`;下次启动直接进入。
|
||||
> 5. **Web-Enabled 下严禁同时本地操作**:CycleGUI 主窗只剩"服务状态窗",所有业务面板硬性隐藏;不提供"临时启用本地 UI"开关。
|
||||
> 6. **共享前端组件库**:使用 pnpm workspace + `frontends/packages/sl-controls`,封装 Workspace3D / 地图 / 任务 / CAD 等可复用 Vue 组件。
|
||||
@@ -79,43 +79,43 @@
|
||||
|
||||
一套为 AGV/AMR 现场打造的「**设计 → 调度 → 运营监控**」三端协同系统。
|
||||
|
||||
**三个"端"对应三套用户体验,但只有两类后端可执行文件,且都由 SimpleLite 统一编排:**
|
||||
**三个"端"对应三套用户体验,但只有两类后端可执行文件,且都由 Simple3 统一编排:**
|
||||
|
||||
| 端 | 前端载体 | 后端可执行文件 | **启动者** | 角色 | 写权限 |
|
||||
|----|----------|----------------|------------|------|--------|
|
||||
| **SimpleLite 桌面端** | CycleGUI 客户端 | `SimpleLite.exe` 自身 | 用户双击 / 服务管理器 | 主程序 + 调度引擎宿主 + 全功能 WebAPI 提供者 + **进程编排器(Bootstrapper + SystemMission)** | **全功能** |
|
||||
| **Platform 管理端** | `platform-vue` (Vue 3 SPA) | `MiGu.Server.exe` | **SimpleLite 内置 `SystemMission.StartPlatformMission` 拉起**(Web-Enabled 时自动 spawn;崩溃由 SimpleLite Watchdog 重启) | **SimpleLite 全能力套壳**:管理员侧 Vue 前端复刻所有设计/调度控件 + 业务平台特有能力(配置中心 / 外部对接 / 库位 / 账号) | **全功能**(等价 SimpleLite) |
|
||||
| **RCSMonitor 运营端** | `rcsmonitor-vue` (Vue 3 SPA) | **共用 `MiGu.Server.exe`**(无独立后端进程) | 随 MiGu.Server 一起被 SimpleLite 拉起 | 运营监控前端:按 `scope=RCSMonitor` + 角色权限 + WidgetGrant 三层裁剪 UI;可对任务/车辆执行白名单运维动作 | **受限**(仅运维白名单) |
|
||||
| **Simple3 桌面端** | CycleGUI 客户端 | `Simple3.exe` 自身 | 用户双击 / 服务管理器 | 主程序 + 调度引擎宿主 + 全功能 WebAPI 提供者 + **进程编排器(Bootstrapper + SystemMission)** | **全功能** |
|
||||
| **Platform 管理端** | `platform-vue` (Vue 3 SPA) | `MiGu.Server.exe` | **Simple3 内置 `SystemMission.StartPlatformMission` 拉起**(Web-Enabled 时自动 spawn;崩溃由 Simple3 Watchdog 重启) | **Simple3 全能力套壳**:管理员侧 Vue 前端复刻所有设计/调度控件 + 业务平台特有能力(配置中心 / 外部对接 / 库位 / 账号) | **全功能**(等价 Simple3) |
|
||||
| **RCSMonitor 运营端** | `rcsmonitor-vue` (Vue 3 SPA) | **共用 `MiGu.Server.exe`**(无独立后端进程) | 随 MiGu.Server 一起被 Simple3 拉起 | 运营监控前端:按 `scope=RCSMonitor` + 角色权限 + WidgetGrant 三层裁剪 UI;可对任务/车辆执行白名单运维动作 | **受限**(仅运维白名单) |
|
||||
|
||||
**关键澄清(v1.4 重点):**
|
||||
|
||||
- **后端可执行文件只有 2 个**:`SimpleLite.exe`、`MiGu.Server.exe`。
|
||||
- **进程启动只有 1 个真正的入口**:用户/服务管理器只启动 `SimpleLite.exe`,MiGu.Server 由 SimpleLite 自己拉起,**不需要也不允许独立安装为 Windows Service**。
|
||||
- **进程编排归属 SimpleLite**:`SystemMission` 是 SimpleLite 自带的 Mission 体系扩展(与业务 Mission 区分开),专门负责拉起/守护/优雅停止 MiGu.Server。Bootstrapper 监听心跳、按指数退避重启、统一记录 OpsAuditLog。
|
||||
- **后端可执行文件只有 2 个**:`Simple3.exe`、`MiGu.Server.exe`。
|
||||
- **进程启动只有 1 个真正的入口**:用户/服务管理器只启动 `Simple3.exe`,MiGu.Server 由 Simple3 自己拉起,**不需要也不允许独立安装为 Windows Service**。
|
||||
- **进程编排归属 Simple3**:`SystemMission` 是 Simple3 自带的 Mission 体系扩展(与业务 Mission 区分开),专门负责拉起/守护/优雅停止 MiGu.Server。Bootstrapper 监听心跳、按指数退避重启、统一记录 OpsAuditLog。
|
||||
- **Vue 两份产物,同一进程托管**:MiGu.Server 在 `/admin/*` 路径下托管 `platform-vue`,在 `/monitor/*` 路径下托管 `rcsmonitor-vue`。
|
||||
- **鉴权与限权完全在 MiGu.Server 完成**:RCSMonitor 不持有任何独立后端代码(没有 `RCSMonitor.Server.exe`、没有独立库)。
|
||||
- **Desktop-Only 模式下,SystemMission 不触发**:MiGu.Server 根本不会启动,Vue 端无法访问。
|
||||
|
||||
> 一句话:**用户点 SimpleLite.exe,由 SimpleLite 通过 Mission 把 Platform 后端拉起来;Platform 后端再把两套 Vue 端服好。**
|
||||
> 一句话:**用户点 Simple3.exe,由 Simple3 通过 Mission 把 Platform 后端拉起来;Platform 后端再把两套 Vue 端服好。**
|
||||
|
||||
### 1.2 核心拆分原则
|
||||
|
||||
1. **Crash-Isolation(搞不崩主服务)**
|
||||
Platform / RCSMonitor 对**地图/任务/车辆等写操作**都通过 SimpleLite 的 `/api/*` 网关;主服务侧做白名单 + 幂等校验 + 频次限流 + 审计。Platform-Vue 走完整 API 集,RCSMonitor-Vue 仅限运维白名单 `/api/ops/*`。
|
||||
Platform / RCSMonitor 对**地图/任务/车辆等写操作**都通过 Simple3 的 `/api/*` 网关;主服务侧做白名单 + 幂等校验 + 频次限流 + 审计。Platform-Vue 走完整 API 集,RCSMonitor-Vue 仅限运维白名单 `/api/ops/*`。
|
||||
|
||||
2. **共享数据模型,独立运行时**
|
||||
地图、车辆、任务三端共用 `SimpleCore` 的 DTO;调度引擎只在 SimpleLite 里跑,Platform 只持有"投影"。
|
||||
地图、车辆、任务三端共用 `SimpleCore` 的 DTO;调度引擎只在 Simple3 里跑,Platform 只持有"投影"。
|
||||
|
||||
3. **库分离 + Provider 抽象**(详见 §7)
|
||||
- `simple_main` 由 SimpleLite 持有;`platform` 由 Platform 持有(含 RCSMonitor 审计/标注);不再有独立的 `rcsmonitor_local`。
|
||||
- `simple_main` 由 Simple3 持有;`platform` 由 Platform 持有(含 RCSMonitor 审计/标注);不再有独立的 `rcsmonitor_local`。
|
||||
- 持久层基于 **EF Core**,**通过 Provider 切换底层数据库**:默认 **SQLite**(边缘单机,配 ROSE HA);可选 **MySQL / PostgreSQL / SQL Server**,匹配现场已有 IT 基础设施与企业级 HA 能力(详见 §7.4–§7.7)。
|
||||
- 高可用方案随 Provider 不同:SQLite 用 ROSE HA;MySQL 用 InnoDB Cluster;PostgreSQL 用 Patroni;SQL Server 用 Always On AG。
|
||||
|
||||
4. **统一协议层**
|
||||
所有进程间调用统一 **WebAPI(REST/JSON)+ WebSocket 事件流**;Vue 端直接对接 Platform 后端的 REST + WS;Platform 后端用 **YARP** 反代 SimpleLite 的设计/调度 API。
|
||||
所有进程间调用统一 **WebAPI(REST/JSON)+ WebSocket 事件流**;Vue 端直接对接 Platform 后端的 REST + WS;Platform 后端用 **YARP** 反代 Simple3 的设计/调度 API。
|
||||
|
||||
5. **统一身份**
|
||||
`SimpleShared.Auth` 提供 JWT + RBAC;Platform 是主权限源(在线时),SimpleLite 在 Desktop-Only 下用本地用户表降级。
|
||||
`SimpleShared.Auth` 提供 JWT + RBAC;Platform 是主权限源(在线时),Simple3 在 Desktop-Only 下用本地用户表降级。
|
||||
|
||||
6. **运行模式互斥(强约束)**
|
||||
- **Desktop-Only**:CycleGUI 客户端独占;WebAPI / Platform **不启动**。
|
||||
@@ -139,7 +139,7 @@ flowchart TB
|
||||
end
|
||||
|
||||
subgraph Host["主控宿主机 / 边缘服务器"]
|
||||
subgraph SL["SimpleLite 主进程 (CycleGUI + ASP.NET Core 8)"]
|
||||
subgraph SL["Simple3 主进程 (CycleGUI + ASP.NET Core 8)"]
|
||||
direction TB
|
||||
SL_Login["启动登录窗<br/>(模式选择 Desktop / Web)"]
|
||||
SL_UI["CycleGUI 客户端<br/>(Desktop-Only: 全功能<br/>Web-Enabled: 仅状态窗)"]
|
||||
@@ -155,7 +155,7 @@ flowchart TB
|
||||
direction TB
|
||||
PL_Static["静态资源<br/>/admin/* → platform-vue dist<br/>/monitor/* → rcsmonitor-vue dist"]
|
||||
PL_API["Platform WebAPI<br/>(配置中心 / 外部对接 / 库位 / 账号)"]
|
||||
PL_YARP["YARP 反代<br/>/api/sl/* → SimpleLite WebAPI<br/>+ Auth/审计中间件"]
|
||||
PL_YARP["YARP 反代<br/>/api/sl/* → Simple3 WebAPI<br/>+ Auth/审计中间件"]
|
||||
PL_Auth["SimpleAuth<br/>(JWT 签发 + Scope 校验)"]
|
||||
PL_Adp["外部系统适配器<br/>(MES / WMS / RCS)"]
|
||||
PL_DB[("SQLite: platform.db<br/>WAL")]
|
||||
@@ -203,11 +203,11 @@ flowchart TB
|
||||
|
||||
**要点:**
|
||||
|
||||
- **只有两个后端可执行文件**:`SimpleLite.exe` 与 `MiGu.Server.exe`;RCSMonitor 不再有独立后端。
|
||||
- **`MiGu.Server.exe` 由 SimpleLite 的 SystemMission 拉起**:用户只启动 SimpleLite;Web-Enabled 时 `StartPlatformMission` 自动 spawn MiGu.Server,子进程心跳异常由 Bootstrapper 重启。
|
||||
- **只有两个后端可执行文件**:`Simple3.exe` 与 `MiGu.Server.exe`;RCSMonitor 不再有独立后端。
|
||||
- **`MiGu.Server.exe` 由 Simple3 的 SystemMission 拉起**:用户只启动 Simple3;Web-Enabled 时 `StartPlatformMission` 自动 spawn MiGu.Server,子进程心跳异常由 Bootstrapper 重启。
|
||||
- **`PL_Static` 同一进程双 SPA 托管**:浏览器访问路径决定加载哪份 dist,登录时也按路径决定 `scope` 参数。
|
||||
- **`PL_YARP` 把 `/api/sl/*` 反代到 SimpleLite**:YARP 在请求转发前后挂中间件做 JWT 校验、权限码二次确认、写操作审计、`Idempotency-Key` 透传、Scope 检查(RCSMonitor 的请求仅允许命中 `/api/ops/*` 这一前缀)。
|
||||
- **3D 由 SimpleLite 的 webVRender 提供**:Vue 端通过 iframe 或 Web Component 嵌入 webVRender 的页面,业务交互(选择、点击坐标等)通过 `postMessage` 与 Vue 桥接。
|
||||
- **`PL_YARP` 把 `/api/sl/*` 反代到 Simple3**:YARP 在请求转发前后挂中间件做 JWT 校验、权限码二次确认、写操作审计、`Idempotency-Key` 透传、Scope 检查(RCSMonitor 的请求仅允许命中 `/api/ops/*` 这一前缀)。
|
||||
- **3D 由 Simple3 的 webVRender 提供**:Vue 端通过 iframe 或 Web Component 嵌入 webVRender 的页面,业务交互(选择、点击坐标等)通过 `postMessage` 与 Vue 桥接。
|
||||
- **Desktop-Only 模式**:`SL_API`、`PL` 都不启动;Vue 无法访问;CycleGUI 是唯一入口。
|
||||
- **Web-Enabled 模式**:CycleGUI 退化为状态窗;所有业务操作通过 Vue 完成;硬性不允许同时本地操作。
|
||||
|
||||
@@ -219,8 +219,8 @@ flowchart TB
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Start([SimpleLite.exe 启动]) --> CheckPrev{simple.json 中<br/>runMode 已记忆?}
|
||||
CheckPrev -- 否 --> Login[弹出登录窗<br/>SimpleLite Boot Login]
|
||||
Start([Simple3.exe 启动]) --> CheckPrev{simple.json 中<br/>runMode 已记忆?}
|
||||
CheckPrev -- 否 --> Login[弹出登录窗<br/>Simple3 Boot Login]
|
||||
CheckPrev -- 是 --> AutoLogin[自动按记忆模式登录<br/>仍需鉴权身份]
|
||||
Login --> Form
|
||||
AutoLogin --> ModeSwitch
|
||||
@@ -269,7 +269,7 @@ flowchart TB
|
||||
|
||||
| 元素 | 控件 | 说明 |
|
||||
|------|------|------|
|
||||
| 标题 | Label | "SimpleLite · 启动登录" |
|
||||
| 标题 | Label | "Simple3 · 启动登录" |
|
||||
| 用户名 | TextInput | 默认上次登录用户 |
|
||||
| 密码 | TextInput (password) | — |
|
||||
| 运行模式 | RadioButtons | `Desktop-Only` / `Web-Enabled` |
|
||||
@@ -281,7 +281,7 @@ flowchart TB
|
||||
| 登录按钮 | Button | 通过后销毁登录窗,进入选定模式 |
|
||||
| 退出按钮 | Button | 不进入主程序,退出进程 |
|
||||
|
||||
> 实现位置:新增 `SimpleLite/UI/BootLoginPanel.cs`,由 `Program.Main` 在 `Enssentials.Load` 之后、`Startup.EntryPoint` 之前调用 `GUI.PromptAndWaitPanel(BootLoginPanel.Build())`(阻塞式模态)。
|
||||
> 实现位置:新增 `Simple3/UI/BootLoginPanel.cs`,由 `Program.Main` 在 `Enssentials.Load` 之后、`Startup.EntryPoint` 之前调用 `GUI.PromptAndWaitPanel(BootLoginPanel.Build())`(阻塞式模态)。
|
||||
|
||||
#### simple.json 增量字段
|
||||
|
||||
@@ -310,18 +310,18 @@ flowchart TB
|
||||
| 场景 | 行为 |
|
||||
|------|------|
|
||||
| Desktop-Only 运行中 | Kestrel 不监听任何端口;MiGu.Server 不 spawn;尝试连接 7001/8080 直接 ECONNREFUSED |
|
||||
| Desktop-Only → Web-Enabled | **必须退出 SimpleLite 重新登录**;不支持热切(避免半启动状态) |
|
||||
| Desktop-Only → Web-Enabled | **必须退出 Simple3 重新登录**;不支持热切(避免半启动状态) |
|
||||
| Web-Enabled 运行中 | CycleGUI 主窗只显示状态窗(运行时长 / 在线用户 / 端口 / 主备状态)+「停止」按钮 |
|
||||
| Web-Enabled 下用户尝试本地控件 | **所有业务面板硬性不渲染**;Workspace 视口也不显示;用户无法通过任何方式重新启用 |
|
||||
| Web-Enabled 异常退出 | Watchdog 重新拉起,恢复到 Web-Enabled;不强迫用户重选 |
|
||||
| 想取消"记住模式" | 启动时按住 `Shift` 或运行 `SimpleLite.exe --re-login` 强制弹出登录窗 |
|
||||
| 想取消"记住模式" | 启动时按住 `Shift` 或运行 `Simple3.exe --re-login` 强制弹出登录窗 |
|
||||
| 远程开关模式 | Platform 管理员可以发 `POST /api/sl/runmode/reset` 把 `rememberRunMode` 置为 `false`,**仅在下次重启时生效** |
|
||||
|
||||
### 3.4 服务状态窗(Web-Enabled 唯一可见的 CycleGUI 面板)
|
||||
|
||||
```text
|
||||
┌──────────────────────────────────────────┐
|
||||
│ SimpleLite Service Status │
|
||||
│ Simple3 Service Status │
|
||||
├──────────────────────────────────────────┤
|
||||
│ 模式: Web-Enabled │
|
||||
│ 启动时间: 2026-05-18 09:00:00 │
|
||||
@@ -348,7 +348,7 @@ flowchart TB
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["SimpleLite.exe<br/>启动 + 登录"] --> Mode{运行模式}
|
||||
A["Simple3.exe<br/>启动 + 登录"] --> Mode{运行模式}
|
||||
|
||||
Mode -- Desktop-Only --> D[CycleGUI 全功能 UI]
|
||||
D --> SchedD[调度引擎]
|
||||
@@ -360,7 +360,7 @@ flowchart LR
|
||||
WHost --> WV[WebTerminal :8223<br/>webVRender]
|
||||
WHost --> StatusPanel[CycleGUI 状态窗]
|
||||
|
||||
subgraph SysMis["SimpleLite SystemMission 体系"]
|
||||
subgraph SysMis["Simple3 SystemMission 体系"]
|
||||
E["StartPlatformMission<br/>(SystemMission 子类)"]
|
||||
SM_Reg["SystemMissionRegistry"]
|
||||
SM_Hb["HeartbeatChannel<br/>(named pipe)"]
|
||||
@@ -380,24 +380,24 @@ flowchart LR
|
||||
|
||||
### 4.2 SystemMission 体系(v1.4 新增章节)
|
||||
|
||||
SimpleLite 现有 `Mission` 是业务任务(运送/搬运),新增 **`SystemMission`** 作为同一基类的"系统级 Mission",用于拉起并守护进程级服务。它**不参与调度引擎的 DispatchLoop**,由 Bootstrapper 直接驱动。
|
||||
Simple3 现有 `Mission` 是业务任务(运送/搬运),新增 **`SystemMission`** 作为同一基类的"系统级 Mission",用于拉起并守护进程级服务。它**不参与调度引擎的 DispatchLoop**,由 Bootstrapper 直接驱动。
|
||||
|
||||
**关键类:**
|
||||
|
||||
| 类型 | 类型 / 命名空间 | 职责 |
|
||||
|------|------------------|------|
|
||||
| `SystemMission`(抽象) | `SimpleLite.RCS.SystemMissions` | 继承自现有 `Mission`,新增 `Spawn() / Stop() / Heartbeat()` 等抽象方法 |
|
||||
| `StartPlatformMission` | `SimpleLite.RCS.SystemMissions` | 拉起 `MiGu.Server.exe`;通过命名管道 `\\.\pipe\simple-bootstrap` 接收心跳 |
|
||||
| `SystemMissionRegistry` | `SimpleLite.Bootstrap` | 单例,注册所有 SystemMission;Web-Enabled 启动时遍历 `Spawn()`;进程退出时遍历 `Stop()` |
|
||||
| `ProcessHost` | `SimpleLite.Bootstrap` | 封装 `System.Diagnostics.Process` + 重定向标准输出到日志 + 退出码捕获 |
|
||||
| `HeartbeatChannel` | `SimpleLite.Bootstrap` | 命名管道服务端;JSON 协议 `{ "ts", "rss", "queueDepth", "status" }`;3 个周期未达 → 标记不健康 |
|
||||
| `ProcessWatchdog` | `SimpleLite.Bootstrap` | 不健康时 `Stop() + Spawn()`;指数退避 1s → 2s → 4s → 8s → 30s(上限) |
|
||||
| `RunMode`(枚举) | `SimpleLite.Bootstrap` | `DesktopOnly` / `WebEnabled` |
|
||||
| `SystemMission`(抽象) | `Simple3.RCS.SystemMissions` | 继承自现有 `Mission`,新增 `Spawn() / Stop() / Heartbeat()` 等抽象方法 |
|
||||
| `StartPlatformMission` | `Simple3.RCS.SystemMissions` | 拉起 `MiGu.Server.exe`;通过命名管道 `\\.\pipe\simple-bootstrap` 接收心跳 |
|
||||
| `SystemMissionRegistry` | `Simple3.Bootstrap` | 单例,注册所有 SystemMission;Web-Enabled 启动时遍历 `Spawn()`;进程退出时遍历 `Stop()` |
|
||||
| `ProcessHost` | `Simple3.Bootstrap` | 封装 `System.Diagnostics.Process` + 重定向标准输出到日志 + 退出码捕获 |
|
||||
| `HeartbeatChannel` | `Simple3.Bootstrap` | 命名管道服务端;JSON 协议 `{ "ts", "rss", "queueDepth", "status" }`;3 个周期未达 → 标记不健康 |
|
||||
| `ProcessWatchdog` | `Simple3.Bootstrap` | 不健康时 `Stop() + Spawn()`;指数退避 1s → 2s → 4s → 8s → 30s(上限) |
|
||||
| `RunMode`(枚举) | `Simple3.Bootstrap` | `DesktopOnly` / `WebEnabled` |
|
||||
|
||||
**生命周期:**
|
||||
|
||||
```text
|
||||
SimpleLite 启动 (Web-Enabled)
|
||||
Simple3 启动 (Web-Enabled)
|
||||
├─ Kestrel Up :7001/7002
|
||||
├─ WebTerminal Up :8223
|
||||
├─ SchedulerRuntime.Start()
|
||||
@@ -414,7 +414,7 @@ SimpleLite 启动 (Web-Enabled)
|
||||
Watchdog 触发 → ProcessHost.Kill() → 退避等待 → Spawn() 重试
|
||||
连续 5 次失败 → 状态窗高亮 "Platform Down",停止重试,需人工介入
|
||||
|
||||
SimpleLite 优雅停机:
|
||||
Simple3 优雅停机:
|
||||
SystemMissionRegistry.StopAll()
|
||||
└─ StartPlatformMission.Stop()
|
||||
├─ 发送 SIGTERM 等价(命名管道 "shutdown" 消息)
|
||||
@@ -424,26 +424,26 @@ SimpleLite 优雅停机:
|
||||
|
||||
**实现要点:**
|
||||
|
||||
- **位置**:新建 `SimpleLite/Bootstrap/` 与 `SimpleLite/RCS/SystemMissions/`。
|
||||
- **位置**:新建 `Simple3/Bootstrap/` 与 `Simple3/RCS/SystemMissions/`。
|
||||
- **不写入主库 Missions 表**:SystemMission 是进程级配置,不参与项目序列化;存储在 `simple_main.db.SystemMissionConfig`(或直接读 `simple.json`)。
|
||||
- **崩溃隔离**:Platform 异常不影响 SimpleLite 调度循环;MiGu.Server 也实现 `IHostedService.StopAsync` 做 WAL Checkpoint + WS 排空 → 给 ROSE 释放磁盘。
|
||||
- **多实例预防**:通过命名互斥体 + `simple.json.allowMultiple=false`(已有)确保 SimpleLite 唯一;MiGu.Server 启动时校验"父进程是否为 SimpleLite",否则拒绝启动(防止有人手动双击 `MiGu.Server.exe`)。
|
||||
- **崩溃隔离**:Platform 异常不影响 Simple3 调度循环;MiGu.Server 也实现 `IHostedService.StopAsync` 做 WAL Checkpoint + WS 排空 → 给 ROSE 释放磁盘。
|
||||
- **多实例预防**:通过命名互斥体 + `simple.json.allowMultiple=false`(已有)确保 Simple3 唯一;MiGu.Server 启动时校验"父进程是否为 Simple3",否则拒绝启动(防止有人手动双击 `MiGu.Server.exe`)。
|
||||
- **静态资源**:MiGu.Server 用 `MapWhen + UseStaticFiles + UseSpa` 各挂一份 `index.html`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 三端职责矩阵
|
||||
|
||||
| 维度 | SimpleLite 桌面端 | **Platform 管理端(platform-vue)** | **RCSMonitor 运营端(rcsmonitor-vue)** |
|
||||
| 维度 | Simple3 桌面端 | **Platform 管理端(platform-vue)** | **RCSMonitor 运营端(rcsmonitor-vue)** |
|
||||
|------|---------------------|------------------------------------------|------------------------------------------|
|
||||
| 地图(站点/轨道/区域)编辑 | **读写**(Desktop-Only) | **全功能读写**(Vue 地图控件内嵌SimpleLite地图) | 只读投影,不可编辑 |
|
||||
| 地图(站点/轨道/区域)编辑 | **读写**(Desktop-Only) | **全功能读写**(Vue 地图控件内嵌Simple3地图) | 只读投影,不可编辑 |
|
||||
| 任务编排(Mission/Recipe) | **读写** | **全功能读写**(含 Demand / Recipe) | 基础动作:暂停/取消/重派/优先级 |
|
||||
| 车辆配置(参数 / 维护策略) | **读写** | **全功能读写** | 基础动作:暂停/继续/结束任务/上线/离线/手动充电等 |
|
||||
| CAD 工具 | **可用** | **可用**(Vue 调用 `/api/sl/cad/*`) | 不可用 |
|
||||
| 自定义字段 / 图层 | **可用** | **可用** | 只读 |
|
||||
| 调度引擎(DispatchLoop) | 进程内拥有 | 远程订阅状态 + 全 API 操作 | 远程订阅状态 + 受限 API |
|
||||
| 车端通信 | 拥有 | 不直连,只看投影 | 不直连,只看投影 |
|
||||
| 3D 视口 | 本地 OpenGL(CycleGUI 原生) | **嵌入 SimpleLite webVRender**(全功能交互) | **嵌入 SimpleLite webVRender**(只读 + 圈选) |
|
||||
| 3D 视口 | 本地 OpenGL(CycleGUI 原生) | **嵌入 Simple3 webVRender**(全功能交互) | **嵌入 Simple3 webVRender**(只读 + 圈选) |
|
||||
| 系统级配置 | 局部(Desktop-Only) | **全局编辑 + 下发** | 不可编辑 |
|
||||
| 外部对接(MES/WMS/RCS) | 不涉及 | **唯一入口** | 不涉及 |
|
||||
| 路径规划/交通/充电策略 | 算法 + 调试 | **可视化编辑 + 下发** | 只读 |
|
||||
@@ -549,7 +549,7 @@ Platform 将典型业务场景抽象为标准模板,支持开箱即用和后
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph 顶层["顶层(按端隔离)"]
|
||||
L1A["SimpleLite (CycleGUI + WebAPI Host)"]
|
||||
L1A["Simple3 (CycleGUI + WebAPI Host)"]
|
||||
L1B["MiGu.Server (ASP.NET Core + YARP)"]
|
||||
L1B2["platform-vue (SPA, 管理员)"]
|
||||
L1B3["rcsmonitor-vue (SPA, 运营)"]
|
||||
@@ -608,17 +608,17 @@ flowchart TB
|
||||
| `SimpleShared.Persistence` | net8.0 | EF Core 8 多 Provider 抽象(**SQLite / MySQL / PostgreSQL / SQL Server**)+ 迁移 + `DbProviderOptions` + 连接串构建器 + `WalSqliteOptions`(仅 SQLite) |
|
||||
| `SimpleShared.OpsCommand` | net8.0 | 运维白名单命令、审计、幂等键、频次限流 |
|
||||
| `SimpleShared.WebApiHost` | net8.0 | Kestrel 公共启动器:JWT 中间件 / Swagger / CORS / 健康检查 / 请求审计 |
|
||||
| `MiGu.Server` | net8.0 ASP.NET Core | 业务 BFF + **YARP 反代 SimpleLite** + 静态托管两份 Vue dist;**启动时校验父进程为 SimpleLite,否则拒绝运行**(防误用) |
|
||||
| `platform-vue` | Vue 3 + Vite + Pinia | 全功能套壳:复刻 SimpleLite 所有控件 |
|
||||
| `MiGu.Server` | net8.0 ASP.NET Core | 业务 BFF + **YARP 反代 Simple3** + 静态托管两份 Vue dist;**启动时校验父进程为 Simple3,否则拒绝运行**(防误用) |
|
||||
| `platform-vue` | Vue 3 + Vite + Pinia | 全功能套壳:复刻 Simple3 所有控件 |
|
||||
| `rcsmonitor-vue` | Vue 3 + Vite + Pinia | 运营监控:按权限渲染,运维动作面板 |
|
||||
| `packages/sl-controls`(pnpm workspace) | Vue 3 组件库 | Workspace3D(嵌 webVRender)/ 地图列表 / 任务表 / CAD 工具栏等共享组件 |
|
||||
|
||||
### 6.1 Platform Vue 控件复刻清单(与 SimpleLite CycleGUI 对照)
|
||||
### 6.1 Platform Vue 控件复刻清单(与 Simple3 CycleGUI 对照)
|
||||
|
||||
| SimpleLite CycleGUI 面板 | Vue 组件(位于 `sl-controls`) | 备注 |
|
||||
| Simple3 CycleGUI 面板 | Vue 组件(位于 `sl-controls`) | 备注 |
|
||||
|--------------------------|--------------------------------|------|
|
||||
| 主菜单栏 / 工具栏 | `<AppShell>` 顶栏 + 侧栏 | platform-vue 全功能;rcsmonitor-vue 仅显示运营菜单 |
|
||||
| Workspace 3D 视口 | `<Workspace3D>` | **iframe / Web Component 嵌入 SimpleLite webVRender**;交互事件用 `postMessage` 桥接 |
|
||||
| Workspace 3D 视口 | `<Workspace3D>` | **iframe / Web Component 嵌入 Simple3 webVRender**;交互事件用 `postMessage` 桥接 |
|
||||
| 地图面板(Sites / Tracks) | `<MapEditor>` + `<TrackTable>` | 表格 + 拓扑视图 |
|
||||
| Car 面板 | `<CarPanel>` | 表格 + 表单 |
|
||||
| Mission 面板 | `<MissionEditor>` | 任务流图 + 表单 |
|
||||
@@ -674,7 +674,7 @@ function onReady() {
|
||||
</script>
|
||||
```
|
||||
|
||||
`SimpleLite/Rendering/SimpleSceneRenderer.cs` 端按 `scope` 和 `ro` 控制:
|
||||
`Simple3/Rendering/SimpleSceneRenderer.cs` 端按 `scope` 和 `ro` 控制:
|
||||
- `scope=RCSMonitor` 或 `ro=1` → 禁用编辑工具(不响应 `SelectObject`/`GetPosition` 中的拖动)
|
||||
- 通过 `postMessage` 向 iframe 父页面回传选择/点击事件
|
||||
|
||||
@@ -690,7 +690,7 @@ builder.Services.AddReverseProxy()
|
||||
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
|
||||
.AddTransforms(ctx =>
|
||||
{
|
||||
// 透传当前 JWT 的 userId / scope 给 SimpleLite
|
||||
// 透传当前 JWT 的 userId / scope 给 Simple3
|
||||
ctx.AddRequestTransform(async tc =>
|
||||
{
|
||||
var ident = tc.HttpContext.User;
|
||||
@@ -767,7 +767,7 @@ app.Run();
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph App["应用代码 (SimpleLite / MiGu.Server)"]
|
||||
subgraph App["应用代码 (Simple3 / MiGu.Server)"]
|
||||
DOM["领域服务 / Repository"]
|
||||
EFC["EF Core 8 DbContext"]
|
||||
OPT["DbProviderOptions<br/>{ provider, conn, ha }"]
|
||||
@@ -813,7 +813,7 @@ flowchart TB
|
||||
|
||||
| 逻辑库 | 拥有进程 | 主要表/数据 |
|
||||
|--------|----------|-------------|
|
||||
| **simple_main** | SimpleLite | Maps, Sites, Tracks, SpecialProps, Missions, Cars, Plugins, DesignAuditLog, OpsAuditLog, LocalUsers, RunModeHistory |
|
||||
| **simple_main** | Simple3 | Maps, Sites, Tracks, SpecialProps, Missions, Cars, Plugins, DesignAuditLog, OpsAuditLog, LocalUsers, RunModeHistory |
|
||||
| **platform** | Platform | Users, Roles, RolePermissions, WidgetGrants, SystemConfig, ExternalAdapters, RoutingPolicies, VehicleMaintenancePolicies, ChargePolicies, TaskAllocationPolicies, TrafficRules, Locations, Inventory, OpsLogs(含 RCSMonitor 来源), Annotations, Snapshots, Versions, CustomWidgets |
|
||||
|
||||
> 原 v1.2 中的 `rcsmonitor_local` 取消;其表(OpsLogs / Annotations / Snapshots / WidgetLayoutCache)合并到 `platform`,加 `source='RCSMonitor'` 字段区分。
|
||||
@@ -824,12 +824,12 @@ flowchart TB
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph A["主节点 NODE-A (Active)"]
|
||||
SL_A["SimpleLite.exe"]
|
||||
SL_A["Simple3.exe"]
|
||||
PL_A["MiGu.Server.exe"]
|
||||
D_A[("受 ROSE 保护盘 R:\<br/>simple_main.db<br/>platform.db<br/>(WAL)")]
|
||||
end
|
||||
subgraph B["备节点 NODE-B (Standby)"]
|
||||
SL_B["SimpleLite.exe (suspended)"]
|
||||
SL_B["Simple3.exe (suspended)"]
|
||||
PL_B["MiGu.Server.exe (suspended)"]
|
||||
D_B[("ROSE 镜像盘 R:\")]
|
||||
end
|
||||
@@ -860,7 +860,7 @@ flowchart TB
|
||||
|------|------|------|
|
||||
| `RES_DISK_R` | Mirror Disk | — |
|
||||
| `RES_VIP_LAN` | Virtual IP | RES_DISK_R |
|
||||
| `RES_SVC_SL` | Service: SimpleLite (Web-Enabled) | RES_DISK_R, RES_VIP_LAN |
|
||||
| `RES_SVC_SL` | Service: Simple3 (Web-Enabled) | RES_DISK_R, RES_VIP_LAN |
|
||||
| `RES_SVC_PL` | Service: MiGu.Server | RES_DISK_R, RES_VIP_LAN, RES_SVC_SL |
|
||||
| `RES_SCRIPT_CHK` | 数据库健康检查脚本 | RES_DISK_R |
|
||||
|
||||
@@ -871,7 +871,7 @@ flowchart TB
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph App["应用层 (VIP + 重试)"]
|
||||
SL["SimpleLite.exe"]
|
||||
SL["Simple3.exe"]
|
||||
PL["MiGu.Server.exe"]
|
||||
end
|
||||
subgraph Router["MySQL Router (或 ProxySQL)"]
|
||||
@@ -911,7 +911,7 @@ flowchart TB
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph App["应用层"]
|
||||
SL["SimpleLite.exe"]
|
||||
SL["Simple3.exe"]
|
||||
PL["MiGu.Server.exe"]
|
||||
end
|
||||
subgraph LB["HAProxy / PgBouncer"]
|
||||
@@ -953,7 +953,7 @@ flowchart TB
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph App["应用层"]
|
||||
SL["SimpleLite.exe"]
|
||||
SL["Simple3.exe"]
|
||||
PL["MiGu.Server.exe"]
|
||||
end
|
||||
subgraph Listener["AG Listener (DNS + VIP)"]
|
||||
@@ -983,7 +983,7 @@ flowchart TB
|
||||
- **EF Core Provider**:`Microsoft.EntityFrameworkCore.SqlServer`;支持 SQL Server 2019+(建议 Enterprise 版以启用同步提交副本数 ≥3 + Read-Only Routing)。Standard 版可降级为 2 副本基本可用性组。
|
||||
- **HA 形态**:**WSFC(Windows Server Failover Cluster)+ Always On Availability Groups**;通过 **AG Listener** 暴露统一虚拟名/VIP,客户端连 Listener 名即可。
|
||||
- **同步策略**:核心库(`platform` / `simple_main`)配 1 个同步提交副本(RPO=0),1–N 个异步副本(可读分析)。
|
||||
- **认证**:建议混合身份认证;MiGu.Server 用 SQL Login,SimpleLite 桌面端可走 Windows 集成认证。
|
||||
- **认证**:建议混合身份认证;MiGu.Server 用 SQL Login,Simple3 桌面端可走 Windows 集成认证。
|
||||
- **TempDB**:每节点分摊;MultipleActiveResultSets(MARS)建议关闭,EF Core 已能避免。
|
||||
- **备份**:`BACKUP DATABASE ... WITH COMPRESSION` + 事务日志备份每 15min;保留策略与公司 IT 一致。
|
||||
- **RTO / RPO**:RTO ≈ 10–30s(AG 自动 failover);RPO ≈ 0(同步提交副本)。
|
||||
@@ -1091,7 +1091,7 @@ flowchart TB
|
||||
"database": "simple_main",
|
||||
"user": "simple_app",
|
||||
"password": "${SECRET_MSSQL_PWD}",
|
||||
"extra": "Encrypt=True;TrustServerCertificate=False;MultiSubnetFailover=True;Application Name=SimpleLite"
|
||||
"extra": "Encrypt=True;TrustServerCertificate=False;MultiSubnetFailover=True;Application Name=Simple3"
|
||||
},
|
||||
"platform": {
|
||||
"server": "tcp:rcs-ag.lan,1433",
|
||||
@@ -1158,7 +1158,7 @@ classDiagram
|
||||
|
||||
**落地四件套:**
|
||||
|
||||
1. **认证**:`SimpleShared.Auth` 提供 `JwtIssuer`;Web-Enabled 时 MiGu.Server 签发;Desktop-Only 时 SimpleLite 本地签发短期 Token(24h)。
|
||||
1. **认证**:`SimpleShared.Auth` 提供 `JwtIssuer`;Web-Enabled 时 MiGu.Server 签发;Desktop-Only 时 Simple3 本地签发短期 Token(24h)。
|
||||
2. **授权**:所有 WebAPI + WS 调用走 `IPermissionEvaluator`;权限码 `<area>.<action>`。
|
||||
3. **范围(Scope)**:`Lite` / `Platform` / `RCSMonitor` / `*`;同一用户在不同端可用集合不同。Vue 端登录时由前端代码根据访问路径自动带入 `scope`(`/admin/*` → `Platform`,`/monitor/*` → `RCSMonitor`)。
|
||||
4. **控件级权限**:除 `Permission` 外,角色还可关联 `WidgetGrant`,每个 `WidgetId` 三档可见性:
|
||||
@@ -1173,7 +1173,7 @@ sequenceDiagram
|
||||
participant U as 运营用户
|
||||
participant RV as rcsmonitor-vue
|
||||
participant P as MiGu.Server
|
||||
participant L as SimpleLite (YARP 后侧)
|
||||
participant L as Simple3 (YARP 后侧)
|
||||
participant DBp as platform.db
|
||||
|
||||
U->>RV: 访问 https://host:8080/monitor
|
||||
@@ -1255,7 +1255,7 @@ flowchart LR
|
||||
UI11 --> Sch
|
||||
Sch --> Val --> Ver --> Pub
|
||||
|
||||
Pub -- "REST PUT /api/sl/config/{section} (via YARP)" --> SL["SimpleLite (主服务)"]
|
||||
Pub -- "REST PUT /api/sl/config/{section} (via YARP)" --> SL["Simple3 (主服务)"]
|
||||
Pub -- "WS broadcast: config.updated" --> RMVue["rcsmonitor-vue"]
|
||||
Pub -- "WS broadcast: config.updated" --> AdminVue["其他在线 platform-vue"]
|
||||
```
|
||||
@@ -1316,7 +1316,7 @@ public record EffectivePermissions(
|
||||
List<WidgetGrantDto> VisibleWidgets);
|
||||
```
|
||||
|
||||
**发布机制:** Schema → 校验 → 版本 → 灰度 → 全量;MiGu.Server 通过 YARP `PUT /api/sl/config/{section}` 推到 SimpleLite,主服务原子切换 + ack;失败自动回滚,WS `config.rollback` 广播。
|
||||
**发布机制:** Schema → 校验 → 版本 → 灰度 → 全量;MiGu.Server 通过 YARP `PUT /api/sl/config/{section}` 推到 Simple3,主服务原子切换 + ack;失败自动回滚,WS `config.rollback` 广播。
|
||||
|
||||
---
|
||||
|
||||
@@ -1327,14 +1327,14 @@ public record EffectivePermissions(
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant L as SimpleLite (CycleGUI)
|
||||
participant L as Simple3 (CycleGUI)
|
||||
participant BL as BootLoginPanel
|
||||
participant K as Kestrel (WebAPI)
|
||||
participant P as MiGu.Server (YARP)
|
||||
participant V as platform-vue
|
||||
participant RV as rcsmonitor-vue
|
||||
|
||||
U->>L: 启动 SimpleLite.exe
|
||||
U->>L: 启动 Simple3.exe
|
||||
L->>L: 读取 simple.json
|
||||
alt rememberRunMode = true
|
||||
L->>L: 直接采用 runMode (仍鉴权)
|
||||
@@ -1373,10 +1373,10 @@ sequenceDiagram
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant L as SimpleLite
|
||||
participant L as Simple3
|
||||
participant BL as BootLoginPanel
|
||||
|
||||
U->>L: 启动 SimpleLite.exe
|
||||
U->>L: 启动 Simple3.exe
|
||||
L->>L: 读 simple.json (无记忆 或 选项=DesktopOnly)
|
||||
L->>BL: 必要时弹出登录窗
|
||||
U->>BL: 输入凭据 + 选 Desktop-Only
|
||||
@@ -1387,13 +1387,13 @@ sequenceDiagram
|
||||
L-->>U: 进入设计端主界面
|
||||
```
|
||||
|
||||
### 10.3 platform-vue 编辑地图站点 → SimpleLite 落库
|
||||
### 10.3 platform-vue 编辑地图站点 → Simple3 落库
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant V as platform-vue
|
||||
participant P as MiGu.Server (YARP)
|
||||
participant L as SimpleLite (WebAPI)
|
||||
participant L as Simple3 (WebAPI)
|
||||
participant DBl as simple_main.db
|
||||
participant RV as rcsmonitor-vue (订阅)
|
||||
|
||||
@@ -1406,7 +1406,7 @@ sequenceDiagram
|
||||
P-->>V: 201 + siteId
|
||||
L->>L: WS broadcast { type: "map.site.created", payload }
|
||||
L-->>P: WS event
|
||||
P-->>V: WS event (前端 Workspace3D iframe 收到 SimpleLite 推送的 PutModelObject 同步)
|
||||
P-->>V: WS event (前端 Workspace3D iframe 收到 Simple3 推送的 PutModelObject 同步)
|
||||
P-->>RV: WS event (RCSMonitor 接收并按权限展示)
|
||||
```
|
||||
|
||||
@@ -1416,7 +1416,7 @@ sequenceDiagram
|
||||
sequenceDiagram
|
||||
participant V as platform-vue
|
||||
participant P as Platform
|
||||
participant L as SimpleLite
|
||||
participant L as Simple3
|
||||
participant E as DispatchLoop
|
||||
participant RV as rcsmonitor-vue
|
||||
|
||||
@@ -1440,7 +1440,7 @@ sequenceDiagram
|
||||
participant U as 运营人员
|
||||
participant RV as rcsmonitor-vue
|
||||
participant P as MiGu.Server (YARP)
|
||||
participant L as SimpleLite (Ops 网关)
|
||||
participant L as Simple3 (Ops 网关)
|
||||
participant E as DispatchLoop
|
||||
participant DBp as platform.db
|
||||
participant DBl as simple_main.db
|
||||
@@ -1476,10 +1476,10 @@ sequenceDiagram
|
||||
VIP->>NA: 路由到 Active
|
||||
Note over NA: 节点故障
|
||||
ROSE->>NA: 心跳超时 → 触发切换
|
||||
ROSE->>NA: 强制停止 SimpleLite / Platform
|
||||
ROSE->>NA: 强制停止 Simple3 / Platform
|
||||
ROSE->>NA: 卸载 R: 卷
|
||||
ROSE->>NB: 挂载 R: 卷
|
||||
ROSE->>NB: 启动 SimpleLite (WAL 重放 + 主库自检)
|
||||
ROSE->>NB: 启动 Simple3 (WAL 重放 + 主库自检)
|
||||
ROSE->>NB: 启动 MiGu.Server
|
||||
ROSE->>VIP: 浮动 IP 漂移到 NODE-B
|
||||
CLI--xVIP: 短暂中断
|
||||
@@ -1495,12 +1495,12 @@ sequenceDiagram
|
||||
|
||||
| 通道 | 协议 | 编码 | 端点 | 用途 |
|
||||
|------|------|------|------|------|
|
||||
| MiGu.Server (YARP) ↔ SimpleLite | REST / WebSocket | JSON | `:7001` / `:7002` | 全功能 API 反代 + 事件订阅 |
|
||||
| MiGu.Server (YARP) ↔ Simple3 | REST / WebSocket | JSON | `:7001` / `:7002` | 全功能 API 反代 + 事件订阅 |
|
||||
| platform-vue ↔ MiGu.Server | REST / WebSocket | JSON | `:8080` / `:8081` (路径前缀 `/admin`) | UI 操作、事件推送 |
|
||||
| rcsmonitor-vue ↔ MiGu.Server | REST / WebSocket | JSON | `:8080` / `:8081` (路径前缀 `/monitor`,`scope=RCSMonitor`) | UI 操作、事件推送(受限) |
|
||||
| Vue Workspace3D ↔ SimpleLite WebTerminal | WSS(webVRender 协议) | webVRender 自有 | `:8223` | 3D 视口同步(嵌入 iframe) |
|
||||
| Vue Workspace3D ↔ Simple3 WebTerminal | WSS(webVRender 协议) | webVRender 自有 | `:8223` | 3D 视口同步(嵌入 iframe) |
|
||||
| 子进程心跳 | Named Pipe | JSON | `\\.\pipe\simple-bootstrap` | 守护 / 重启 |
|
||||
| Vehicle ↔ SimpleLite | TCP / WS / OPC-UA | 业务私有 | `:8222` | 车端协议 |
|
||||
| Vehicle ↔ Simple3 | TCP / WS / OPC-UA | 业务私有 | `:8222` | 车端协议 |
|
||||
| ROSE 心跳 | 专用网卡 + 串口 | ROSE 私有 | — | 节点健康监测 |
|
||||
| ROSE 镜像复制 | 专用网卡 | ROSE 私有 | — | 块级实时数据同步 |
|
||||
|
||||
@@ -1510,7 +1510,7 @@ sequenceDiagram
|
||||
- 鉴权:`Authorization: Bearer <JWT>`;服务间互调附 `X-Svc-Token` + `X-On-Behalf-Of: <userId>` + `X-Scope: <scope>`
|
||||
- 幂等:写操作支持 `Idempotency-Key` 请求头
|
||||
- 版本化:配置类资源使用 `ETag` / `If-Match` 做乐观锁
|
||||
- Swagger:Platform / SimpleLite 都暴露 `/swagger`(生产可关)
|
||||
- Swagger:Platform / Simple3 都暴露 `/swagger`(生产可关)
|
||||
- **YARP Scope 白名单**:`scope=RCSMonitor` 时仅允许 `/api/sl/ops/*` 与 `/api/sl/projection/*` 命中;其他路径直接 403
|
||||
|
||||
---
|
||||
@@ -1521,12 +1521,12 @@ sequenceDiagram
|
||||
flowchart TB
|
||||
subgraph Cluster["双机 ROSE HA 集群 (Web-Enabled)"]
|
||||
subgraph NA["NODE-A (Active)"]
|
||||
SL_A["SimpleLite.exe<br/>Kestrel :7001/7002<br/>WebTerminal :8223"]
|
||||
SL_A["Simple3.exe<br/>Kestrel :7001/7002<br/>WebTerminal :8223"]
|
||||
PL_A["MiGu.Server.exe<br/>:8080/8081<br/>(YARP + 双 Vue dist)"]
|
||||
R_A["ROSE 受保护盘 R:\<br/>两库 (WAL)"]
|
||||
end
|
||||
subgraph NB["NODE-B (Standby)"]
|
||||
SL_B["SimpleLite.exe (suspended)"]
|
||||
SL_B["Simple3.exe (suspended)"]
|
||||
PL_B["MiGu.Server.exe (suspended)"]
|
||||
R_B["ROSE 镜像盘 R:\<br/>(实时同步)"]
|
||||
end
|
||||
@@ -1556,12 +1556,12 @@ flowchart TB
|
||||
|
||||
| 端口 | 服务 | 启用条件 |
|
||||
|------|------|----------|
|
||||
| 7001 | SimpleLite WebAPI | Web-Enabled |
|
||||
| 7002 | SimpleLite WebSocket | Web-Enabled |
|
||||
| 7001 | Simple3 WebAPI | Web-Enabled |
|
||||
| 7002 | Simple3 WebSocket | Web-Enabled |
|
||||
| 8080 | Platform WebAPI + platform-vue 静态(/admin)+ rcsmonitor-vue 静态(/monitor) | Web-Enabled |
|
||||
| 8081 | Platform WebSocket | Web-Enabled |
|
||||
| 8222 | SimpleLite 车端 / API(现有) | 所有模式 |
|
||||
| 8223 | SimpleLite WebTerminal(webVRender,Vue 嵌入用) | Web-Enabled |
|
||||
| 8222 | Simple3 车端 / API(现有) | 所有模式 |
|
||||
| 8223 | Simple3 WebTerminal(webVRender,Vue 嵌入用) | Web-Enabled |
|
||||
|
||||
> Desktop-Only 模式下:7001 / 7002 / 8080 / 8081 / 8223 全部**不监听**。
|
||||
|
||||
@@ -1588,7 +1588,7 @@ flowchart LR
|
||||
SHH["SimpleShared.WebApiHost"]
|
||||
end
|
||||
subgraph apps["两端后端"]
|
||||
SL["SimpleLite (CycleGUI + WebAPI)"]
|
||||
SL["Simple3 (CycleGUI + WebAPI)"]
|
||||
PSrv["MiGu.Server (WebAPI + YARP)"]
|
||||
end
|
||||
subgraph frontends["前端 (pnpm workspace)"]
|
||||
@@ -1709,7 +1709,7 @@ pnpm --filter rcsmonitor-vue build # 产出 apps/rcsmonitor-vue/dist
|
||||
|--------|----------|----------|
|
||||
| **M0 - 内核下沉** | 1 周 | 新建 SimpleShared.* 6 个工程;Configuration → IConfigSnapshotProvider;EF Core + SQLite + 迁移 |
|
||||
| **M1 - 启动登录 + 运行模式** | 1 周 | `BootLoginPanel`;`RunMode` 枚举;CycleGUI 状态窗;`simple.json.runMode` 持久化;Web-Enabled 业务面板硬隐藏 |
|
||||
| **M2 - WebAPI 协议层** | 1.5 周 | SimpleLite 集成 Kestrel;定义 `/api/projection/*`、`/api/ops/*`、`/api/config/*`、`/api/map/*` 等;WS Hub;JWT |
|
||||
| **M2 - WebAPI 协议层** | 1.5 周 | Simple3 集成 Kestrel;定义 `/api/projection/*`、`/api/ops/*`、`/api/config/*`、`/api/map/*` 等;WS Hub;JWT |
|
||||
| **M3 - 共享前端组件库** | 1.5 周 | `frontends/packages/sl-controls`:Workspace3D(嵌入 webVRender)+ 地图列表 + 任务表 + CAD 工具栏 + OpsActionPanel 等 |
|
||||
| **M4 - Platform 后端 + YARP** | 2 周 | MiGu.Server(WebAPI + Swagger + YARP `/api/sl/*` + Scope 中间件 + 静态托管双 Vue dist)+ 配置中心 11 个 schema + 用户/角色/控件权限 |
|
||||
| **M5 - platform-vue (管理员)** | 2.5 周 | 全功能套壳:登录、地图设计、车辆/任务、CAD、字段/图层、配置中心、库位、自定义控件、回放、设备管理、车队生命周期、场景模板 |
|
||||
@@ -1726,14 +1726,14 @@ pnpm --filter rcsmonitor-vue build # 产出 apps/rcsmonitor-vue/dist
|
||||
|---|------|------|
|
||||
| 1 | 运行模式持久化 | **写入 `simple.json.runMode` / `rememberRunMode`**(不使用 %APPDATA%) |
|
||||
| 2 | Web-Enabled 下是否允许同时本地操作 | **不允许**;CycleGUI 仅状态窗;不提供"临时启用本地 UI"开关 |
|
||||
| 3 | 3D 引擎 | **复用 SimpleLite WebTerminal 的 webVRender**,Vue 通过 iframe / Web Component 嵌入 |
|
||||
| 3 | 3D 引擎 | **复用 Simple3 WebTerminal 的 webVRender**,Vue 通过 iframe / Web Component 嵌入 |
|
||||
| 4 | 反向代理 | **YARP**(MiGu.Server 用 YARP 反代 `/api/sl/*`) |
|
||||
| 5 | RCSMonitor 是否需要独立后端 | **否**,完全依赖 MiGu.Server;通过 `scope=RCSMonitor` + 角色 + WidgetGrant 限权 |
|
||||
| 6 | 共享前端组件库 | **pnpm workspace + `frontends/packages/sl-controls`** |
|
||||
| 7 | 运营白名单 | 采纳 §5.1 列表(10 条,后续可扩) |
|
||||
| 8 | ROSE 复制粒度 | 块级镜像盘(RPO≈0)首选;视现场版本兜底文件级 |
|
||||
| 9 | 车端连接策略 | 走 VIP,统一切换;车端实现 WS 自动重连 |
|
||||
| 10 | **Platform / RCSMonitor 启动归属(v1.4)** | **由 SimpleLite 内置 `SystemMission.StartPlatformMission` 拉起并守护**;不允许独立安装 MiGu.Server.exe 为 Windows Service;MiGu.Server 启动时校验父进程必须是 SimpleLite |
|
||||
| 10 | **Platform / RCSMonitor 启动归属(v1.4)** | **由 Simple3 内置 `SystemMission.StartPlatformMission` 拉起并守护**;不允许独立安装 MiGu.Server.exe 为 Windows Service;MiGu.Server 启动时校验父进程必须是 Simple3 |
|
||||
| 11 | **持久层多 Provider(v1.5)** | EF Core 抽象,**默认 SQLite + ROSE HA**;可选 **MySQL InnoDB Cluster / PostgreSQL Patroni / SQL Server Always On AG**;每 Provider 一套 EF Migrations,CI 全矩阵跑通;运行期由 `simple.json.database.provider` 决定 |
|
||||
| 12 | **跨 Provider 切换策略** | 不支持热切;切换走停服窗口 + `tools/db/migrate.ps1`;旧文件作为回退包保留 |
|
||||
|
||||
@@ -1743,22 +1743,22 @@ pnpm --filter rcsmonitor-vue build # 产出 apps/rcsmonitor-vue/dist
|
||||
|
||||
| 现有文件 | 改动方向 |
|
||||
|----------|----------|
|
||||
| `SimpleLite/Program.cs` | 在 `Enssentials.Load` 之后插入 `BootLoginPanel.RunModal()`;根据返回的 `RunMode` 决定后续路径 |
|
||||
| `SimpleLite/Configuration.cs` | 拆为 `LocalBootConfig`(启动期,含 `runMode`、`rememberRunMode`、监听地址、各端口)+ `IConfigSnapshotProvider`(运行期);数据库路径走 `%SIMPLE_DATA_ROOT%` |
|
||||
| `SimpleLite/Startup.cs` | 按 `RunMode` 分支:Desktop-Only 走原有 CycleGUI;Web-Enabled 启动 Kestrel + WebAPI + WebTerminal + Spawn Platform + 切换 CycleGUI 到状态窗 |
|
||||
| `SimpleLite/UI/SimpleUI.cs` | 增加 `RunMode.WebEnabled` 时**所有业务面板硬性不创建**;新增"服务状态窗"面板(唯一允许) |
|
||||
| `SimpleLite/UI/BootLoginPanel.cs`(新增) | 启动登录窗实现 + 模式持久化 |
|
||||
| `SimpleLite/UI/ServiceStatusPanel.cs`(新增) | Web-Enabled 状态窗实现 |
|
||||
| `SimpleLite/RCS/Mission.cs` | 抽出 `SystemMission` 抽象基类(与业务 Mission 同一基类但走独立 Registry,**不进入 DispatchLoop**) |
|
||||
| `SimpleLite/RCS/SystemMissions/StartPlatformMission.cs`(新增) | 拉起 `MiGu.Server.exe`、绑定命名管道、上报心跳与状态到 ServiceStatusPanel |
|
||||
| `SimpleLite/Bootstrap/ProcessHost.cs`(新增) | 封装 `System.Diagnostics.Process` + 日志重定向 + 优雅停止 |
|
||||
| `SimpleLite/Bootstrap/HeartbeatChannel.cs`(新增) | 命名管道服务端 + JSON 心跳协议 |
|
||||
| `SimpleLite/Bootstrap/SystemMissionRegistry.cs`(新增) | 注册/批量 Spawn/Stop SystemMission |
|
||||
| `SimpleLite/Bootstrap/ProcessWatchdog.cs`(新增) | 指数退避重启 + 多次失败后熔断并通知状态窗 |
|
||||
| `SimpleLite/Bootstrap/RunMode.cs`(新增) | `DesktopOnly` / `WebEnabled` 枚举 + 启动期校验 |
|
||||
| `SimpleLite/Rendering/SimpleSceneRenderer.cs` | 按 webVRender 的 `?scope=` / `?ro=` 参数禁用编辑工具 |
|
||||
| `Simple3/Program.cs` | 在 `Enssentials.Load` 之后插入 `BootLoginPanel.RunModal()`;根据返回的 `RunMode` 决定后续路径 |
|
||||
| `Simple3/Configuration.cs` | 拆为 `LocalBootConfig`(启动期,含 `runMode`、`rememberRunMode`、监听地址、各端口)+ `IConfigSnapshotProvider`(运行期);数据库路径走 `%SIMPLE_DATA_ROOT%` |
|
||||
| `Simple3/Startup.cs` | 按 `RunMode` 分支:Desktop-Only 走原有 CycleGUI;Web-Enabled 启动 Kestrel + WebAPI + WebTerminal + Spawn Platform + 切换 CycleGUI 到状态窗 |
|
||||
| `Simple3/UI/SimpleUI.cs` | 增加 `RunMode.WebEnabled` 时**所有业务面板硬性不创建**;新增"服务状态窗"面板(唯一允许) |
|
||||
| `Simple3/UI/BootLoginPanel.cs`(新增) | 启动登录窗实现 + 模式持久化 |
|
||||
| `Simple3/UI/ServiceStatusPanel.cs`(新增) | Web-Enabled 状态窗实现 |
|
||||
| `Simple3/RCS/Mission.cs` | 抽出 `SystemMission` 抽象基类(与业务 Mission 同一基类但走独立 Registry,**不进入 DispatchLoop**) |
|
||||
| `Simple3/RCS/SystemMissions/StartPlatformMission.cs`(新增) | 拉起 `MiGu.Server.exe`、绑定命名管道、上报心跳与状态到 ServiceStatusPanel |
|
||||
| `Simple3/Bootstrap/ProcessHost.cs`(新增) | 封装 `System.Diagnostics.Process` + 日志重定向 + 优雅停止 |
|
||||
| `Simple3/Bootstrap/HeartbeatChannel.cs`(新增) | 命名管道服务端 + JSON 心跳协议 |
|
||||
| `Simple3/Bootstrap/SystemMissionRegistry.cs`(新增) | 注册/批量 Spawn/Stop SystemMission |
|
||||
| `Simple3/Bootstrap/ProcessWatchdog.cs`(新增) | 指数退避重启 + 多次失败后熔断并通知状态窗 |
|
||||
| `Simple3/Bootstrap/RunMode.cs`(新增) | `DesktopOnly` / `WebEnabled` 枚举 + 启动期校验 |
|
||||
| `Simple3/Rendering/SimpleSceneRenderer.cs` | 按 webVRender 的 `?scope=` / `?ro=` 参数禁用编辑工具 |
|
||||
| `SimpleScheduler/WebApi.cs` | 调用方从 Nancy 切到 ASP.NET Core Controller;保持 `SchedulerRuntime` 外观稳定 |
|
||||
| `SimpleLite/SimpleProject.cs` | 持久化双写:JSON(兼容)+ SQLite(新主存,受 ROSE 保护盘) |
|
||||
| `Simple3/SimpleProject.cs` | 持久化双写:JSON(兼容)+ SQLite(新主存,受 ROSE 保护盘) |
|
||||
| `Plugins/Sample` | 示例插件读取 `IConfigSnapshotProvider`;不再读 `Configuration.conf` |
|
||||
| 新增 `tools/ha/*` | ROSE 资源启停脚本、健康检查脚本、切换演练脚本 |
|
||||
| 新增 `tools/db/*` | 跨 Provider 迁移脚本(`migrate.ps1` / `dump.ps1` / `restore.ps1`);按目标 DB 校验 schema 与索引 |
|
||||
@@ -1909,12 +1909,12 @@ pnpm --filter rcsmonitor-vue build # 产出 apps/rcsmonitor-vue/dist
|
||||
|---|-----|------|------|
|
||||
| B1 | Kestrel + Swagger + CORS | ✅ | `Program.cs` |
|
||||
| B2 | Auth / Health / Config / Ops / Projection 控制器 | ✅ 骨架 | Mock/内存存储 |
|
||||
| B3 | YARP `/api/sl/*` → :8222 | ✅ 配置 | 依赖 SimpleLite 实际监听 |
|
||||
| B3 | YARP `/api/sl/*` → :8222 | ✅ 配置 | 依赖 Simple3 实际监听 |
|
||||
| B4 | 静态托管 + SPA fallback | ✅ | 单一 `wwwroot/index.html` |
|
||||
| B5 | JWT 真签发 + 库持久化 | ☐ | 仍为 Mock |
|
||||
| B6 | 父进程 SimpleLite 校验 | ☐ | §4、§15 #10 |
|
||||
| B6 | 父进程 Simple3 校验 | ☐ | §4、§15 #10 |
|
||||
|
||||
### 18.3 SimpleLite / 共享内核(ARCHITECTURE 主体)
|
||||
### 18.3 Simple3 / 共享内核(ARCHITECTURE 主体)
|
||||
|
||||
| # | 项 | 状态 | 备注 |
|
||||
|---|-----|------|------|
|
||||
@@ -1942,5 +1942,5 @@ pnpm --filter rcsmonitor-vue build # 产出 apps/rcsmonitor-vue/dist
|
||||
|
||||
## 关联文档
|
||||
|
||||
- [SIMPLELITE_DEVELOPMENT_PLAN.md](./SIMPLELITE_DEVELOPMENT_PLAN.md) — SimpleLite 功能对齐执行计划
|
||||
- [SIMPLELITE_DEVELOPMENT_PLAN.md](./SIMPLELITE_DEVELOPMENT_PLAN.md) — Simple3 功能对齐执行计划
|
||||
- [SIMPLELITE_COMPOSER_PARITY_PLAN.md](./SIMPLELITE_COMPOSER_PARITY_PLAN.md) — 与 SimpleComposer 差距分析
|
||||
|
||||
+19
-19
@@ -1,19 +1,19 @@
|
||||
# SimpleLite 面向咪咕平台数据 API 文档
|
||||
# Simple3 面向咪咕平台数据 API 文档
|
||||
|
||||
> **版本**:与 SimpleLite 源码同步
|
||||
> **版本**:与 Simple3 源码同步
|
||||
> **更新日期**:2026-06-18
|
||||
> **服务栈**:EmbedIO WebApi(非 ASP.NET Core)
|
||||
> **OpenAPI**:`Docs/openapi/simplelite-projection.json`(已在 MiGu.Server Swagger UI 中展示)
|
||||
> **OpenAPI**:`Docs/openapi/simple3-projection.json`(已在 MiGu.Server Swagger UI 中展示)
|
||||
|
||||
---
|
||||
|
||||
## 1. 架构与访问路径
|
||||
|
||||
SimpleLite 在进程内启动 HTTP 投影服务,默认监听 `http://127.0.0.1:8222`。咪咕平台后端 **MiGu.Server** 通过 YARP 反向代理将请求转发到 SimpleLite。
|
||||
Simple3 在进程内启动 HTTP 投影服务,默认监听 `http://127.0.0.1:8222`。咪咕平台后端 **MiGu.Server** 通过 YARP 反向代理将请求转发到 Simple3。
|
||||
|
||||
| 层级 | 前缀 | 说明 |
|
||||
|------|------|------|
|
||||
| 直连 SimpleLite | `http://127.0.0.1:8222/projection/...` | 开发调试、本机回环 |
|
||||
| 直连 Simple3 | `http://127.0.0.1:8222/projection/...` | 开发调试、本机回环 |
|
||||
| 经 MiGu.Server | `http://{host}:8080/api/sl/projection/...` | 平台前端/运维正式入口 |
|
||||
|
||||
```
|
||||
@@ -23,7 +23,7 @@ SimpleLite 在进程内启动 HTTP 投影服务,默认监听 `http://127.0.0.1
|
||||
MiGu.Server :8080 (/api/sl/*)
|
||||
│ 追加 X-Platform-Internal-Token
|
||||
▼
|
||||
SimpleLite :8222 (/projection/*)
|
||||
Simple3 :8222 (/projection/*)
|
||||
```
|
||||
|
||||
### 1.1 鉴权
|
||||
@@ -31,12 +31,12 @@ SimpleLite :8222 (/projection/*)
|
||||
| 场景 | 要求 |
|
||||
|------|------|
|
||||
| 经 MiGu.Server 访问 | 需登录 JWT(`Authorization: Bearer` 或 Cookie `simple.auth.token`);YARP 自动注入 `X-Platform-Internal-Token` |
|
||||
| 直连 SimpleLite | 本机回环(127.0.0.1 / ::1)默认放行;远程需携带与 MiGu.Server 共享的 `X-Platform-Internal-Token` |
|
||||
| 直连 Simple3 | 本机回环(127.0.0.1 / ::1)默认放行;远程需携带与 MiGu.Server 共享的 `X-Platform-Internal-Token` |
|
||||
|
||||
Token 解析优先级(SimpleLite 侧):
|
||||
Token 解析优先级(Simple3 侧):
|
||||
|
||||
1. `simple.json` → `platform.internalToken`
|
||||
2. 环境变量 `SIMPLELITE__PLATFORM__INTERNALTOKEN`
|
||||
2. 环境变量 `SIMPLE3__PLATFORM__INTERNALTOKEN`
|
||||
3. `Platform.Server/data/.internal-token` 或 `MiGu.Server/data/.internal-token`
|
||||
|
||||
### 1.2 通用响应格式
|
||||
@@ -60,8 +60,8 @@ Token 解析优先级(SimpleLite 侧):
|
||||
|
||||
开发环境下启动 MiGu.Server 后访问:
|
||||
|
||||
- **统一文档**:`http://localhost:8080/swagger`(含 MiGu.Server 自有 API + SimpleLite 全部 WebApi)
|
||||
- SimpleLite 相关接口标签前缀为 `SimpleLite/`
|
||||
- **统一文档**:`http://localhost:8080/swagger`(含 MiGu.Server 自有 API + Simple3 全部 WebApi)
|
||||
- Simple3 相关接口标签前缀为 `Simple3/`
|
||||
|
||||
---
|
||||
|
||||
@@ -112,7 +112,7 @@ Token 解析优先级(SimpleLite 侧):
|
||||
{
|
||||
"id": "C01",
|
||||
"name": "AGV-1",
|
||||
"typeName": "SimpleLite.RCS.CarTypes.Car",
|
||||
"typeName": "Simple3.RCS.CarTypes.Car",
|
||||
"rawId": 1,
|
||||
"x": 1200.5,
|
||||
"y": 800.0,
|
||||
@@ -285,7 +285,7 @@ Token 解析优先级(SimpleLite 侧):
|
||||
|
||||
同一车型若多个 Coder 引用不同 Fields 类型,接口会合并后取**最派生、字段最全**的类型(如 Kiva 取 `KivaTrackFields` 而非 `BasicTrackFields`)。返回的 `fields` 数组**包含继承链上全部 public 字段**(例如 `KivaTrackFields` 会同时包含 `BasicTrackFields` 的 `IOArea`、`Speed` 等基类字段)。
|
||||
|
||||
> **实现说明**:Fields 类在插件程序集中多为 `internal`;插件已通过 `InternalsVisibleTo("SimpleLite")` 向 SimpleLite 开放。**插件在 collectible ALC 中加载时**,SimpleCore 特性类型与宿主不一致,须通过 `CustomAttributeData` 读取 Coder 上的 `siteFields`/`trackFields` 等 metadata(不能依赖 `GetCustomAttributes(typeof(TemplateTrackCoderSettings))`)。`StandardScene.dll` 内 Fields 由 `CoderFieldsMetadata.Describe` 在 Core 程序集内反射导出。
|
||||
> **实现说明**:Fields 类在插件程序集中多为 `internal`;插件已通过 `InternalsVisibleTo("Simple3")` 向 Simple3 开放。**插件在 collectible ALC 中加载时**,SimpleCore 特性类型与宿主不一致,须通过 `CustomAttributeData` 读取 Coder 上的 `siteFields`/`trackFields` 等 metadata(不能依赖 `GetCustomAttributes(typeof(TemplateTrackCoderSettings))`)。`StandardScene.dll` 内 Fields 由 `CoderFieldsMetadata.Describe` 在 Core 程序集内反射导出。
|
||||
|
||||
---
|
||||
|
||||
@@ -467,7 +467,7 @@ Token 解析优先级(SimpleLite 侧):
|
||||
|
||||
咪咕 Vue 前端封装位于 `Migu2.0/frontends/apps/simple-platform-vue/src/api/`:
|
||||
|
||||
| 文件 | 对应 SimpleLite 前缀 |
|
||||
| 文件 | 对应 Simple3 前缀 |
|
||||
|------|----------------------|
|
||||
| `reflection.ts` | `/api/sl/projection/reflection` |
|
||||
| `mapEdit.ts` | `/api/sl/projection/map-edit` |
|
||||
@@ -480,16 +480,16 @@ Token 解析优先级(SimpleLite 侧):
|
||||
|
||||
| 服务 | 默认端口 | 路径 |
|
||||
|------|----------|------|
|
||||
| MiGu.Server | 8080 | `/api/sl/*` → SimpleLite |
|
||||
| SimpleLite 投影 API | 8222 | `/projection/*` |
|
||||
| SimpleLite webVRender | 8223 | `/vr/*`(3D 嵌入 UI,非数据 API) |
|
||||
| MiGu.Server | 8080 | `/api/sl/*` → Simple3 |
|
||||
| Simple3 投影 API | 8222 | `/projection/*` |
|
||||
| Simple3 webVRender | 8223 | `/vr/*`(3D 嵌入 UI,非数据 API) |
|
||||
|
||||
**健康检查**:`GET http://localhost:8080/api/health/simplelite`
|
||||
**健康检查**:`GET http://localhost:8080/api/health/simple3`
|
||||
|
||||
**本地联调**:
|
||||
|
||||
```powershell
|
||||
# 1. 启动 SimpleLite(或登录 MiGu.Server 后自动拉起)
|
||||
# 1. 启动 Simple3(或登录 MiGu.Server 后自动拉起)
|
||||
# 2. 启动 MiGu.Server
|
||||
# 3. 浏览器打开 http://localhost:8080/swagger
|
||||
# 4. 授权 Bearer token 后测试 /api/sl/projection/cars
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
`MIGU2.0` 是“迷毂 · 智能调度平台”的平台仓库,主要包含两部分:
|
||||
|
||||
- 平台后端:`MiGu.Server`,ASP.NET Core 8 WebAPI,监听默认 `8080`,负责登录、权限、配置中心、静态前端托管、YARP 反向代理和 SimpleLite 子进程拉起。
|
||||
- 平台后端:`MiGu.Server`,ASP.NET Core 8 WebAPI,监听默认 `8080`,负责登录、权限、配置中心、静态前端托管、YARP 反向代理和 Simple3 子进程拉起。
|
||||
- 平台前端:`frontends/apps/simple-platform-vue`,Vue 3 + Vite + Pinia + Element Plus 单页应用,单工程承载管理员端 `/admin/*` 与运营端 `/monitor/*`。
|
||||
|
||||
调度内核 `SimpleLite` 不在本仓库内,按 README 描述位于相邻仓库 `../Simple/SimpleLite`。当前仓库通过进程拉起、HTTP 代理、webVRender iframe 和本地文件读取等方式与 `SimpleLite` 协作。
|
||||
调度内核 `Simple3` 不在本仓库内,按 README 描述位于相邻仓库 `../Simple/Simple3`。当前仓库通过进程拉起、HTTP 代理、webVRender iframe 和本地文件读取等方式与 `Simple3` 协作。
|
||||
|
||||
## 2. 技术栈总览
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- 授权:ASP.NET Core Authorization Policy + 自研 RBAC。
|
||||
- 代理:YARP Reverse Proxy。
|
||||
- 持久化:当前为 JSON 文件持久化,主要落在 `MiGu.Server/data/`;长期蓝图中计划接入 EF Core 多数据库 Provider。
|
||||
- 进程编排:`SimpleLiteLauncher` 拉起或复用 `SimpleLite.exe`。
|
||||
- 进程编排:`Simple3Launcher` 拉起或复用 `Simple3.exe`。
|
||||
|
||||
### 前端
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
|
||||
### 外部/相邻系统
|
||||
|
||||
- `SimpleLite.exe`:调度内核、地图/任务/车辆领域能力、Projection API、webVRender。
|
||||
- `Simple3.exe`:调度内核、地图/任务/车辆领域能力、Projection API、webVRender。
|
||||
- webVRender:默认 `8223`,由前端 `Workspace3D.vue` 以 iframe 方式嵌入。
|
||||
- SimpleLite Projection/Web API:默认 `8222`,通过 `/api/sl/*` 由 YARP 转发。
|
||||
- Simple3 Projection/Web API:默认 `8222`,通过 `/api/sl/*` 由 YARP 转发。
|
||||
|
||||
## 3. 顶层目录职责
|
||||
|
||||
@@ -64,33 +64,33 @@ flowchart LR
|
||||
Server --> Auth["Auth/RBAC/JWT"]
|
||||
Server --> Config["ConfigStore<br/>data/config-*.json"]
|
||||
Server --> Wizard["部署向导<br/>deployment profile"]
|
||||
Server --> Launcher["SimpleLiteLauncher"]
|
||||
Launcher -->|登录后按 launchMode 拉起/复用| SL["SimpleLite.exe<br/>相邻仓库"]
|
||||
Server -->|YARP /api/sl/*| SLApi["SimpleLite :8222"]
|
||||
Server --> Launcher["Simple3Launcher"]
|
||||
Launcher -->|登录后按 launchMode 拉起/复用| SL["Simple3.exe<br/>相邻仓库"]
|
||||
Server -->|YARP /api/sl/*| SLApi["Simple3 :8222"]
|
||||
Server -->|YARP /vr/*| VR["webVRender :8223"]
|
||||
Vue -->|iframe 或 /vr 代理| VR
|
||||
```
|
||||
|
||||
关键说明:
|
||||
|
||||
- `MiGu.Server` 负责托管 SPA、处理登录与配置、代理 `/api/sl/*` 到 SimpleLite。
|
||||
- 用户登录时可选择 `DesktopAndWeb` 或 `WebOnly`,后端转换为 SimpleLite 命令行 `--display-mode=web+local` 或 `--display-mode=web`。
|
||||
- `SimpleLiteLauncher` 默认 `FollowParent=false`,即 `MiGu.Server` 退出不会杀掉已启动的 SimpleLite。
|
||||
- 如果 `8222` 已经有 SimpleLite 在运行,后端会做 TCP + HTTP 探测并复用既有实例,避免重复拉起。
|
||||
- `MiGu.Server` 负责托管 SPA、处理登录与配置、代理 `/api/sl/*` 到 Simple3。
|
||||
- 用户登录时可选择 `DesktopAndWeb` 或 `WebOnly`,后端转换为 Simple3 命令行 `--display-mode=web+local` 或 `--display-mode=web`。
|
||||
- `Simple3Launcher` 默认 `FollowParent=false`,即 `MiGu.Server` 退出不会杀掉已启动的 Simple3。
|
||||
- 如果 `8222` 已经有 Simple3 在运行,后端会做 TCP + HTTP 探测并复用既有实例,避免重复拉起。
|
||||
- 前端的 3D/地图画布主要通过 `Workspace3D.vue` 加载 `http://localhost:8223` 或经 `/vr` 同源代理。
|
||||
|
||||
## 5. 文档蓝图与当前代码的差异
|
||||
|
||||
仓库中存在两类叙述:
|
||||
|
||||
- `README.md` 和 `MiGu.Server/README.md` 已经描述“用户先启动 `MiGu.Server`,登录后拉起 `SimpleLite`”这一当前实现。
|
||||
- `ARCHITECTURE.md` 仍保留较多早期蓝图,例如“SimpleLite 通过 SystemMission 拉起 MiGu.Server”“双 SPA 独立托管”“SimpleShared.* 多工程”“EF Core 多 Provider”等。
|
||||
- `README.md` 和 `MiGu.Server/README.md` 已经描述“用户先启动 `MiGu.Server`,登录后拉起 `Simple3`”这一当前实现。
|
||||
- `ARCHITECTURE.md` 仍保留较多早期蓝图,例如“Simple3 通过 SystemMission 拉起 MiGu.Server”“双 SPA 独立托管”“SimpleShared.* 多工程”“EF Core 多 Provider”等。
|
||||
|
||||
当前代码落地状态:
|
||||
|
||||
- 已落地:`MiGu.Server` 主入口、登录拉起 SimpleLite、YARP 代理、JWT/Cookie 鉴权、RBAC、配置中心 JSON 持久化、部署向导、日志管理、地图 JSON 预览、单 Vue SPA 管理/运营双域。
|
||||
- 部分落地:配置中心模型、运维白名单、SimpleLite 反射下发、webVRender 嵌入、地图编辑/监控前端能力。
|
||||
- 尚未在本仓库落地:`SimpleShared.*` 工程、EF Core 多数据库持久层、SystemMission 由 SimpleLite 反向守护平台、独立 `platform-vue`/`rcsmonitor-vue` 双工程、`packages/sl-controls` 共享组件库。
|
||||
- 已落地:`MiGu.Server` 主入口、登录拉起 Simple3、YARP 代理、JWT/Cookie 鉴权、RBAC、配置中心 JSON 持久化、部署向导、日志管理、地图 JSON 预览、单 Vue SPA 管理/运营双域。
|
||||
- 部分落地:配置中心模型、运维白名单、Simple3 反射下发、webVRender 嵌入、地图编辑/监控前端能力。
|
||||
- 尚未在本仓库落地:`SimpleShared.*` 工程、EF Core 多数据库持久层、SystemMission 由 Simple3 反向守护平台、独立 `platform-vue`/`rcsmonitor-vue` 双工程、`packages/sl-controls` 共享组件库。
|
||||
|
||||
## 6. 后端模块
|
||||
|
||||
@@ -101,14 +101,14 @@ flowchart LR
|
||||
- 修正 `ContentRootPath`:直接运行 `bin/Debug/net8.0/MiGu.Server.exe` 时向上寻找 `MiGu.Server.csproj`,确保配置、data、wwwroot 路径一致。
|
||||
- 默认监听:未配置 `urls` 时使用 `http://0.0.0.0:8080`。
|
||||
- WebRoot 自动探测:优先使用 `frontends/apps/simple-platform-vue/dist/index.html`,其次 `MiGu.Server/wwwroot/index.html`。
|
||||
- 注册 Controller、Swagger、CORS、JWT Bearer、Authorization Policy、YARP、ConfigStore、OpsAuditStore、HttpClient、SimpleLiteLauncher。
|
||||
- 注册 Controller、Swagger、CORS、JWT Bearer、Authorization Policy、YARP、ConfigStore、OpsAuditStore、HttpClient、Simple3Launcher。
|
||||
- 设置 Forwarded Headers,兼容反向代理后的 HTTPS Cookie。
|
||||
- 托管静态资源和 SPA fallback。
|
||||
|
||||
核心关系:
|
||||
|
||||
- 调用 `JwtIssuer` 颁发/验签 JWT。
|
||||
- 调用 `InternalTokenStore` 管理转发给 SimpleLite 的 `X-Platform-Internal-Token`。
|
||||
- 调用 `InternalTokenStore` 管理转发给 Simple3 的 `X-Platform-Internal-Token`。
|
||||
- YARP `sl-route` 会自动注入内部 token。
|
||||
- `app.MapControllers()` 处理平台自身 API,`app.MapReverseProxy()` 处理 `/api/sl/*` 与 `/vr/*`。
|
||||
|
||||
@@ -116,7 +116,7 @@ flowchart LR
|
||||
|
||||
职责:
|
||||
|
||||
- `POST /api/auth/login`:校验用户名密码与 scope,按登录请求 `launchMode` 拉起 SimpleLite,签发 JWT 和 Cookie。
|
||||
- `POST /api/auth/login`:校验用户名密码与 scope,按登录请求 `launchMode` 拉起 Simple3,签发 JWT 和 Cookie。
|
||||
- `POST /api/auth/logout`:清理 Cookie。
|
||||
- `GET /api/auth/me`:让前端对本地 token 进行服务端实校。
|
||||
- `POST /api/auth/switch-scope`:同一用户在 `Platform` 与 `RCSMonitor` 之间切换 scope,并由服务端重新计算权限。
|
||||
@@ -125,7 +125,7 @@ flowchart LR
|
||||
|
||||
- `RbacStore`:用户密码校验、scope 判断、有效权限计算。
|
||||
- `JwtIssuer`:签发 JWT。
|
||||
- `SimpleLiteLauncher`:登录后拉起或复用 SimpleLite。
|
||||
- `Simple3Launcher`:登录后拉起或复用 Simple3。
|
||||
- `ConfigStore`:读取部署画像,决定是否需要进入配置向导。
|
||||
|
||||
### 6.3 RBAC 权限模块:`Auth/`
|
||||
@@ -136,7 +136,7 @@ flowchart LR
|
||||
- `RbacModels.cs`:用户、角色、DTO。
|
||||
- `PageCatalog.cs`:页面权限目录。
|
||||
- `JwtIssuer.cs`:JWT 颁发和验签参数。
|
||||
- `InternalTokenStore.cs`:平台与 SimpleLite 内部通信 token。
|
||||
- `InternalTokenStore.cs`:平台与 Simple3 内部通信 token。
|
||||
|
||||
职责:
|
||||
|
||||
@@ -182,32 +182,32 @@ flowchart LR
|
||||
|
||||
- 前端 `useConfigStore` 统一读写这些 section。
|
||||
- 许多配置页只是不同 section 的编辑视图。
|
||||
- `deployment` section 还会参与菜单裁剪与 SimpleLite 插件选择。
|
||||
- `deployment` section 还会参与菜单裁剪与 Simple3 插件选择。
|
||||
|
||||
注意:
|
||||
|
||||
- 控制器类加了 `[Authorize]`,但当前 `PUT` 也是普通 `[Authorize]`,注释中曾提到 PlatformScope,实际代码没有强制仅平台 scope 可写。
|
||||
- 当前 JSON 文件属于占位持久层;长期架构文档计划迁移到 EF Core。
|
||||
|
||||
### 6.5 SimpleLite 拉起与诊断:`Launcher/`
|
||||
### 6.5 Simple3 拉起与诊断:`Launcher/`
|
||||
|
||||
主要文件:
|
||||
|
||||
- `SimpleLiteLauncher.cs`:拉起、复用、重启、诊断、插件选择联动。
|
||||
- `SimpleLiteOptions.cs`:绑定 `appsettings.json:SimpleLite`。
|
||||
- `SimpleLiteBuildSync.cs`:辅助同步/探测 SimpleLite 构建与 API。
|
||||
- `Simple3Launcher.cs`:拉起、复用、重启、诊断、插件选择联动。
|
||||
- `Simple3Options.cs`:绑定 `appsettings.json:Simple3`。
|
||||
- `Simple3BuildSync.cs`:辅助同步/探测 Simple3 构建与 API。
|
||||
|
||||
职责:
|
||||
|
||||
- 将业务启动模式翻译为 SimpleLite 内部 display mode:
|
||||
- 将业务启动模式翻译为 Simple3 内部 display mode:
|
||||
- `WebOnly` -> `--display-mode=web`
|
||||
- `DesktopAndWeb` -> `--display-mode=web+local`
|
||||
- 拉起前检查 `8222` 是否已有 SimpleLite,并用 `/projection/cars` 确认不是其他进程占用端口。
|
||||
- 查找 `SimpleLite.exe`:优先配置路径,再尝试相邻仓库、发布包同目录等候选路径。
|
||||
- 默认 Windows 下通过 `cmd /c start` 脱离父进程,避免关闭平台后端时带走 SimpleLite。
|
||||
- 拉起前检查 `8222` 是否已有 Simple3,并用 `/projection/cars` 确认不是其他进程占用端口。
|
||||
- 查找 `Simple3.exe`:优先配置路径,再尝试相邻仓库、发布包同目录等候选路径。
|
||||
- 默认 Windows 下通过 `cmd /c start` 脱离父进程,避免关闭平台后端时带走 Simple3。
|
||||
- `FollowParent=true` 时使用 Windows JobObject 绑定父子进程。
|
||||
- 写入 `plugins/active-scenes.json`,把部署向导选出的导航场景传给 SimpleLite。
|
||||
- `GET /api/health/simplelite` 返回配置、路径解析、端口、版本 API 可用性等诊断信息。
|
||||
- 写入 `plugins/active-scenes.json`,把部署向导选出的导航场景传给 Simple3。
|
||||
- `GET /api/health/simple3` 返回配置、路径解析、端口、版本 API 可用性等诊断信息。
|
||||
|
||||
### 6.6 运维白名单与审计:`OpsController` + `OpsAuditStore`
|
||||
|
||||
@@ -217,7 +217,7 @@ flowchart LR
|
||||
- 二次校验 JWT 的 `ops` claim。
|
||||
- 支持 `IdempotencyKey`,避免重复点击/重试重复下发。
|
||||
- `monitor.note.write` 仅写审计。
|
||||
- 其他运维动作需要在 `appsettings.json:Ops:Dispatch` 显式配置 `opCode -> "kind:Method"` 后,才会转发到 SimpleLite 反射 API。
|
||||
- 其他运维动作需要在 `appsettings.json:Ops:Dispatch` 显式配置 `opCode -> "kind:Method"` 后,才会转发到 Simple3 反射 API。
|
||||
- 审计记录由 `OpsAuditStore` 落盘,重启后不丢。
|
||||
|
||||
默认白名单:
|
||||
@@ -229,9 +229,9 @@ flowchart LR
|
||||
重要设计:
|
||||
|
||||
- 未配置映射时不会“假成功”,而是返回 `ok=false` 并说明“已记录审计但未下发”。
|
||||
- 真实下发路径为 SimpleLite 反射执行接口:`/projection/reflection/execute/{kind}/{id}/{method}`。
|
||||
- 真实下发路径为 Simple3 反射执行接口:`/projection/reflection/execute/{kind}/{id}/{method}`。
|
||||
|
||||
### 6.7 投影与 SimpleLite 代理
|
||||
### 6.7 投影与 Simple3 代理
|
||||
|
||||
模块组成:
|
||||
|
||||
@@ -249,8 +249,8 @@ flowchart LR
|
||||
职责:
|
||||
|
||||
- `GET /api/health`:平台后端健康检查,返回启动时间、运行时长、端口规划。
|
||||
- `GET /api/health/simplelite`:SimpleLite 路径和端口诊断。
|
||||
- `POST /api/health/simplelite/restart-for-update`:授权用户可触发关闭 SimpleLite、同步最新 DLL 并重新拉起。
|
||||
- `GET /api/health/simple3`:Simple3 路径和端口诊断。
|
||||
- `POST /api/health/simple3/restart-for-update`:授权用户可触发关闭 Simple3、同步最新 DLL 并重新拉起。
|
||||
|
||||
### 6.9 部署配置向导:`WizardController` + `DeploymentCatalog`
|
||||
|
||||
@@ -258,7 +258,7 @@ flowchart LR
|
||||
|
||||
- 首次部署时收集导航方式、功能模块、业务场景。
|
||||
- 保存后写入 `deployment` 配置 section。
|
||||
- 将导航方式映射为 SimpleLite 场景插件 ID,并调用 `SimpleLiteLauncher.WriteActiveScenes()` 写入 `plugins/active-scenes.json`。
|
||||
- 将导航方式映射为 Simple3 场景插件 ID,并调用 `Simple3Launcher.WriteActiveScenes()` 写入 `plugins/active-scenes.json`。
|
||||
- 按部署画像裁剪页面,例如当前 `wms` 模块会点亮 `admin-config-location`。
|
||||
|
||||
对外 API:
|
||||
@@ -273,7 +273,7 @@ flowchart LR
|
||||
|
||||
职责:
|
||||
|
||||
- 读取 SimpleLite 工作目录下的 `log/` 文件。
|
||||
- 读取 Simple3 工作目录下的 `log/` 文件。
|
||||
- 支持概览、文件列表、目录浏览、日志条目分页、合订本、日志分析、原文查看、下载。
|
||||
- 日志行按 `Diagnosis.Log` 的格式解析,支持标签聚合和数值字段识别。
|
||||
|
||||
@@ -290,14 +290,14 @@ flowchart LR
|
||||
|
||||
职责:
|
||||
|
||||
- 先调用 SimpleLite `/projection/map-edit/maps` 获取地图目录。
|
||||
- 先调用 Simple3 `/projection/map-edit/maps` 获取地图目录。
|
||||
- 再在本机读取对应地图 JSON 原文,供平台地图管理右侧预览。
|
||||
- 对地图名做非法字符检查,并校验最终路径必须落在地图目录下。
|
||||
|
||||
关系:
|
||||
|
||||
- 依赖 SimpleLite 与 MiGu.Server 同机部署。
|
||||
- 依赖 `SimpleLiteOptions.ProjectionPort` 和 `InternalTokenStore`。
|
||||
- 依赖 Simple3 与 MiGu.Server 同机部署。
|
||||
- 依赖 `Simple3Options.ProjectionPort` 和 `InternalTokenStore`。
|
||||
|
||||
## 7. 前端模块
|
||||
|
||||
@@ -328,7 +328,7 @@ flowchart LR
|
||||
|
||||
职责:
|
||||
|
||||
- 登录页采集用户名、密码、scope 和 SimpleLite 启动模式。
|
||||
- 登录页采集用户名、密码、scope 和 Simple3 启动模式。
|
||||
- 登录成功后保存 token、用户、scope、runMode、effectivePermissions 到 Pinia 与 localStorage。
|
||||
- `auth.validate()` 通过 `/api/auth/me` 处理服务端重启、JWT secret 重生导致的旧 token 失效问题。
|
||||
- `switchScope()` 不在客户端伪造权限,而是请求后端重新签发 token 和权限。
|
||||
@@ -346,7 +346,7 @@ flowchart LR
|
||||
- `api/config.ts`:配置中心读写。
|
||||
- `api/projection.ts`:站点、路径、车辆、任务投影。
|
||||
- `api/ops.ts`:运维白名单执行和审计。
|
||||
- `api/reflection.ts`:SimpleLite 反射对象、字段、方法执行。
|
||||
- `api/reflection.ts`:Simple3 反射对象、字段、方法执行。
|
||||
- `api/mapEdit.ts`:地图编辑相关 API。
|
||||
- `api/wizard.ts`:部署向导。
|
||||
- `api/logs.ts`:日志管理。
|
||||
@@ -354,7 +354,7 @@ flowchart LR
|
||||
关系:
|
||||
|
||||
- 所有平台 API 都以 `/api` 为 baseURL。
|
||||
- `/api/sl/*` 由 MiGu.Server 反代给 SimpleLite。
|
||||
- `/api/sl/*` 由 MiGu.Server 反代给 Simple3。
|
||||
- 开发环境可通过 `VITE_USE_MOCK=true` 使用前端 mock,但反射、工作台等部分 API 仍要求真实后端。
|
||||
|
||||
### 7.4 Shell、主题和权限控制组件
|
||||
@@ -418,7 +418,7 @@ flowchart LR
|
||||
- `useHistory`:撤销/重做命令栈。
|
||||
- `useSelection`:对象选择状态。
|
||||
- `useAlignment`、`useBatchGenerate`、`useClipboard`:编辑辅助能力。
|
||||
- `mapEditApi`、`reflectionApi`:与 SimpleLite 的地图编辑和反射 API 通信。
|
||||
- `mapEditApi`、`reflectionApi`:与 Simple3 的地图编辑和反射 API 通信。
|
||||
|
||||
#### 地图监控
|
||||
|
||||
@@ -431,7 +431,7 @@ flowchart LR
|
||||
- `VehicleMonitorPanel`:车辆监控台。
|
||||
- `MissionListPanel`:任务列表。
|
||||
- `MonitorSelectionPanel`:选中对象详情和动作。
|
||||
- `useProjectionStream`:订阅 SimpleLite/平台 SSE 事件。
|
||||
- `useProjectionStream`:订阅 Simple3/平台 SSE 事件。
|
||||
|
||||
### 7.7 运营端页面:`/monitor/*`
|
||||
|
||||
@@ -469,13 +469,13 @@ flowchart LR
|
||||
- 选择导航方式:磁导航、二维码导航、激光导航。
|
||||
- 选择功能模块:当前包含 WMS、PTL。
|
||||
- 选择业务场景模板。
|
||||
- 预览将激活的 SimpleLite 场景插件,如 `scene.magnetic`、`scene.qrcode`、`scene.laser`。
|
||||
- 预览将激活的 Simple3 场景插件,如 `scene.magnetic`、`scene.qrcode`、`scene.laser`。
|
||||
|
||||
保存后:
|
||||
|
||||
- 调用 `PUT /api/wizard/profile`。
|
||||
- 后端写入 `deployment` section。
|
||||
- 后端写入 SimpleLite `plugins/active-scenes.json`。
|
||||
- 后端写入 Simple3 `plugins/active-scenes.json`。
|
||||
- 前端调用 `auth.markWizardDone()`,跳转到对应首页。
|
||||
|
||||
## 8. 数据与持久化
|
||||
@@ -491,9 +491,9 @@ flowchart LR
|
||||
|
||||
这些文件采用 JSON 持久化,部分写入走 `AtomicFile`,损坏时会备份后回退默认值。
|
||||
|
||||
### SimpleLite 侧文件
|
||||
### Simple3 侧文件
|
||||
|
||||
MiGu.Server 会直接或间接使用 SimpleLite 工作目录:
|
||||
MiGu.Server 会直接或间接使用 Simple3 工作目录:
|
||||
|
||||
- `plugins/active-scenes.json`:部署向导写入,控制内核场景插件选择。
|
||||
- `log/**/*.log`:日志管理模块读取并分析。
|
||||
@@ -511,7 +511,7 @@ MiGu.Server 会直接或间接使用 SimpleLite 工作目录:
|
||||
- `/api/maps/{name}/content`:地图 JSON 预览。
|
||||
- `/api/health/*`:健康检查。
|
||||
|
||||
### 平台后端到 SimpleLite
|
||||
### 平台后端到 Simple3
|
||||
|
||||
- `/api/sl/*` -> YARP -> `http://127.0.0.1:8222/*`。
|
||||
- `/vr/*` -> YARP -> `http://127.0.0.1:8223/*`。
|
||||
@@ -533,7 +533,7 @@ flowchart TB
|
||||
RBAC["RbacStore / JwtIssuer"]
|
||||
Config["ConfigStore"]
|
||||
Wizard["WizardController"]
|
||||
Launcher["SimpleLiteLauncher"]
|
||||
Launcher["Simple3Launcher"]
|
||||
Ops["OpsController"]
|
||||
Logs["LogsController"]
|
||||
Maps["MapsContentController"]
|
||||
@@ -551,8 +551,8 @@ flowchart TB
|
||||
WizardVue["WizardView.vue"]
|
||||
end
|
||||
|
||||
subgraph SimpleLite["SimpleLite 相邻仓库"]
|
||||
SLExe["SimpleLite.exe"]
|
||||
subgraph Simple3["Simple3 相邻仓库"]
|
||||
SLExe["Simple3.exe"]
|
||||
Projection["Projection API :8222"]
|
||||
VRender["webVRender :8223"]
|
||||
LogsFile["log/**/*.log"]
|
||||
@@ -591,14 +591,14 @@ flowchart TB
|
||||
|
||||
## 11. 典型业务链路
|
||||
|
||||
### 11.1 登录并启动 SimpleLite
|
||||
### 11.1 登录并启动 Simple3
|
||||
|
||||
1. 用户打开 `/login`。
|
||||
2. 前端提交用户名、密码、scope、launchMode 到 `/api/auth/login`。
|
||||
3. `AuthController` 调 `RbacStore.VerifyCredentials()` 校验密码。
|
||||
4. `AuthController` 检查用户是否可使用请求的 scope。
|
||||
5. `AuthController` 调 `SimpleLiteLauncher.MaybeStart()`。
|
||||
6. `SimpleLiteLauncher` 复用或拉起 `SimpleLite.exe`。
|
||||
5. `AuthController` 调 `Simple3Launcher.MaybeStart()`。
|
||||
6. `Simple3Launcher` 复用或拉起 `Simple3.exe`。
|
||||
7. 后端计算有效权限,签发 JWT,写 httpOnly Cookie。
|
||||
8. 前端保存登录态,并按 `needsWizard` 跳转 `/wizard` 或业务首页。
|
||||
|
||||
@@ -608,7 +608,7 @@ flowchart TB
|
||||
2. 守卫确保 scope 为 `Platform`,并检查 `admin-map-editor` 页面权限。
|
||||
3. `MapEditorView` 加载 `Workspace3D`,以 `canvasOnly=true` 打开 `8223` webVRender。
|
||||
4. Vue 侧顶栏、工具栏、属性面板接管编辑 UI。
|
||||
5. 创建、删除、批量生成、字段修改等操作通过 `mapEditApi` 或 `reflectionApi` 调用 SimpleLite。
|
||||
5. 创建、删除、批量生成、字段修改等操作通过 `mapEditApi` 或 `reflectionApi` 调用 Simple3。
|
||||
6. `useHistory` 记录可回滚命令,部分操作会用快照恢复。
|
||||
|
||||
### 11.3 运营端执行运维动作
|
||||
@@ -618,7 +618,7 @@ flowchart TB
|
||||
3. 用户确认后调用 `/api/sl/ops/execute`。
|
||||
4. `OpsController` 校验白名单、JWT 操作码、幂等键。
|
||||
5. 若是备注,则只写审计;若是内核动作,读取 `Ops:Dispatch` 映射。
|
||||
6. 已配置映射时调用 SimpleLite 反射 execute;未配置映射时返回未下发。
|
||||
6. 已配置映射时调用 Simple3 反射 execute;未配置映射时返回未下发。
|
||||
7. 审计落盘,前端显示执行结果。
|
||||
|
||||
### 11.4 首次部署配置向导
|
||||
@@ -628,7 +628,7 @@ flowchart TB
|
||||
3. 前端加载 `/api/wizard/options` 和 `/api/wizard/profile`。
|
||||
4. 用户选择导航方式、模块、场景。
|
||||
5. 保存后端写 `deployment` section。
|
||||
6. 后端将导航方式转换为场景插件 ID,并写 SimpleLite `plugins/active-scenes.json`。
|
||||
6. 后端将导航方式转换为场景插件 ID,并写 Simple3 `plugins/active-scenes.json`。
|
||||
7. 后续登录时,`AuthController.BuildSession()` 会按部署画像裁剪可见页面。
|
||||
|
||||
## 12. 构建与运行
|
||||
@@ -645,7 +645,7 @@ dotnet run
|
||||
|
||||
- 平台:`http://localhost:8080/login`
|
||||
- 健康检查:`http://localhost:8080/api/health`
|
||||
- SimpleLite 诊断:`http://localhost:8080/api/health/simplelite`
|
||||
- Simple3 诊断:`http://localhost:8080/api/health/simple3`
|
||||
- Swagger:开发环境 `/swagger`
|
||||
|
||||
### 前端开发
|
||||
@@ -679,7 +679,7 @@ pnpm dev
|
||||
- 架构文档与当前实现存在历史差异,后续应更新 `ARCHITECTURE.md` 中关于启动归属、双 SPA 拆分、共享组件库和持久层状态的描述。
|
||||
- `ConfigController.Put` 当前只要求登录,未按注释限制 `PlatformScope`,如果运营 scope 可调用配置写入,需要确认是否符合预期。
|
||||
- 前端登录页仍有 Mock 文案,真实 API 模式下会误导用户。
|
||||
- `ServiceStatusView.vue` 目前展示大量 Mock 状态,尚未接入 `/api/health` 与 `/api/health/simplelite` 的真实数据。
|
||||
- `ServiceStatusView.vue` 目前展示大量 Mock 状态,尚未接入 `/api/health` 与 `/api/health/simple3` 的真实数据。
|
||||
- `ProjectionController` 是本地 Mock,占位意义大于生产意义,真实链路依赖 `/api/sl/*`。
|
||||
- 运维白名单默认没有 `Ops:Dispatch` 映射,未配置前只会审计,不会真实下发内核动作。
|
||||
- 多数据库、EF Core、SystemMission、独立前端包和共享组件库仍是蓝图,不应被外部交付文档表述为已完成。
|
||||
@@ -687,8 +687,8 @@ pnpm dev
|
||||
## 14. 快速索引
|
||||
|
||||
- 后端启动:`MiGu.Server/Program.cs`
|
||||
- 登录和启动 SimpleLite:`MiGu.Server/Controllers/AuthController.cs`
|
||||
- SimpleLite 拉起器:`MiGu.Server/Launcher/SimpleLiteLauncher.cs`
|
||||
- 登录和启动 Simple3:`MiGu.Server/Controllers/AuthController.cs`
|
||||
- Simple3 拉起器:`MiGu.Server/Launcher/Simple3Launcher.cs`
|
||||
- 配置中心:`MiGu.Server/Configs/ConfigStore.cs`
|
||||
- 权限中心:`MiGu.Server/Auth/RbacStore.cs`
|
||||
- 权限 API:`MiGu.Server/Controllers/RbacController.cs`
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace MiGu.DB.Domains.Fleet;
|
||||
|
||||
/// <summary>
|
||||
/// CDM 搬运任务的平台侧快照(表 cdm_tasks)。
|
||||
/// 以任务 Id 为主键;SimpleLite/StandardScene 把终态任务从自身 JSON 里删除,这里则永久保留=完整历史。
|
||||
/// 以任务 Id 为主键;Simple3/StandardScene 把终态任务从自身 JSON 里删除,这里则永久保留=完整历史。
|
||||
/// </summary>
|
||||
public sealed class CdmTaskRecord
|
||||
{
|
||||
|
||||
@@ -3,24 +3,24 @@
|
||||
namespace MiGu.Server.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// MiGu.Server ↔ SimpleLite 8222 之间的 **内部共享 token**。
|
||||
/// MiGu.Server ↔ Simple3 8222 之间的 **内部共享 token**。
|
||||
///
|
||||
/// 设计动机:SimpleLite 8222 的 EmbedIO WebApi(ReflectionApi / PersistenceApi / MapEditApi)
|
||||
/// 历史上完全无鉴权,远程访问 = 接管调度内核。为不在 SimpleLite 侧实现完整 JWT 验签
|
||||
/// (减少 SimpleLite 复杂度),采用一个轻量约定:
|
||||
/// 设计动机:Simple3 8222 的 EmbedIO WebApi(ReflectionApi / PersistenceApi / MapEditApi)
|
||||
/// 历史上完全无鉴权,远程访问 = 接管调度内核。为不在 Simple3 侧实现完整 JWT 验签
|
||||
/// (减少 Simple3 复杂度),采用一个轻量约定:
|
||||
///
|
||||
/// - <b>本机回环</b>(127.0.0.1 / ::1)SimpleLite 直接放行(开发机直连不受影响);
|
||||
/// - <b>本机回环</b>(127.0.0.1 / ::1)Simple3 直接放行(开发机直连不受影响);
|
||||
/// - <b>其它来源</b>必须携带 <c>X-Platform-Internal-Token</c> header;
|
||||
/// - MiGu.Server YARP 反代 <c>/api/sl/*</c> 到 SimpleLite 8222 时,YARP transform
|
||||
/// - MiGu.Server YARP 反代 <c>/api/sl/*</c> 到 Simple3 8222 时,YARP transform
|
||||
/// 会自动追加该 header;
|
||||
/// - SimpleLite 端通过 <c>simple.json:platform.internalToken</c> 或环境变量
|
||||
/// <c>SIMPLELITE__PLATFORM__INTERNALTOKEN</c> 配置同一个 token;两端不一致即拒绝。
|
||||
/// - Simple3 端通过 <c>simple.json:platform.internalToken</c> 或环境变量
|
||||
/// <c>SIMPLE3__PLATFORM__INTERNALTOKEN</c> 配置同一个 token;两端不一致即拒绝。
|
||||
///
|
||||
/// Token 来源优先级:
|
||||
/// 1) appsettings.json:Internal:Token 显式配置(生产推荐:strong, length ≥ 32)
|
||||
/// 2) 环境变量 PLATFORM__INTERNAL__TOKEN
|
||||
/// 3) 兜底:进程随机 64 字节 base64,并在日志告警 + 写入 <c>data/.internal-token</c>
|
||||
/// 文件供本机 SimpleLite 读取(同机部署常见场景)
|
||||
/// 文件供本机 Simple3 读取(同机部署常见场景)
|
||||
/// </summary>
|
||||
public sealed class InternalTokenStore
|
||||
{
|
||||
@@ -39,7 +39,7 @@ public sealed class InternalTokenStore
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:进程随机 + 落地到 data/.internal-token 便于同机 SimpleLite 读取
|
||||
// 兜底:进程随机 + 落地到 data/.internal-token 便于同机 Simple3 读取
|
||||
var dataDir = Path.Combine(env.ContentRootPath, "data");
|
||||
Directory.CreateDirectory(dataDir);
|
||||
var file = Path.Combine(dataDir, ".internal-token");
|
||||
@@ -71,12 +71,12 @@ public sealed class InternalTokenStore
|
||||
PersistedFilePath = file;
|
||||
logger.LogWarning(
|
||||
"Internal token 未配置 —— 已生成进程随机值并写入 {File}(重启后保留)。" +
|
||||
"若 MiGu.Server 与 SimpleLite 不在同一台机器,请把同一个值写入 SimpleLite 端 simple.json:platform.internalToken。",
|
||||
"若 MiGu.Server 与 Simple3 不在同一台机器,请把同一个值写入 Simple3 端 simple.json:platform.internalToken。",
|
||||
file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "internal token 落盘失败 —— 远程 SimpleLite 调用将无法通过鉴权(每次重启 token 都不同)。");
|
||||
logger.LogError(ex, "internal token 落盘失败 —— 远程 Simple3 调用将无法通过鉴权(每次重启 token 都不同)。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace MiGu.Server.Configs;
|
||||
/// <para>它是「按选型裁剪」的单一事实来源:</para>
|
||||
/// <list type="number">
|
||||
/// <item><see cref="Configured"/>=false 时,登录返回 <c>NeedsWizard=true</c>,前端跳转配置向导;</item>
|
||||
/// <item><see cref="NavigationKinds"/> 经 <see cref="ToActiveSceneIds"/> 映射为 SimpleLite 场景 id,
|
||||
/// <item><see cref="NavigationKinds"/> 经 <see cref="ToActiveSceneIds"/> 映射为 Simple3 场景 id,
|
||||
/// 由 Launcher 写入 <c>plugins/active-scenes.json</c> / 透传 <c>--scenes</c>,驱动内核「选择性加载」导航场景插件;</item>
|
||||
/// <item><see cref="Modules"/> 驱动平台菜单与能力裁剪(PageCatalog 可见页)。</item>
|
||||
/// </list>
|
||||
@@ -35,7 +35,7 @@ public record DeploymentProfile(
|
||||
UpdatedBy: "");
|
||||
|
||||
/// <summary>
|
||||
/// 导航方式 → SimpleLite 场景 id 映射。与 <c>*.scene.json.id</c>、<c>SimpleCore.Navigation.NavKind</c>
|
||||
/// 导航方式 → Simple3 场景 id 映射。与 <c>*.scene.json.id</c>、<c>SimpleCore.Navigation.NavKind</c>
|
||||
/// 以及内核 <c>active-scenes.json.activeScenes</c> 一致;这是平台与内核之间「导航选型」的契约约定。
|
||||
/// <para>两平台制(StandardScene 拆分落地):磁导航 → <c>scene.mag</c>;
|
||||
/// 二维码与激光共用融合平台 <c>scene.qrlidar</c>(激光坐标导航 + 二维码按轨道逐段触发,可融合可单用)。</para>
|
||||
@@ -49,7 +49,7 @@ public record DeploymentProfile(
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 把已选 <see cref="NavigationKinds"/> 映射为 SimpleLite 激活场景 id 列表(去重、忽略未知项、保持选择顺序)。
|
||||
/// 把已选 <see cref="NavigationKinds"/> 映射为 Simple3 激活场景 id 列表(去重、忽略未知项、保持选择顺序)。
|
||||
/// 供 Launcher 写 <c>active-scenes.json</c> / 拼 <c>--scenes</c> 参数使用。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ToActiveSceneIds()
|
||||
|
||||
@@ -12,19 +12,17 @@ namespace MiGu.Server.Controllers;
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 登录请求体。
|
||||
/// 会话 N+1(启动反转):新增 <see cref="LaunchMode"/>。前端登录页让用户选 "WebOnly" / "DesktopAndWeb";
|
||||
/// MiGu.Server 据此拉起 SimpleLite 子进程并透传 <c>--display-mode=web|web+local</c>。
|
||||
/// 历史调用方不传该字段时默认 "DesktopAndWeb"(与之前 web+local 默认行为一致,向后兼容)。
|
||||
/// 登录请求体。可选 <see cref="LaunchMode"/>;Simple3 仅 Web 宿主,缺省/未知一律按 WebOnly。
|
||||
/// MiGu.Server 据此拉起子进程并透传 <c>--display-mode=web</c>。
|
||||
/// </summary>
|
||||
public record LoginRequest(string Username, string Password, string Scope, string? LaunchMode = null);
|
||||
public record LoginRequest(string Username, string Password, string? Scope = null, string? LaunchMode = null);
|
||||
|
||||
/// <summary>
|
||||
/// 登录响应。
|
||||
/// 会话 N+1 增量字段:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>RunMode</c>:根据 SimpleLite 真实拉起结果回填(WebEnabled / WebOnly / Detached)。Detached 表示后端未能拉起 SimpleLite,前端可降级展示。</item>
|
||||
/// <item><c>LaunchStatus</c>:<see cref="SimpleLiteLauncher.LaunchResult.Status"/> 枚举字符串,前端用于精细化提示。</item>
|
||||
/// <item><c>RunMode</c>:根据 Simple3 真实拉起结果回填(WebEnabled / WebOnly / Detached)。Detached 表示后端未能拉起 Simple3,前端可降级展示。</item>
|
||||
/// <item><c>LaunchStatus</c>:<see cref="Simple3Launcher.LaunchResult.Status"/> 枚举字符串,前端用于精细化提示。</item>
|
||||
/// <item><c>LaunchWarning</c>:可空告警文本;非空时前端应该弹消息条告知用户。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
@@ -51,17 +49,18 @@ public class AuthController : ControllerBase
|
||||
string Scope,
|
||||
string RunMode,
|
||||
EffectivePermissions EffectivePermissions,
|
||||
bool NeedsWizard = false);
|
||||
bool NeedsWizard = false,
|
||||
string? Token = null);
|
||||
|
||||
private const string CookieName = "simple.auth.token";
|
||||
|
||||
private readonly RbacStore _rbac;
|
||||
private readonly JwtIssuer _jwt;
|
||||
private readonly SimpleLiteLauncher _launcher;
|
||||
private readonly Simple3Launcher _launcher;
|
||||
private readonly ConfigStore _config;
|
||||
private readonly ILogger<AuthController> _log;
|
||||
|
||||
public AuthController(RbacStore rbac, JwtIssuer jwt, SimpleLiteLauncher launcher, ConfigStore config, ILogger<AuthController> log)
|
||||
public AuthController(RbacStore rbac, JwtIssuer jwt, Simple3Launcher launcher, ConfigStore config, ILogger<AuthController> log)
|
||||
{
|
||||
_rbac = rbac;
|
||||
_jwt = jwt;
|
||||
@@ -79,41 +78,40 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(req.Username))
|
||||
return BadRequest(new { message = "用户名不能为空" });
|
||||
if (req.Scope is not ("Platform" or "RCSMonitor"))
|
||||
return BadRequest(new { message = "无效 scope" });
|
||||
|
||||
// 真密码校验:RbacStore.VerifyCredentials 对不存在 / 已禁用 / 密码错统一返回 null,防用户名枚举。
|
||||
var user = _rbac.VerifyCredentials(req.Username, req.Password);
|
||||
if (user == null)
|
||||
return Unauthorized(new { message = "用户名或密码错误,或账号已被停用" });
|
||||
|
||||
// scope 必须落在该账号「角色覆盖的 scope」集合内(admin 角色 scope=* 覆盖全部)。
|
||||
if (!_rbac.CanUseScope(user, req.Scope))
|
||||
return StatusCode(403, new { message = $"账号 {user.Username} 没有访问 {req.Scope} 的权限" });
|
||||
// 入口由账号角色决定,不再接受登录页挑选「管理端 / 运营端」。
|
||||
var scope = _rbac.ResolveLoginScope(user);
|
||||
if (scope == null)
|
||||
return StatusCode(403, new { message = $"账号 {user.Username} 没有任何可登录区域,请联系管理员分配角色" });
|
||||
|
||||
// 会话 N+1:按 LaunchMode 拉起 SimpleLite 子进程(线程池执行,避免占用请求线程)。
|
||||
// 会话 N+1:按 LaunchMode 拉起 Simple3 子进程(线程池执行,避免占用请求线程)。
|
||||
var launchMode = NormalizeLaunchMode(req.LaunchMode);
|
||||
SimpleLiteLauncher.LaunchResult? launchResult = null;
|
||||
Simple3Launcher.LaunchResult? launchResult = null;
|
||||
try
|
||||
{
|
||||
// M1:waitForReady=false —— 拉起 SimpleLite 后立即返回,不在登录请求里同步等端口
|
||||
// M1:waitForReady=false —— 拉起 Simple3 后立即返回,不在登录请求里同步等端口
|
||||
// 就绪(冷启动可能十几秒)。前端拿 LaunchStatus=Starting 即可,必要时轮询健康检查。
|
||||
launchResult = await Task.Run(() => _launcher.MaybeStart(launchMode, waitForReady: false));
|
||||
_log.LogInformation("SimpleLite launch result for user={User} launchMode={Mode}: Started={Started} Status={Status} Detail={Detail}",
|
||||
_log.LogInformation("Simple3 launch result for user={User} launchMode={Mode}: Started={Started} Status={Status} Detail={Detail}",
|
||||
user.Username, launchMode, launchResult.Value.Started, launchResult.Value.Status, launchResult.Value.Detail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "SimpleLite launch threw for user={User} launchMode={Mode}", user.Username, launchMode);
|
||||
_log.LogError(ex, "Simple3 launch threw for user={User} launchMode={Mode}", user.Username, launchMode);
|
||||
}
|
||||
|
||||
var runMode = ResolveRunMode(launchResult, launchMode);
|
||||
|
||||
var (perm, roleNames, token) = BuildSession(user, req.Scope);
|
||||
var (perm, roleNames, token) = BuildSession(user, scope);
|
||||
SetAuthCookie(token);
|
||||
|
||||
var dto = new AuthUserDto(user.Id, user.Username, user.DisplayName, roleNames);
|
||||
return Ok(new LoginResponse(token, dto, req.Scope, runMode, perm,
|
||||
return Ok(new LoginResponse(token, dto, scope, runMode, perm,
|
||||
LaunchStatus: launchResult?.Status,
|
||||
LaunchWarning: launchResult?.Warning,
|
||||
NeedsWizard: NeedsWizard()));
|
||||
@@ -144,13 +142,20 @@ public class AuthController : ControllerBase
|
||||
if (user == null || !user.Enabled)
|
||||
return Unauthorized(new { message = "账号已失效或被停用" });
|
||||
|
||||
// 账号当前是否还允许这个 scope(管理员可能在此期间调整了角色)。
|
||||
if (!_rbac.CanUseScope(user, scope))
|
||||
return StatusCode(403, new { message = $"账号 {user.Username} 没有访问 {scope} 的权限" });
|
||||
var preferred = _rbac.ResolveLoginScope(user);
|
||||
if (preferred == null)
|
||||
return StatusCode(403, new { message = $"账号 {user.Username} 没有任何可登录区域,请联系管理员分配角色" });
|
||||
|
||||
var (perm, roleNames, token) = BuildSession(user, preferred);
|
||||
string? rotated = null;
|
||||
if (!string.Equals(preferred, scope, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SetAuthCookie(token);
|
||||
rotated = token;
|
||||
}
|
||||
|
||||
var (perm, roleNames, _) = BuildSession(user, scope);
|
||||
var dto = new AuthUserDto(user.Id, user.Username, user.DisplayName, roleNames);
|
||||
return Ok(new MeResponse(dto, scope, InferRunMode(), perm, NeedsWizard()));
|
||||
return Ok(new MeResponse(dto, preferred, InferRunMode(), perm, NeedsWizard(), rotated));
|
||||
}
|
||||
|
||||
/// <summary>用同一身份切换 scope 并重发 token + perms。</summary>
|
||||
@@ -172,6 +177,11 @@ public class AuthController : ControllerBase
|
||||
if (!_rbac.CanUseScope(user, req.Scope))
|
||||
return StatusCode(403, new { message = $"账号 {user.Username} 没有访问 {req.Scope} 的权限" });
|
||||
|
||||
// 有管理权限的账号固定走管理入口,不再切到运营壳;请用运营权限账号登录查看运营页。
|
||||
var preferred = _rbac.ResolveLoginScope(user);
|
||||
if (preferred == PageCatalog.ScopePlatform && req.Scope == PageCatalog.ScopeMonitor)
|
||||
return StatusCode(403, new { message = "请使用运营权限账号查看运营页面" });
|
||||
|
||||
var (perm, roleNames, token) = BuildSession(user, req.Scope);
|
||||
SetAuthCookie(token);
|
||||
|
||||
@@ -200,22 +210,23 @@ public class AuthController : ControllerBase
|
||||
return (perm, roleNames, token);
|
||||
}
|
||||
|
||||
/// <summary>me / switchScope 不重启 SimpleLite,依据 Launcher 记录的 LastLaunchMode 反推 RunMode。</summary>
|
||||
/// <summary>me / switchScope 不重启 Simple3,依据 Launcher 记录的 LastLaunchMode 反推 RunMode。</summary>
|
||||
private string InferRunMode()
|
||||
{
|
||||
var last = _launcher.LastLaunchMode;
|
||||
if (string.IsNullOrEmpty(last)) return "Detached";
|
||||
if (last == SimpleLiteLauncher.ExternalReuseLaunchMode) return "WebEnabled";
|
||||
if (last == Simple3Launcher.ExternalReuseLaunchMode) return "WebOnly";
|
||||
return last.Contains("local", StringComparison.OrdinalIgnoreCase) ? "WebEnabled" : "WebOnly";
|
||||
}
|
||||
|
||||
/// <summary>根据 Launcher 真实结果决定 RunMode(避免 SimpleLite 没起却假装 WebEnabled)。</summary>
|
||||
private static string ResolveRunMode(SimpleLiteLauncher.LaunchResult? result, string launchMode)
|
||||
/// <summary>根据 Launcher 真实结果决定 RunMode(避免内核没起却假装已连接)。</summary>
|
||||
private static string ResolveRunMode(Simple3Launcher.LaunchResult? result, string launchMode)
|
||||
{
|
||||
if (result is not { Started: true })
|
||||
return "Detached";
|
||||
// Simple3 无本地端:复用既有实例也按 WebOnly 展示。
|
||||
if (result.Value.Status == "ReusingExisting")
|
||||
return "WebEnabled";
|
||||
return "WebOnly";
|
||||
if (!string.IsNullOrEmpty(result.Value.DisplayMode))
|
||||
{
|
||||
if (result.Value.DisplayMode.Equals("web", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -230,8 +241,9 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
return raw?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"webonly" or "web-only" or "web" => "WebOnly",
|
||||
_ => "DesktopAndWeb",
|
||||
"desktopandweb" or "web+local" or "weblocal" or "local" => "DesktopAndWeb",
|
||||
// Simple3 默认仅 Web;未传 / 未知也走 WebOnly
|
||||
_ => "WebOnly",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class FleetController : ControllerBase
|
||||
|
||||
/// <summary>
|
||||
/// CDM 搬运任务列表(读平台快照库 cdm_tasks)。在线时先即时同步一次拿最新,
|
||||
/// SimpleLite 关闭时回退最近快照,并通过 online/lastSyncAt 告知前端数据是否滞后。
|
||||
/// Simple3 关闭时回退最近快照,并通过 online/lastSyncAt 告知前端数据是否滞后。
|
||||
/// </summary>
|
||||
[HttpGet("tasks")]
|
||||
public async Task<ActionResult<object>> Tasks([FromQuery] int limit = 1000, CancellationToken ct = default)
|
||||
@@ -76,7 +76,7 @@ public sealed class FleetController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 车辆报警列表(读平台记录 vehicle_alarms)。含活跃 + 历史;SimpleLite 离线时回退最近记录,
|
||||
/// 车辆报警列表(读平台记录 vehicle_alarms)。含活跃 + 历史;Simple3 离线时回退最近记录,
|
||||
/// 通过 online/lastSyncAt 告知数据是否滞后。activeOnly=true 仅返回未恢复的报警。
|
||||
/// </summary>
|
||||
[HttpGet("alarms")]
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace MiGu.Server.Controllers;
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
private static readonly DateTimeOffset StartTime = DateTimeOffset.UtcNow;
|
||||
private readonly SimpleLiteLauncher _launcher;
|
||||
private readonly Simple3Launcher _launcher;
|
||||
|
||||
public HealthController(SimpleLiteLauncher launcher) => _launcher = launcher;
|
||||
public HealthController(Simple3Launcher launcher) => _launcher = launcher;
|
||||
|
||||
/// <summary>匿名存活探针:仅返回进程级状态,不暴露端口拓扑等部署细节。</summary>
|
||||
[HttpGet]
|
||||
@@ -26,28 +26,28 @@ public class HealthController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SimpleLite 拉起配置诊断:查看当前 ExecutablePath、解析结果、端口是否已有服务。
|
||||
/// 配置位置:<c>MiGu.Server/appsettings.json</c> → <c>SimpleLite</c> 节点。
|
||||
/// Simple3 拉起配置诊断:查看当前 ExecutablePath、解析结果、端口是否已有服务。
|
||||
/// 配置位置:<c>MiGu.Server/appsettings.json</c> → <c>Simple3</c> 节点。
|
||||
/// 含服务器本地路径等敏感信息,要求登录。
|
||||
/// </summary>
|
||||
[HttpGet("simplelite")]
|
||||
[HttpGet("simple3")]
|
||||
[Authorize]
|
||||
public IActionResult GetSimpleLiteDiagnostics() => Ok(_launcher.GetDiagnostics());
|
||||
public IActionResult GetSimple3Diagnostics() => Ok(_launcher.GetDiagnostics());
|
||||
|
||||
/// <summary>关闭本机全部 SimpleLite 进程。仅 Platform 管理端可调。</summary>
|
||||
[HttpPost("simplelite/stop")]
|
||||
/// <summary>关闭本机全部 Simple3 进程。仅 Platform 管理端可调。</summary>
|
||||
[HttpPost("simple3/stop")]
|
||||
[Authorize(Policy = "PlatformScope")]
|
||||
public IActionResult StopSimpleLite()
|
||||
public IActionResult StopSimple3()
|
||||
{
|
||||
var killed = _launcher.StopAll();
|
||||
var diag = _launcher.GetDiagnostics();
|
||||
return Ok(new { killed, diagnostics = diag });
|
||||
}
|
||||
|
||||
/// <summary>关闭并重新拉起 SimpleLite(不同步 DLL)。仅 Platform 管理端可调。</summary>
|
||||
[HttpPost("simplelite/restart")]
|
||||
/// <summary>关闭并重新拉起 Simple3(不同步 DLL)。仅 Platform 管理端可调。</summary>
|
||||
[HttpPost("simple3/restart")]
|
||||
[Authorize(Policy = "PlatformScope")]
|
||||
public IActionResult RestartSimpleLite([FromQuery] string launchMode = "webonly")
|
||||
public IActionResult RestartSimple3([FromQuery] string launchMode = "webonly")
|
||||
{
|
||||
var result = _launcher.Restart(launchMode);
|
||||
var diag = _launcher.GetDiagnostics();
|
||||
@@ -55,12 +55,12 @@ public class HealthController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭 SimpleLite、同步最新 DLL、重新拉起。用于「前往站点」API 缺失时一键更新。
|
||||
/// 会终止本机全部 SimpleLite 进程并重启,仅 Platform 管理端可调。
|
||||
/// 关闭 Simple3、同步最新 DLL、重新拉起。用于「前往站点」API 缺失时一键更新。
|
||||
/// 会终止本机全部 Simple3 进程并重启,仅 Platform 管理端可调。
|
||||
/// </summary>
|
||||
[HttpPost("simplelite/restart-for-update")]
|
||||
[HttpPost("simple3/restart-for-update")]
|
||||
[Authorize(Policy = "PlatformScope")]
|
||||
public IActionResult RestartSimpleLiteForUpdate([FromQuery] string launchMode = "webonly")
|
||||
public IActionResult RestartSimple3ForUpdate([FromQuery] string launchMode = "webonly")
|
||||
{
|
||||
var result = _launcher.RestartForUpdate(launchMode);
|
||||
var diag = _launcher.GetDiagnostics();
|
||||
|
||||
@@ -8,8 +8,8 @@ using MiGu.Server.Launcher;
|
||||
namespace MiGu.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 读取 SimpleLite 地图固定目录下的 JSON 原文,供平台「地图管理」右侧预览。
|
||||
/// 先向 SimpleLite 拉取 maps 列表拿到 directory,再读本机同路径文件(与 SimpleLite 同机部署)。
|
||||
/// 读取 Simple3 地图固定目录下的 JSON 原文,供平台「地图管理」右侧预览。
|
||||
/// 先向 Simple3 拉取 maps 列表拿到 directory,再读本机同路径文件(与 Simple3 同机部署)。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
@@ -18,13 +18,13 @@ public class MapsContentController : ControllerBase
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly InternalTokenStore _internalToken;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly Simple3Options _sl;
|
||||
private readonly ILogger<MapsContentController> _log;
|
||||
|
||||
public MapsContentController(
|
||||
IHttpClientFactory httpFactory,
|
||||
InternalTokenStore internalToken,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
IOptions<Simple3Options> sl,
|
||||
ILogger<MapsContentController> log)
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
@@ -47,7 +47,7 @@ public class MapsContentController : ControllerBase
|
||||
{
|
||||
var directory = await FetchMapsDirectoryAsync(ct);
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
return StatusCode(503, new { message = "无法从 SimpleLite 获取地图目录" });
|
||||
return StatusCode(503, new { message = "无法从 Simple3 获取地图目录" });
|
||||
|
||||
var fileName = name.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ? name : $"{name}.json";
|
||||
var fullPath = Path.GetFullPath(Path.Combine(directory, fileName));
|
||||
@@ -86,7 +86,7 @@ public class MapsContentController : ControllerBase
|
||||
using var resp = await client.SendAsync(msg, ct);
|
||||
var body = await resp.Content.ReadAsStringAsync(ct);
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
throw new InvalidOperationException($"SimpleLite maps 列表返回 {(int)resp.StatusCode}");
|
||||
throw new InvalidOperationException($"Simple3 maps 列表返回 {(int)resp.StatusCode}");
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var root = doc.RootElement;
|
||||
|
||||
@@ -4,8 +4,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
namespace MiGu.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 投影 API 占位:真实落地时由 YARP 反代到 SimpleLite WebAPI 的 /api/projection/* 路径。
|
||||
/// 本地 Mock 数据仅用于无 SimpleLite 运行时的开发联调。
|
||||
/// 投影 API 占位:真实落地时由 YARP 反代到 Simple3 WebAPI 的 /api/projection/* 路径。
|
||||
/// 本地 Mock 数据仅用于无 Simple3 运行时的开发联调。
|
||||
///
|
||||
/// AR-4: 全 class 加 [Authorize] —— 任何登录用户都能读 mock 投影数据;未登录直接 401。
|
||||
/// </summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace MiGu.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 迷毂「数据中心」:把 scene.signal 的 Model JSON 以表格读写(PLC 握手 / 磁条交管)。
|
||||
/// 文件落在 SimpleLite 工作目录 <c>Config/Signal/*.json</c>,不依赖 SimpleLite 进程是否在跑。
|
||||
/// 文件落在 Simple3 工作目录 <c>Config/Signal/*.json</c>,不依赖 Simple3 进程是否在跑。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize(Policy = "PlatformScope")]
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace MiGu.Server.Controllers;
|
||||
/// 数据持久化于 <c>platform.db</c> 的 <c>simple_fields</c> 表。
|
||||
///
|
||||
/// 对应前端「字段管理」页(<c>/admin/simple-fields</c>,页面 key <c>admin-simple-fields</c>)。
|
||||
/// 前端主流程为「刷新」读库、「默认」从 SimpleLite 拉取模板、「保存」走 <see cref="SaveBatch"/> 全量替换。
|
||||
/// 前端主流程为「刷新」读库、「默认」从 Simple3 拉取模板、「保存」走 <see cref="SaveBatch"/> 全量替换。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MiGu.Server.Controllers;
|
||||
/// </list>
|
||||
/// 说明:保存时即把选型固化为单一事实来源 <c>deployment</c> section,并同步联动 Launcher ——
|
||||
/// <see cref="SaveProfile"/> 调 <c>WriteActiveScenes</c> 写 <c>plugins/active-scenes.json</c> / 透传 <c>--scenes</c>,
|
||||
/// 驱动 SimpleLite 下次启动选择性加载选定的导航场景插件。
|
||||
/// 驱动 Simple3 下次启动选择性加载选定的导航场景插件。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
@@ -25,10 +25,10 @@ namespace MiGu.Server.Controllers;
|
||||
public class WizardController : ControllerBase
|
||||
{
|
||||
private readonly ConfigStore _store;
|
||||
private readonly SimpleLiteLauncher _launcher;
|
||||
private readonly Simple3Launcher _launcher;
|
||||
private readonly ILogger<WizardController> _log;
|
||||
|
||||
public WizardController(ConfigStore store, SimpleLiteLauncher launcher, ILogger<WizardController> log)
|
||||
public WizardController(ConfigStore store, Simple3Launcher launcher, ILogger<WizardController> log)
|
||||
{
|
||||
_store = store;
|
||||
_launcher = launcher;
|
||||
@@ -81,7 +81,7 @@ public class WizardController : ControllerBase
|
||||
|
||||
_store.PutDeployment(profile);
|
||||
|
||||
// 平台 → 内核联动:把导航选型写入 SimpleLite 的 plugins/active-scenes.json(下次启动选择性加载;
|
||||
// 平台 → 内核联动:把导航选型写入 Simple3 的 plugins/active-scenes.json(下次启动选择性加载;
|
||||
// 已运行实例可由前端再调 POST /api/sl/projection/scenes/apply 触发增量 reload)。
|
||||
// scene.device(门/充电桩/按钮盒驱动)为各类项目通用能力,向导暂无独立选项,固定并入激活集合;
|
||||
// 选 SPS / Pack 时 ToLauncherSceneIds 会并入 scene.signal(PLC 握手)。
|
||||
@@ -107,7 +107,7 @@ public class WizardController : ControllerBase
|
||||
/// 统一对外投影:camelCase 字段 + 推导出的 activeSceneIds(导航选型 → 内核场景 id)+
|
||||
/// hiddenPages(被裁剪的菜单页)+ 可选的 activeScenesWrite(保存时写 active-scenes.json 的结果)。
|
||||
/// </summary>
|
||||
private static object Project(DeploymentProfile dp, SimpleLiteLauncher.ActiveScenesWriteResult? write = null) => new
|
||||
private static object Project(DeploymentProfile dp, Simple3Launcher.ActiveScenesWriteResult? write = null) => new
|
||||
{
|
||||
configured = dp.Configured,
|
||||
platformType = dp.PlatformType,
|
||||
|
||||
@@ -8,14 +8,14 @@ using MiGu.Server.Persistence;
|
||||
namespace MiGu.Server.Fleet;
|
||||
|
||||
/// <summary>
|
||||
/// 单例:轮询 SimpleLite 车辆 + 每车状态,读取「车体_AlarmInfo/车体_AlarmLevel」并对帐进 platform.db(vehicle_alarms)。
|
||||
/// 出现→开 active;文案变→更新;消失→置 cleared 并记录恢复时间/时长。永不删=完整历史;SimpleLite 离线仍可查最近记录。
|
||||
/// 单例:轮询 Simple3 车辆 + 每车状态,读取「车体_AlarmInfo/车体_AlarmLevel」并对帐进 platform.db(vehicle_alarms)。
|
||||
/// 出现→开 active;文案变→更新;消失→置 cleared 并记录恢复时间/时长。永不删=完整历史;Simple3 离线仍可查最近记录。
|
||||
/// </summary>
|
||||
public sealed class AlarmCollector
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly Simple3Options _sl;
|
||||
private readonly InternalTokenStore _token;
|
||||
private readonly ILogger<AlarmCollector> _log;
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
@@ -28,7 +28,7 @@ public sealed class AlarmCollector
|
||||
public AlarmCollector(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IHttpClientFactory httpFactory,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
IOptions<Simple3Options> sl,
|
||||
InternalTokenStore token,
|
||||
ILogger<AlarmCollector> log)
|
||||
{
|
||||
|
||||
@@ -32,14 +32,14 @@ public sealed class CdmTaskDto
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单例:从 SimpleLite 投影拉取 CDM 任务并 upsert 到 platform.db(cdm_tasks),永不删除=保留历史。
|
||||
/// 同时维护「SimpleLite 是否在线 / 最近同步时间」,供任务页离线降级展示。
|
||||
/// 单例:从 Simple3 投影拉取 CDM 任务并 upsert 到 platform.db(cdm_tasks),永不删除=保留历史。
|
||||
/// 同时维护「Simple3 是否在线 / 最近同步时间」,供任务页离线降级展示。
|
||||
/// </summary>
|
||||
public sealed class CdmTaskSyncer
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly Simple3Options _sl;
|
||||
private readonly InternalTokenStore _token;
|
||||
private readonly ILogger<CdmTaskSyncer> _log;
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
@@ -53,7 +53,7 @@ public sealed class CdmTaskSyncer
|
||||
public CdmTaskSyncer(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IHttpClientFactory httpFactory,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
IOptions<Simple3Options> sl,
|
||||
InternalTokenStore token,
|
||||
ILogger<CdmTaskSyncer> log)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace MiGu.Server.Fleet;
|
||||
|
||||
/// <summary>与 SimpleLite GET /projection/fleet/health 行对齐,供车队运维前端消费。</summary>
|
||||
/// <summary>与 Simple3 GET /projection/fleet/health 行对齐,供车队运维前端消费。</summary>
|
||||
public sealed class FleetHealthRowDto
|
||||
{
|
||||
public int CarId { get; set; }
|
||||
|
||||
@@ -7,13 +7,13 @@ using MiGu.Server.Ota;
|
||||
namespace MiGu.Server.Fleet;
|
||||
|
||||
/// <summary>
|
||||
/// 车队健康:保留 SimpleLite 的 CPU/故障率等,延迟改为对 WatchDog(:9776) 做 TCP RTT。
|
||||
/// SimpleLite /fleet/health 探测的是车载 HTTP :8081,多数现场未开该端口会假超时 2000ms。
|
||||
/// 车队健康:保留 Simple3 的 CPU/故障率等,延迟改为对 WatchDog(:9776) 做 TCP RTT。
|
||||
/// Simple3 /fleet/health 探测的是车载 HTTP :8081,多数现场未开该端口会假超时 2000ms。
|
||||
/// </summary>
|
||||
public sealed class FleetHealthService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly Simple3Options _sl;
|
||||
private readonly InternalTokenStore _token;
|
||||
private readonly WatchDogClient _wd;
|
||||
private readonly ILogger<FleetHealthService> _log;
|
||||
@@ -25,7 +25,7 @@ public sealed class FleetHealthService
|
||||
|
||||
public FleetHealthService(
|
||||
IHttpClientFactory httpFactory,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
IOptions<Simple3Options> sl,
|
||||
InternalTokenStore token,
|
||||
WatchDogClient wd,
|
||||
ILogger<FleetHealthService> log)
|
||||
@@ -39,7 +39,7 @@ public sealed class FleetHealthService
|
||||
|
||||
public async Task<List<FleetHealthRowDto>> GetAsync(CancellationToken ct)
|
||||
{
|
||||
var rows = await FetchSimpleLiteHealthAsync(ct);
|
||||
var rows = await FetchSimple3HealthAsync(ct);
|
||||
if (rows.Count == 0)
|
||||
rows = await BuildRowsFromCarsAsync(ct);
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class FleetHealthService
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(row.Ip))
|
||||
{
|
||||
// 无 IP 时保留 SimpleLite 原探测结果
|
||||
// 无 IP 时保留 Simple3 原探测结果
|
||||
row.LatencySource ??= row.LatencyMs != null ? "onboard" : "none";
|
||||
return;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public sealed class FleetHealthService
|
||||
}
|
||||
else
|
||||
{
|
||||
// WatchDog 不通:若 SimpleLite 车载探测成功则保留,否则标不可达
|
||||
// WatchDog 不通:若 Simple3 车载探测成功则保留,否则标不可达
|
||||
if (row.Reachable == true && row.LatencyMs is > 0 and < 2000)
|
||||
{
|
||||
row.LatencySource = "onboard";
|
||||
@@ -93,7 +93,7 @@ public sealed class FleetHealthService
|
||||
return rows;
|
||||
}
|
||||
|
||||
private async Task<List<FleetHealthRowDto>> FetchSimpleLiteHealthAsync(CancellationToken ct)
|
||||
private async Task<List<FleetHealthRowDto>> FetchSimple3HealthAsync(CancellationToken ct)
|
||||
{
|
||||
var port = _sl.ProjectionPort > 0 ? _sl.ProjectionPort : 8222;
|
||||
try
|
||||
@@ -114,7 +114,7 @@ public sealed class FleetHealthService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogDebug(ex, "fleet/health from SimpleLite failed");
|
||||
_log.LogDebug(ex, "fleet/health from Simple3 failed");
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
+19
-17
@@ -4,45 +4,46 @@ using Microsoft.Extensions.Logging;
|
||||
namespace MiGu.Server.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// 将 <c>obj/Debug</c> 下最新编译的 SimpleLite 同步到 <c>bin/Debug</c>(仅当 SimpleLite 未运行时)。
|
||||
/// 将 <c>obj/Debug</c> 下最新编译的 Simple3 同步到 <c>bin/Debug</c>(仅当 Simple3 未运行时)。
|
||||
/// </summary>
|
||||
public static class SimpleLiteBuildSync
|
||||
public static class Simple3BuildSync
|
||||
{
|
||||
public static bool TrySyncFromObjToBin(string contentRoot, ILogger? log = null)
|
||||
{
|
||||
if (!TryResolvePaths(contentRoot, out var objDll, out var objExe, out var binDir))
|
||||
return false;
|
||||
|
||||
var binDll = Path.Combine(binDir, "SimpleLite.dll");
|
||||
var binExe = Path.Combine(binDir, "SimpleLite.exe");
|
||||
var binDll = Path.Combine(binDir, "Simple3.dll");
|
||||
var binExe = Path.Combine(binDir, "Simple3.exe");
|
||||
|
||||
if (!File.Exists(objDll))
|
||||
{
|
||||
log?.LogDebug("[SimpleLiteBuildSync] obj DLL 不存在: {Path}", objDll);
|
||||
log?.LogDebug("[Simple3BuildSync] obj DLL 不存在: {Path}", objDll);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Process.GetProcessesByName("SimpleLite").Any(p => !p.HasExited))
|
||||
if (Process.GetProcessesByName("Simple3").Any(p => !p.HasExited)
|
||||
|| Process.GetProcessesByName("SimpleLite").Any(p => !p.HasExited))
|
||||
{
|
||||
log?.LogWarning("[SimpleLiteBuildSync] SimpleLite 仍在运行,跳过 DLL 同步。请先关闭 SimpleLite 窗口。");
|
||||
log?.LogWarning("[Simple3BuildSync] Simple3 仍在运行,跳过 DLL 同步。请先关闭内核窗口。");
|
||||
return false;
|
||||
}
|
||||
|
||||
var objTime = File.GetLastWriteTimeUtc(objDll);
|
||||
if (File.Exists(binDll) && File.GetLastWriteTimeUtc(binDll) >= objTime)
|
||||
{
|
||||
log?.LogDebug("[SimpleLiteBuildSync] bin 已是最新,无需同步");
|
||||
log?.LogDebug("[Simple3BuildSync] bin 已是最新,无需同步");
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(binDir);
|
||||
File.Copy(objDll, binDll, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步 {Src} → {Dst}", objDll, binDll);
|
||||
log?.LogInformation("[Simple3BuildSync] 已同步 {Src} → {Dst}", objDll, binDll);
|
||||
|
||||
if (File.Exists(objExe))
|
||||
{
|
||||
File.Copy(objExe, binExe, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步 {Src} → {Dst}", objExe, binExe);
|
||||
log?.LogInformation("[Simple3BuildSync] 已同步 {Src} → {Dst}", objExe, binExe);
|
||||
}
|
||||
|
||||
SyncRuntimeDeps(objDll, binDir, log);
|
||||
@@ -51,7 +52,7 @@ public static class SimpleLiteBuildSync
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 探测 SimpleLite 是否带「前往站点」路由(区分新旧 DLL)。
|
||||
/// 探测 Simple3 是否带「前往站点」路由(区分新旧 DLL)。
|
||||
/// 必须用不存在的对象 id(-1):路由存在时后端进 handler 找不到对象,返回 JSON
|
||||
/// <c>success:false</c>(无任何副作用);路由不存在时 EmbedIO 返回 HTML 404。
|
||||
/// 早期版本误用 car/0 + siteId=0 —— 一旦场景里真有 id=0 的车和站点,每次健康
|
||||
@@ -96,7 +97,7 @@ public static class SimpleLiteBuildSync
|
||||
|
||||
var dst = Path.Combine(binDir, name);
|
||||
File.Copy(src, dst, true);
|
||||
log?.LogInformation("[SimpleLiteBuildSync] 已同步依赖 {Name}", name);
|
||||
log?.LogInformation("[Simple3BuildSync] 已同步依赖 {Name}", name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +107,11 @@ public static class SimpleLiteBuildSync
|
||||
binDir = "";
|
||||
var repo = FindRepoRoot(contentRoot);
|
||||
if (repo == null) return false;
|
||||
var sl = Path.Combine(repo, "Simple", "SimpleLite");
|
||||
objDll = Path.Combine(sl, "obj", "Debug", "SimpleLite.dll");
|
||||
objExe = Path.Combine(sl, "obj", "Debug", "SimpleLite.exe");
|
||||
var sl = Path.Combine(repo, "Simple", "Simple3");
|
||||
objDll = Path.Combine(sl, "obj", "Debug", "Simple3.dll");
|
||||
objExe = Path.Combine(sl, "obj", "Debug", "Simple3.exe");
|
||||
binDir = Path.Combine(sl, "bin", "Debug");
|
||||
return true;
|
||||
return Directory.Exists(sl);
|
||||
}
|
||||
|
||||
private static string? FindRepoRoot(string contentRoot)
|
||||
@@ -118,7 +119,8 @@ public static class SimpleLiteBuildSync
|
||||
var dir = new DirectoryInfo(contentRoot);
|
||||
while (dir != null)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(dir.FullName, "Simple", "SimpleLite")))
|
||||
if (Directory.Exists(Path.Combine(dir.FullName, "Simple", "Simple3"))
|
||||
|| Directory.Exists(Path.Combine(dir.FullName, "Simple", "SimpleLite")))
|
||||
return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
+128
-109
@@ -7,21 +7,21 @@ using Microsoft.Extensions.Options;
|
||||
namespace MiGu.Server.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// 平台登录成功后按 LaunchMode 把 SimpleLite.exe 作为子进程拉起。
|
||||
/// 平台登录成功后按 LaunchMode 把 Simple3.exe 作为子进程拉起。
|
||||
///
|
||||
/// 设计要点(与原 <c>SimpleLite/Platform/PlatformLauncher.cs</c> 镜像):
|
||||
/// 设计要点(与原 <c>Simple3/Platform/PlatformLauncher.cs</c> 镜像):
|
||||
/// - 幂等:第一次登录拉起,后续登录复用同一个子进程(不会重复启)。
|
||||
/// - 子进程退出后下一次登录可以重新拉起(不阻塞用户重试)。
|
||||
/// - 默认独立(<see cref="SimpleLiteOptions.FollowParent"/> = false):SimpleLite 与 MiGu.Server 互不影响,
|
||||
/// - 默认独立(<see cref="Simple3Options.FollowParent"/> = false):Simple3 与 MiGu.Server 互不影响,
|
||||
/// 关闭任一方不会 kill 另一方;Windows 上用 <c>cmd /c start</c> 脱离父进程组/控制台。
|
||||
/// - 可选跟随(FollowParent = true):Windows JobObject 绑定,MiGu.Server 退出时一并结束 SimpleLite。
|
||||
/// - 命令行透传:把 LaunchMode 翻成 SimpleLite 的 <c>--display-mode=web</c> / <c>--display-mode=web+local</c>。
|
||||
/// - 启动后阻塞等待 SimpleLite Projection :8222 端口可达(最长 ReadinessTimeoutMs),让前端 /api/sl/* 不再立刻 502。
|
||||
/// - 可选跟随(FollowParent = true):Windows JobObject 绑定,MiGu.Server 退出时一并结束 Simple3。
|
||||
/// - 命令行透传:把 LaunchMode 翻成 Simple3 的 <c>--display-mode=web</c> / <c>--display-mode=web+local</c>。
|
||||
/// - 启动后阻塞等待 Simple3 Projection :8222 端口可达(最长 ReadinessTimeoutMs),让前端 /api/sl/* 不再立刻 502。
|
||||
/// </summary>
|
||||
public sealed class SimpleLiteLauncher : IDisposable
|
||||
public sealed class Simple3Launcher : IDisposable
|
||||
{
|
||||
private readonly SimpleLiteOptions _opts;
|
||||
private readonly ILogger<SimpleLiteLauncher> _log;
|
||||
private readonly Simple3Options _opts;
|
||||
private readonly ILogger<Simple3Launcher> _log;
|
||||
private readonly IHostEnvironment _env;
|
||||
private readonly object _sync = new();
|
||||
|
||||
@@ -30,13 +30,13 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
private string? _lastLaunchMode;
|
||||
private bool _disposed;
|
||||
|
||||
public SimpleLiteLauncher(IOptions<SimpleLiteOptions> opts, ILogger<SimpleLiteLauncher> log, IHostEnvironment env)
|
||||
public Simple3Launcher(IOptions<Simple3Options> opts, ILogger<Simple3Launcher> log, IHostEnvironment env)
|
||||
{
|
||||
_opts = opts.Value;
|
||||
_log = log;
|
||||
_env = env;
|
||||
// 会话 N+2:默认不再在 ProcessExit 时清理子进程 —— SimpleLite 是「独立程序」,MiGu.Server 关掉
|
||||
// 不应该带走 SimpleLite。仅当用户显式 opt-in FollowParent=true 时才挂软关闭兜底。
|
||||
// 会话 N+2:默认不再在 ProcessExit 时清理子进程 —— Simple3 是「独立程序」,MiGu.Server 关掉
|
||||
// 不应该带走 Simple3。仅当用户显式 opt-in FollowParent=true 时才挂软关闭兜底。
|
||||
if (_opts.FollowParent)
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => Dispose();
|
||||
@@ -53,11 +53,11 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
public string? LastLaunchMode { get { lock (_sync) return _lastLaunchMode; } }
|
||||
|
||||
/// <summary>外部已存在的 SimpleLite(MiGu.Server 重启复用上一轮实例)占位 LaunchMode 值,不参与命令行 displayMode 翻译。</summary>
|
||||
/// <summary>外部已存在的 Simple3(MiGu.Server 重启复用上一轮实例)占位 LaunchMode 值,不参与命令行 displayMode 翻译。</summary>
|
||||
internal const string ExternalReuseLaunchMode = "external";
|
||||
|
||||
/// <summary>
|
||||
/// 按 launchMode 拉起 SimpleLite(已运行则跳过)。
|
||||
/// 按 launchMode 拉起 Simple3(已运行则跳过)。
|
||||
/// </summary>
|
||||
/// <param name="launchMode">"WebOnly" 或 "DesktopAndWeb"(大小写不敏感)。</param>
|
||||
/// <returns>本次调用产生的状态摘要,可写入登录响应或日志。</returns>
|
||||
@@ -67,20 +67,20 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
if (!_opts.Enabled)
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] auto-start disabled (appsettings: SimpleLite.Enabled=false); launchMode={Mode} ignored", launchMode);
|
||||
return new LaunchResult(false, "Disabled", "appsettings:SimpleLite:Enabled=false", DisplayMode: null,
|
||||
Warning: "SimpleLite 自动拉起已被 appsettings:SimpleLite:Enabled=false 关闭;登录已成功但 SimpleLite 未启动,/api/sl/* 反代请求会返回 502。");
|
||||
_log.LogInformation("[Simple3] auto-start disabled (appsettings: Simple3.Enabled=false); launchMode={Mode} ignored", launchMode);
|
||||
return new LaunchResult(false, "Disabled", "appsettings:Simple3:Enabled=false", DisplayMode: null,
|
||||
Warning: "Simple3 自动拉起已被 appsettings:Simple3:Enabled=false 关闭;登录已成功但 Simple3 未启动,/api/sl/* 反代请求会返回 502。");
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_proc is { HasExited: false })
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] already running pid={Pid}, displayMode={DisplayMode}; skip duplicate launch", _proc.Id, _lastLaunchMode);
|
||||
_log.LogInformation("[Simple3] already running pid={Pid}, displayMode={DisplayMode}; skip duplicate launch", _proc.Id, _lastLaunchMode);
|
||||
var warning = !string.IsNullOrEmpty(_lastLaunchMode) &&
|
||||
!string.Equals(_lastLaunchMode, displayMode, StringComparison.OrdinalIgnoreCase)
|
||||
? $"SimpleLite 已经在运行(pid={_proc.Id}, displayMode={_lastLaunchMode})。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用;如需切换,请手动关闭旧 SimpleLite 后重新登录。"
|
||||
? $"Simple3 已经在运行(pid={_proc.Id}, displayMode={_lastLaunchMode})。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用;如需切换,请手动关闭旧 Simple3 后重新登录。"
|
||||
: null;
|
||||
return new LaunchResult(true, "AlreadyRunning",
|
||||
$"pid={_proc.Id}, displayMode={_lastLaunchMode}",
|
||||
@@ -88,44 +88,44 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
Warning: warning);
|
||||
}
|
||||
|
||||
// 会话 N+2:MiGu.Server 重启后 _proc 引用丢失,但上一轮拉起的 SimpleLite 可能仍在跑(因为
|
||||
// 会话 N+2:MiGu.Server 重启后 _proc 引用丢失,但上一轮拉起的 Simple3 可能仍在跑(因为
|
||||
// 默认 FollowParent=false 不带走它)。这里在拉起前先探测 Projection 端口:能连通就视为复用,
|
||||
// 避免「allowMultiple=false 时新 SimpleLite 检测到多开自杀」+「端口冲突」两类常见崩溃。
|
||||
// 避免「allowMultiple=false 时新 Simple3 检测到多开自杀」+「端口冲突」两类常见崩溃。
|
||||
//
|
||||
// 注意:探测仅判断「有 SimpleLite 在 8222 占着」,无法知道它当时选的 LaunchMode。如果用户本次
|
||||
// 想换模式,这条路径下不会生效;日志里会明确告知,由用户决定是否手动关掉旧 SimpleLite 再登录。
|
||||
// 注意:探测仅判断「有 Simple3 在 8222 占着」,无法知道它当时选的 LaunchMode。如果用户本次
|
||||
// 想换模式,这条路径下不会生效;日志里会明确告知,由用户决定是否手动关掉旧 Simple3 再登录。
|
||||
//
|
||||
// 增强(A3):先做 TCP 探活(快),再做 HTTP JSON 探针验证「真的是 SimpleLite」,避免被某个偶然占
|
||||
// 增强(A3):先做 TCP 探活(快),再做 HTTP JSON 探针验证「真的是 Simple3」,避免被某个偶然占
|
||||
// 用 8222 的无关进程误判成复用。Probe 失败时不再当作复用成功,否则前端会拿到假的 WebEnabled。
|
||||
if (TryConnect("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(500)))
|
||||
{
|
||||
var probeOk = ProbeSimpleLiteHttp("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(800));
|
||||
var probeOk = ProbeSimple3Http("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(800));
|
||||
if (!probeOk)
|
||||
{
|
||||
_log.LogWarning(
|
||||
"[SimpleLite] projection port :{Port} is occupied, but SimpleLite HTTP probe failed. Skip launch to avoid port conflict.",
|
||||
"[Simple3] projection port :{Port} is occupied, but Simple3 HTTP probe failed. Skip launch to avoid port conflict.",
|
||||
_opts.ProjectionPort);
|
||||
return new LaunchResult(false, "PortOccupied",
|
||||
$"projection :{_opts.ProjectionPort} tcp reachable but /projection/cars probe failed",
|
||||
DisplayMode: null,
|
||||
Warning: $"端口 :{_opts.ProjectionPort} 已被占用,但未识别为 SimpleLite Projection 服务。" +
|
||||
"请关闭占用该端口的进程,或调整 SimpleLite:ProjectionPort / SimpleLite 配置后重试。");
|
||||
Warning: $"端口 :{_opts.ProjectionPort} 已被占用,但未识别为 Simple3 Projection 服务。" +
|
||||
"请关闭占用该端口的进程,或调整 Simple3:ProjectionPort / Simple3 配置后重试。");
|
||||
}
|
||||
|
||||
_log.LogInformation(
|
||||
"[SimpleLite] projection :{Port} already reachable (httpProbe=ok); assume an existing SimpleLite is running. " +
|
||||
"Skip launch. If user picked a different LaunchMode this session, please close the existing SimpleLite window and login again.",
|
||||
"[Simple3] projection :{Port} already reachable (httpProbe=ok); assume an existing Simple3 is running. " +
|
||||
"Skip launch. If user picked a different LaunchMode this session, please close the existing Simple3 window and login again.",
|
||||
_opts.ProjectionPort);
|
||||
|
||||
// A2 修复:保留一个占位 LaunchMode 让 LastLaunchMode 不再为 null —— 这样 SwitchScope
|
||||
// 推断 runMode 时不会落到 "web+local" 兜底,前端 RunMode 角标也不会与实际不符。
|
||||
_lastLaunchMode = ExternalReuseLaunchMode;
|
||||
|
||||
var reuseWarning = $"检测到 SimpleLite 已经在 :{_opts.ProjectionPort} 上运行(可能是 MiGu.Server 重启前残留)。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用到既有实例。如需切换,请手动关闭旧 SimpleLite 窗口后重新登录。";
|
||||
if (SimpleLiteBuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort) == false)
|
||||
var reuseWarning = $"检测到 Simple3 已经在 :{_opts.ProjectionPort} 上运行(可能是 MiGu.Server 重启前残留)。" +
|
||||
$"本次选择的启动模式『{launchMode}』未被应用到既有实例。如需切换,请手动关闭旧 Simple3 窗口后重新登录。";
|
||||
if (Simple3BuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort) == false)
|
||||
{
|
||||
reuseWarning += " 当前 SimpleLite 版本过旧,缺少「前往站点」API;请关闭 SimpleLite 窗口后调用 POST /api/health/simplelite/restart-for-update,或运行 scripts/redeploy-simplelite.ps1。";
|
||||
reuseWarning += " 当前 Simple3 版本过旧,缺少「前往站点」API;请关闭 Simple3 窗口后调用 POST /api/health/simple3/restart-for-update,或运行 scripts/redeploy-simple3.ps1。";
|
||||
}
|
||||
|
||||
return new LaunchResult(true, "ReusingExisting",
|
||||
@@ -134,18 +134,18 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
Warning: reuseWarning);
|
||||
}
|
||||
|
||||
SimpleLiteBuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
Simple3BuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
|
||||
var resolved = ResolveExecutable(_opts.ExecutablePath);
|
||||
if (resolved == null)
|
||||
{
|
||||
_log.LogWarning(
|
||||
"[SimpleLite] auto-start skipped: SimpleLite.exe not found. Configure appsettings:SimpleLite:ExecutablePath or place SimpleLite next to MiGu.Server. ContentRoot={Root}",
|
||||
"[Simple3] auto-start skipped: Simple3.exe not found. Configure appsettings:Simple3:ExecutablePath or place Simple3 next to MiGu.Server. ContentRoot={Root}",
|
||||
_env.ContentRootPath);
|
||||
return new LaunchResult(false, "ExecutableNotFound",
|
||||
"Set appsettings:SimpleLite:ExecutablePath or place SimpleLite next to MiGu.Server.",
|
||||
"Set appsettings:Simple3:ExecutablePath or place Simple3 next to MiGu.Server.",
|
||||
DisplayMode: null,
|
||||
Warning: "找不到 SimpleLite.exe。请在 appsettings:SimpleLite:ExecutablePath 显式配置,或者把 SimpleLite.exe 放到 MiGu.Server 同目录。");
|
||||
Warning: "找不到 Simple3.exe。请在 appsettings:Simple3:ExecutablePath 显式配置,或者把 Simple3.exe 放到 MiGu.Server 同目录。");
|
||||
}
|
||||
|
||||
var workdir = string.IsNullOrWhiteSpace(_opts.WorkingDirectory)
|
||||
@@ -156,12 +156,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var proc = StartSimpleLiteProcess(resolved, arguments, workdir);
|
||||
var proc = StartSimple3Process(resolved, arguments, workdir);
|
||||
if (proc == null)
|
||||
{
|
||||
_log.LogError("[SimpleLite] Process.Start returned null; exe={Exe} args={Args}", resolved, arguments);
|
||||
_log.LogError("[Simple3] Process.Start returned null; exe={Exe} args={Args}", resolved, arguments);
|
||||
return new LaunchResult(false, "ProcessStartFailed", $"exe={resolved}", DisplayMode: null,
|
||||
Warning: $"启动 SimpleLite 进程失败;exe={resolved}");
|
||||
Warning: $"启动 Simple3 进程失败;exe={resolved}");
|
||||
}
|
||||
|
||||
WireProcessExitHandler(proc);
|
||||
@@ -171,20 +171,20 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
if (_opts.FollowParent) AttachToJobObject(proc);
|
||||
|
||||
_log.LogInformation("[SimpleLite] launched pid={Pid} displayMode={Mode} exe={Exe} args=\"{Args}\" workdir={Workdir}; standalone={Standalone}",
|
||||
_log.LogInformation("[Simple3] launched pid={Pid} displayMode={Mode} exe={Exe} args=\"{Args}\" workdir={Workdir}; standalone={Standalone}",
|
||||
proc.Id, displayMode, resolved, arguments, workdir, !_opts.FollowParent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogError(ex, "[SimpleLite] auto-start failed");
|
||||
_log.LogError(ex, "[Simple3] auto-start failed");
|
||||
return new LaunchResult(false, "Exception", ex.Message, DisplayMode: null,
|
||||
Warning: $"启动 SimpleLite 时抛异常:{ex.GetType().Name}: {ex.Message}");
|
||||
Warning: $"启动 Simple3 时抛异常:{ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// M1:登录路径传 waitForReady=false —— 进程拉起后立即返回,不再同步阻塞最长
|
||||
// ReadinessTimeoutMs 等端口就绪(避免冷启动登录干等十几秒)。前端可轮询
|
||||
// GET /api/health/simplelite 获知就绪状态。
|
||||
// GET /api/health/simple3 获知就绪状态。
|
||||
if (!waitForReady)
|
||||
{
|
||||
return new LaunchResult(true, "Starting",
|
||||
@@ -200,12 +200,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
DisplayMode: displayMode,
|
||||
Warning: ready
|
||||
? null
|
||||
: $"SimpleLite 进程已起,但 Projection :{_opts.ProjectionPort} 在 {_opts.ReadinessTimeoutMs}ms 内未响应。前端 /api/sl/* 可能短暂 502;可稍后刷新。");
|
||||
: $"Simple3 进程已起,但 Projection :{_opts.ProjectionPort} 在 {_opts.ReadinessTimeoutMs}ms 内未响应。前端 /api/sl/* 可能短暂 502;可稍后刷新。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动关闭 SimpleLite 子进程。
|
||||
/// 会话 N+2 起默认不调(FollowParent=false)—— SimpleLite 是独立程序,MiGu.Server 关闭不带走它。
|
||||
/// 手动关闭 Simple3 子进程。
|
||||
/// 会话 N+2 起默认不调(FollowParent=false)—— Simple3 是独立程序,MiGu.Server 关闭不带走它。
|
||||
/// 仅当用户显式 opt-in FollowParent=true 时由 ProcessExit / ApplicationStopping 钩子调用。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
@@ -227,7 +227,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
// 独立模式:不杀子进程,仅释放 MiGu.Server 侧 Process 句柄。
|
||||
if (proc is { HasExited: false })
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] standalone mode: MiGu.Server stopping — SimpleLite pid={Pid} keeps running (FollowParent=false)", proc.Id);
|
||||
_log.LogInformation("[Simple3] standalone mode: MiGu.Server stopping — Simple3 pid={Pid} keeps running (FollowParent=false)", proc.Id);
|
||||
}
|
||||
try { proc?.Dispose(); } catch { /* ignore */ }
|
||||
return;
|
||||
@@ -236,7 +236,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
if (proc is { HasExited: false })
|
||||
{
|
||||
try { proc.Kill(entireProcessTree: true); }
|
||||
catch (Exception ex) { _log.LogWarning("[SimpleLite] kill failed: {Msg}", ex.Message); }
|
||||
catch (Exception ex) { _log.LogWarning("[Simple3] kill failed: {Msg}", ex.Message); }
|
||||
try { proc.WaitForExit(2000); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
@@ -248,21 +248,21 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
private static string NormalizeDisplayMode(string launchMode)
|
||||
{
|
||||
// LaunchMode 是「业务语言」(WebOnly / DesktopAndWeb);displayMode 是 SimpleLite「内部语言」(web / web+local)。
|
||||
// LaunchMode 是「业务语言」(WebOnly / DesktopAndWeb);displayMode 是 Simple3「内部语言」(web / web+local)。
|
||||
// 这里做一次显式翻译,前端 / 后端日志均用业务语言,命令行用内部语言。
|
||||
return launchMode?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"webonly" or "web" or "web-only" => "web",
|
||||
// 任何未知值都按"完整本地+web"兜底,保证最小惊讶(既能桌面用,也能浏览器用)。
|
||||
_ => "web+local",
|
||||
"desktopandweb" or "web+local" or "weblocal" or "local" => "web+local",
|
||||
// Simple3 无本地桌面端:默认 / 未知一律 web
|
||||
_ => "web",
|
||||
};
|
||||
}
|
||||
|
||||
private string BuildArguments(string displayMode, string extra)
|
||||
{
|
||||
var args = $"--display-mode={displayMode}";
|
||||
// 迷榖嵌入:平台 iframe 场景默认带 --migu,让 SimpleLite WebTerminal 默认纯画布(隐藏 ImGui panel),
|
||||
// 业务 UI 由 Vue 平台前端接管;declare 时序失败时也安全回退到画布。可用 appsettings:SimpleLite:EmbeddedCanvas=false 关闭。
|
||||
// 迷榖嵌入:平台 iframe 场景默认带 --migu,让 Simple3 WebTerminal 默认纯画布(隐藏 ImGui panel),
|
||||
// 业务 UI 由 Vue 平台前端接管;declare 时序失败时也安全回退到画布。可用 appsettings:Simple3:EmbeddedCanvas=false 关闭。
|
||||
if (_opts.EmbeddedCanvas) args += " --migu";
|
||||
// 选择性加载:把平台写入的 plugins/active-scenes.json 同步透传为 --scenes(命令行优先级最高,与文件一致,双保险)。
|
||||
var sceneArg = ReadActiveScenesArg();
|
||||
@@ -271,7 +271,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>解析 SimpleLite 工作目录(与拉起时一致):优先显式 <see cref="SimpleLiteOptions.WorkingDirectory"/>,
|
||||
/// <summary>解析 Simple3 工作目录(与拉起时一致):优先显式 <see cref="Simple3Options.WorkingDirectory"/>,
|
||||
/// 否则取解析到的 exe 所在目录。两者都拿不到返回 null。</summary>
|
||||
public string? ResolveWorkingDirectory()
|
||||
{
|
||||
@@ -281,7 +281,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
return resolved == null ? null : (Path.GetDirectoryName(resolved) ?? Environment.CurrentDirectory);
|
||||
}
|
||||
|
||||
/// <summary>SimpleLite 的 plugins 目录(工作目录/plugins);定位不到工作目录时返回 null。</summary>
|
||||
/// <summary>Simple3 的 plugins 目录(工作目录/plugins);定位不到工作目录时返回 null。</summary>
|
||||
public string? ResolvePluginsDir()
|
||||
{
|
||||
var wd = ResolveWorkingDirectory();
|
||||
@@ -289,16 +289,16 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把「配置向导选定的导航场景」写入 SimpleLite 的 <c>plugins/active-scenes.json</c> ——
|
||||
/// 这是「平台 → 内核」选择性加载的主通道。下次 SimpleLite 启动即据此只加载选定导航场景插件;
|
||||
/// 已在运行的实例需重启或调 <c>POST /projection/scenes/apply</c> 才生效。字段名与 SimpleLite 端
|
||||
/// 把「配置向导选定的导航场景」写入 Simple3 的 <c>plugins/active-scenes.json</c> ——
|
||||
/// 这是「平台 → 内核」选择性加载的主通道。下次 Simple3 启动即据此只加载选定导航场景插件;
|
||||
/// 已在运行的实例需重启或调 <c>POST /projection/scenes/apply</c> 才生效。字段名与 Simple3 端
|
||||
/// <c>ActiveScenesConfig</c> 对齐(activeScenes / alwaysLoad / source / updatedAt)。
|
||||
/// </summary>
|
||||
public ActiveScenesWriteResult WriteActiveScenes(IEnumerable<string> activeScenes, IEnumerable<string>? alwaysLoad, string source)
|
||||
{
|
||||
var pluginsDir = ResolvePluginsDir();
|
||||
if (pluginsDir == null)
|
||||
return new ActiveScenesWriteResult(false, null, "未找到 SimpleLite 工作目录/可执行文件,无法定位 plugins 目录");
|
||||
return new ActiveScenesWriteResult(false, null, "未找到 Simple3 工作目录/可执行文件,无法定位 plugins 目录");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(pluginsDir);
|
||||
@@ -313,13 +313,13 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
updatedAt = DateTime.UtcNow
|
||||
};
|
||||
File.WriteAllText(path, JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
|
||||
_log.LogInformation("[SimpleLite] active-scenes.json 写入 {Path}: active=[{Scenes}]",
|
||||
_log.LogInformation("[Simple3] active-scenes.json 写入 {Path}: active=[{Scenes}]",
|
||||
path, string.Join(",", payload.activeScenes));
|
||||
return new ActiveScenesWriteResult(true, path, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning(ex, "[SimpleLite] 写 active-scenes.json 失败");
|
||||
_log.LogWarning(ex, "[Simple3] 写 active-scenes.json 失败");
|
||||
return new ActiveScenesWriteResult(false, null, ex.Message);
|
||||
}
|
||||
}
|
||||
@@ -349,11 +349,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
/// <summary>写 active-scenes.json 的结果(供向导保存接口回显)。</summary>
|
||||
public readonly record struct ActiveScenesWriteResult(bool Ok, string? Path, string? Error);
|
||||
|
||||
/// <summary>终止本机全部 SimpleLite 进程并清理 MiGu.Server 侧托管引用。</summary>
|
||||
/// <summary>终止本机全部 Simple3(及旧名 SimpleLite)进程并清理 MiGu.Server 侧托管引用。</summary>
|
||||
public int StopAll()
|
||||
{
|
||||
var killed = 0;
|
||||
foreach (var proc in Process.GetProcessesByName("SimpleLite"))
|
||||
foreach (var name in new[] { "Simple3", "SimpleLite" })
|
||||
foreach (var proc in Process.GetProcessesByName(name))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -361,12 +362,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
proc.Kill(entireProcessTree: true);
|
||||
killed++;
|
||||
_log.LogInformation("[SimpleLite] stop: killed pid={Pid}", proc.Id);
|
||||
_log.LogInformation("[Simple3] stop: killed {Name} pid={Pid}", name, proc.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] stop: kill pid={Pid} failed: {Msg}", proc.Id, ex.Message);
|
||||
_log.LogWarning("[Simple3] stop: kill {Name} pid={Pid} failed: {Msg}", name, proc.Id, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -385,7 +386,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
return killed;
|
||||
}
|
||||
|
||||
/// <summary>关闭全部 SimpleLite 后按 launchMode 重新拉起(不同步 DLL)。</summary>
|
||||
/// <summary>关闭全部 Simple3 后按 launchMode 重新拉起(不同步 DLL)。</summary>
|
||||
public LaunchResult Restart(string launchMode = "webonly")
|
||||
{
|
||||
StopAll();
|
||||
@@ -393,19 +394,19 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭所有 SimpleLite 进程、同步 obj→bin 最新 DLL,再按 launchMode 重新拉起。
|
||||
/// 关闭所有 Simple3 进程、同步 obj→bin 最新 DLL,再按 launchMode 重新拉起。
|
||||
/// </summary>
|
||||
public LaunchResult RestartForUpdate(string launchMode = "webonly")
|
||||
{
|
||||
StopAll();
|
||||
|
||||
var synced = SimpleLiteBuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
var synced = Simple3BuildSync.TrySyncFromObjToBin(_env.ContentRootPath, _log);
|
||||
var result = MaybeStart(launchMode);
|
||||
if (!synced && result.Warning == null)
|
||||
{
|
||||
return result with
|
||||
{
|
||||
Warning = "未能从 obj/Debug 同步 DLL(可能未编译或 SimpleLite 仍占用文件)。请先运行 scripts/redeploy-simplelite.ps1。"
|
||||
Warning = "未能从 obj/Debug 同步 DLL(可能未编译或 Simple3 仍占用文件)。请先运行 scripts/redeploy-simple3.ps1。"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -416,12 +417,12 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
private DateTimeOffset _gotoSiteProbeAt = DateTimeOffset.MinValue;
|
||||
private static readonly TimeSpan GotoSiteProbeTtl = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>启动前诊断:当前配置、解析到的 exe、端口占用等(供 /api/health/simplelite 与启动日志)。</summary>
|
||||
public SimpleLiteDiagnostics GetDiagnostics()
|
||||
/// <summary>启动前诊断:当前配置、解析到的 exe、端口占用等(供 /api/health/simple3 与启动日志)。</summary>
|
||||
public Simple3Diagnostics GetDiagnostics()
|
||||
{
|
||||
var resolved = ResolveExecutable(_opts.ExecutablePath);
|
||||
var projectionUp = TryConnect("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(400));
|
||||
// 探测结果缓存 60s:诊断端点可能被前端轮询,避免每次都对 SimpleLite 发探测请求。
|
||||
// 探测结果缓存 60s:诊断端点可能被前端轮询,避免每次都对 Simple3 发探测请求。
|
||||
bool? gotoSite = null;
|
||||
if (projectionUp)
|
||||
{
|
||||
@@ -431,7 +432,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
gotoSite = SimpleLiteBuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort);
|
||||
gotoSite = Simple3BuildSync.ProbeGotoSiteRoute(_opts.ProjectionPort);
|
||||
if (gotoSite is not null)
|
||||
{
|
||||
_gotoSiteProbeCache = gotoSite;
|
||||
@@ -440,9 +441,9 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
}
|
||||
var deployHint = gotoSite == false
|
||||
? "关闭 SimpleLite 后:运行 Migu2.0/scripts/redeploy-simplelite.ps1,或 POST /api/health/simplelite/restart-for-update"
|
||||
? "关闭 Simple3 后:运行 Migu2.0/scripts/redeploy-simple3.ps1,或 POST /api/health/simple3/restart-for-update"
|
||||
: null;
|
||||
return new SimpleLiteDiagnostics(
|
||||
return new Simple3Diagnostics(
|
||||
Enabled: _opts.Enabled,
|
||||
FollowParent: _opts.FollowParent,
|
||||
ConfiguredExecutablePath: _opts.ExecutablePath ?? "",
|
||||
@@ -456,26 +457,26 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
ProjectionPortReachable: projectionUp,
|
||||
GotoSiteApiAvailable: gotoSite,
|
||||
DeployHint: deployHint,
|
||||
ConfigHint: "编辑 MiGu.Server/appsettings.json 的 SimpleLite 节点(所有环境生效);开发机可叠加 appsettings.Development.json。" +
|
||||
" FollowParent=false 时关闭 MiGu.Server 不会结束 SimpleLite。");
|
||||
ConfigHint: "编辑 MiGu.Server/appsettings.json 的 Simple3 节点(所有环境生效);开发机可叠加 appsettings.Development.json。" +
|
||||
" FollowParent=false 时关闭 MiGu.Server 不会结束 Simple3。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉起 SimpleLite。FollowParent=false 时在 Windows 上用 <c>cmd /c start</c> 脱离父进程组,避免平台退出连带结束 SimpleLite。
|
||||
/// 拉起 Simple3。FollowParent=false 时在 Windows 上用 <c>cmd /c start</c> 脱离父进程组,避免平台退出连带结束 Simple3。
|
||||
/// </summary>
|
||||
private Process? StartSimpleLiteProcess(string resolved, string arguments, string workdir)
|
||||
private Process? StartSimple3Process(string resolved, string arguments, string workdir)
|
||||
{
|
||||
if (_opts.FollowParent)
|
||||
return StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
return StartSimple3Direct(resolved, arguments, workdir, useShellExecute: true);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
return StartSimpleLiteDetachedWindows(resolved, arguments, workdir)
|
||||
?? StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
return StartSimple3DetachedWindows(resolved, arguments, workdir)
|
||||
?? StartSimple3Direct(resolved, arguments, workdir, useShellExecute: true);
|
||||
|
||||
return StartSimpleLiteDirect(resolved, arguments, workdir, useShellExecute: true);
|
||||
return StartSimple3Direct(resolved, arguments, workdir, useShellExecute: true);
|
||||
}
|
||||
|
||||
private static Process? StartSimpleLiteDirect(string resolved, string arguments, string workdir, bool useShellExecute)
|
||||
private static Process? StartSimple3Direct(string resolved, string arguments, string workdir, bool useShellExecute)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
@@ -493,14 +494,15 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
/// <summary>
|
||||
/// 通过 cmd start 在新进程组/新控制台中启动,与 MiGu.Server 控制台 Ctrl+C、进程树结束解耦。
|
||||
/// </summary>
|
||||
private Process? StartSimpleLiteDetachedWindows(string resolved, string arguments, string workdir)
|
||||
private Process? StartSimple3DetachedWindows(string resolved, string arguments, string workdir)
|
||||
{
|
||||
var exeName = Path.GetFileNameWithoutExtension(resolved);
|
||||
var beforeIds = new HashSet<int>(
|
||||
Process.GetProcessesByName(exeName).Select(p => { try { return p.Id; } catch { return -1; } })
|
||||
.Where(id => id > 0));
|
||||
|
||||
var cmdArgs = $"/c start \"SimpleLite\" /D \"{workdir}\" \"{resolved}\" {arguments}";
|
||||
// cmd start 的第一个引号参数是「窗口标题」,不是 exe;写死 Simple3 会让 Simple3 控制台标题错名。
|
||||
var cmdArgs = $"/c start \"{exeName}\" /D \"{workdir}\" \"{resolved}\" {arguments}";
|
||||
var shimPsi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
@@ -521,7 +523,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
if (p.HasExited) continue;
|
||||
if (beforeIds.Contains(p.Id)) continue;
|
||||
_log.LogInformation("[SimpleLite] detached start via cmd.exe; new pid={Pid}", p.Id);
|
||||
_log.LogInformation("[Simple3] detached start via cmd.exe; new pid={Pid}", p.Id);
|
||||
return AttachToExistingProcess(p.Id);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
@@ -529,7 +531,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
|
||||
_log.LogWarning("[SimpleLite] detached start: no new {Name} process observed after cmd.exe start", exeName);
|
||||
_log.LogWarning("[Simple3] detached start: no new {Name} process observed after cmd.exe start", exeName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -543,7 +545,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] attach to pid={Pid} failed: {Msg}", pid, ex.Message);
|
||||
_log.LogWarning("[Simple3] attach to pid={Pid} failed: {Msg}", pid, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -554,7 +556,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
int exit;
|
||||
try { exit = proc.ExitCode; } catch { exit = -1; }
|
||||
_log.LogInformation("[SimpleLite] process exited code={Code}; next login will relaunch if needed", exit);
|
||||
_log.LogInformation("[Simple3] process exited code={Code}; next login will relaunch if needed", exit);
|
||||
lock (_sync)
|
||||
{
|
||||
if (ReferenceEquals(_proc, proc))
|
||||
@@ -577,16 +579,26 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
var cwd = _env.ContentRootPath;
|
||||
var staticCandidates = new[]
|
||||
{
|
||||
Path.Combine(cwd, "Simple3.exe"),
|
||||
Path.Combine(cwd, "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "Simple3", "Simple3.exe"),
|
||||
Path.Combine(cwd, "SimpleLite", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "Simple3.exe"),
|
||||
Path.Combine(cwd, "..", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "SimpleLite", "bin", "Debug", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "SimpleLite", "bin", "Release", "SimpleLite.exe"),
|
||||
// Migu2.0 与 Simple 并列:Migu2.0/MiGu.Server → ../../Simple/SimpleLite/bin/Debug
|
||||
// 发布布局(本机常见):D:\MDCS\Release\Simple\Simple3
|
||||
@"D:\MDCS\Release\Simple\Simple3\Simple3.exe",
|
||||
// 源码并列:Migu2.0/MiGu.Server → ../../Simple/Simple3(OutputPath 常直接写到 Release)
|
||||
Path.Combine(cwd, "..", "..", "Simple", "Simple3", "Simple3.exe"),
|
||||
Path.Combine(cwd, "..", "..", "Simple", "Simple3", "bin", "Debug", "Simple3.exe"),
|
||||
Path.Combine(cwd, "..", "..", "Simple", "Simple3", "bin", "Release", "Simple3.exe"),
|
||||
Path.Combine(cwd, "..", "Simple", "Simple3", "Simple3.exe"),
|
||||
// 兼容旧 SimpleLite 目录/产物名
|
||||
Path.Combine(cwd, "..", "..", "Simple", "SimpleLite", "bin", "Debug", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "..", "Simple", "SimpleLite", "bin", "Release", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "Simple", "SimpleLite", "bin", "Debug", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "Simple", "SimpleLite", "bin", "Release", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "SimpleLite", "bin", "Debug", "SimpleLite.exe"),
|
||||
Path.Combine(cwd, "..", "SimpleLite", "bin", "Release", "SimpleLite.exe"),
|
||||
};
|
||||
foreach (var c in staticCandidates)
|
||||
{
|
||||
@@ -599,6 +611,13 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
foreach (var sub in new[]
|
||||
{
|
||||
"Simple3/Simple3.exe",
|
||||
"Simple3/bin/Debug/Simple3.exe",
|
||||
"Simple3/bin/Release/Simple3.exe",
|
||||
"Simple/Simple3/Simple3.exe",
|
||||
"Simple/Simple3/bin/Debug/Simple3.exe",
|
||||
"Simple/Simple3/bin/Release/Simple3.exe",
|
||||
"SimpleLite/SimpleLite.exe",
|
||||
"SimpleLite/bin/Debug/SimpleLite.exe",
|
||||
"SimpleLite/bin/Release/SimpleLite.exe",
|
||||
"Simple/SimpleLite/bin/Debug/SimpleLite.exe",
|
||||
@@ -668,14 +687,14 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
|
||||
if (TryConnect("127.0.0.1", _opts.ProjectionPort, TimeSpan.FromMilliseconds(interval)))
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] projection :{Port} ready", _opts.ProjectionPort);
|
||||
_log.LogInformation("[Simple3] projection :{Port} ready", _opts.ProjectionPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
Thread.Sleep(interval);
|
||||
}
|
||||
|
||||
_log.LogWarning("[SimpleLite] projection :{Port} not ready within {Timeout}ms", _opts.ProjectionPort, _opts.ReadinessTimeoutMs);
|
||||
_log.LogWarning("[Simple3] projection :{Port} not ready within {Timeout}ms", _opts.ProjectionPort, _opts.ReadinessTimeoutMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -694,10 +713,10 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在 TCP 通的基础上读取 SimpleLite Projection 的强类型 JSON 端点,判别「真的是 SimpleLite」。
|
||||
/// 在 TCP 通的基础上读取 Simple3 Projection 的强类型 JSON 端点,判别「真的是 Simple3」。
|
||||
/// 只接受 2xx 且响应体像 JSON 数组/对象;任意普通 HTTP 服务占用 8222 不再被误认为可复用。
|
||||
/// </summary>
|
||||
private static bool ProbeSimpleLiteHttp(string host, int port, TimeSpan timeout)
|
||||
private static bool ProbeSimple3Http(string host, int port, TimeSpan timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -718,20 +737,20 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉起 SimpleLite 的结果摘要。
|
||||
/// 拉起 Simple3 的结果摘要。
|
||||
/// <list type="bullet">
|
||||
/// <item><c>Started</c>:本次调用后是否处于"已运行"状态(包含 AlreadyRunning / ReusingExisting / Ready / StartedButNotReady)。</item>
|
||||
/// <item><c>Status</c>:状态枚举字符串(见上)。</item>
|
||||
/// <item><c>Detail</c>:技术细节,写入服务端日志。</item>
|
||||
/// <item><c>DisplayMode</c>:本次实际生效的 displayMode(web / web+local / external / null)。
|
||||
/// 与 LaunchMode 业务字段区分:external 表示复用了 MiGu.Server 重启前残留的 SimpleLite,对应模式未知。</item>
|
||||
/// 与 LaunchMode 业务字段区分:external 表示复用了 MiGu.Server 重启前残留的 Simple3,对应模式未知。</item>
|
||||
/// <item><c>Warning</c>:透传给前端登录响应的告警文本,null 表示无需告警。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public readonly record struct LaunchResult(bool Started, string Status, string Detail,
|
||||
string? DisplayMode = null, string? Warning = null);
|
||||
|
||||
public sealed record SimpleLiteDiagnostics(
|
||||
public sealed record Simple3Diagnostics(
|
||||
bool Enabled,
|
||||
bool FollowParent,
|
||||
string ConfiguredExecutablePath,
|
||||
@@ -807,7 +826,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
_log.LogInformation("[SimpleLite] JobObject skipped on non-Windows; falling back to ProcessExit-only cleanup");
|
||||
_log.LogInformation("[Simple3] JobObject skipped on non-Windows; falling back to ProcessExit-only cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -818,7 +837,7 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
_job = CreateJobObject(IntPtr.Zero, null);
|
||||
if (_job == IntPtr.Zero)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] CreateJobObject failed; child may outlive MiGu.Server");
|
||||
_log.LogWarning("[Simple3] CreateJobObject failed; child may outlive MiGu.Server");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -831,17 +850,17 @@ public sealed class SimpleLiteLauncher : IDisposable
|
||||
{
|
||||
Marshal.StructureToPtr(info, ptr, false);
|
||||
if (!SetInformationJobObject(_job, JobObjectExtendedLimitInformation, ptr, (uint)len))
|
||||
_log.LogWarning("[SimpleLite] SetInformationJobObject failed; child may outlive MiGu.Server");
|
||||
_log.LogWarning("[Simple3] SetInformationJobObject failed; child may outlive MiGu.Server");
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(ptr); }
|
||||
}
|
||||
|
||||
if (!AssignProcessToJobObject(_job, child.Handle))
|
||||
_log.LogWarning("[SimpleLite] AssignProcessToJobObject failed; child may outlive MiGu.Server");
|
||||
_log.LogWarning("[Simple3] AssignProcessToJobObject failed; child may outlive MiGu.Server");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.LogWarning("[SimpleLite] JobObject setup error: {Type}: {Msg}", ex.GetType().Name, ex.Message);
|
||||
_log.LogWarning("[Simple3] JobObject setup error: {Type}: {Msg}", ex.GetType().Name, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-23
@@ -1,31 +1,30 @@
|
||||
namespace MiGu.Server.Launcher;
|
||||
|
||||
/// <summary>
|
||||
/// MiGu.Server 启动后由「平台登录」按 LaunchMode 拉起的 SimpleLite 子进程配置。
|
||||
/// MiGu.Server 启动后由「平台登录」按 LaunchMode 拉起的 Simple3 子进程配置。
|
||||
///
|
||||
/// 会话 N+1(启动反转):原架构是 SimpleLite 启动 → 拉 MiGu.Server;现在反过来:
|
||||
/// MiGu.Server 作为主入口启动 → 登录页选 LaunchMode → 后端拉起 SimpleLite.exe,
|
||||
/// 通过 <c>--display-mode</c> 参数把 web / web+local 透传给 SimpleLite 的 Configuration。
|
||||
/// 会话 N+1(启动反转):原架构是 Simple3 启动 → 拉 MiGu.Server;现在反过来:
|
||||
/// MiGu.Server 作为主入口启动 → 登录页选 LaunchMode → 后端拉起 Simple3.exe,
|
||||
/// 通过 <c>--display-mode</c> 参数把 web / web+local 透传给 Simple3 的 Configuration。
|
||||
///
|
||||
/// 绑定 <c>appsettings.json:SimpleLite</c>。
|
||||
/// 绑定 <c>appsettings.json:Simple3</c>。
|
||||
/// </summary>
|
||||
public sealed class SimpleLiteOptions
|
||||
public sealed class Simple3Options
|
||||
{
|
||||
/// <summary>主开关。false = 永不拉起;登录无论选什么 LaunchMode 都不会启动子进程,用于「只跑 Platform 调试」场景。</summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// SimpleLite 可执行文件绝对/相对路径(相对 MiGu.Server 工作目录)。留空则按以下顺序自动探测:
|
||||
/// 1) <c>./SimpleLite.exe</c>(合并发布布局:SimpleLite 与 MiGu.Server 同目录)
|
||||
/// 2) <c>./SimpleLite/SimpleLite.exe</c>
|
||||
/// 3) <c>../SimpleLite.exe</c>(MiGu.Server 部署到子目录的常见布局)
|
||||
/// 4) <c>../SimpleLite/bin/Debug/SimpleLite.exe</c>(开发态:Visual Studio 默认输出)
|
||||
/// 5) <c>../SimpleLite/bin/Release/SimpleLite.exe</c>
|
||||
/// 6) 沿父目录上行寻找 <c>SimpleLite/bin/{Debug|Release}/SimpleLite.exe</c>
|
||||
/// Simple3 可执行文件绝对/相对路径(相对 MiGu.Server 工作目录)。留空则按以下顺序自动探测:
|
||||
/// 1) <c>./Simple3.exe</c>(合并发布布局;仍兼容探测旧名 <c>./SimpleLite.exe</c>)
|
||||
/// 2) <c>./Simple3/Simple3.exe</c>
|
||||
/// 3) 开发态:兄弟仓库 <c>../Simple/Simple3/</c> 产出目录或 <c>bin/Debug|Release</c>
|
||||
/// 4) 发布目录常见布局:<c>D:\MDCS\Release\Simple\Simple3\Simple3.exe</c>(仅当该绝对路径存在)
|
||||
/// 5) 沿父目录上行寻找 Simple3 产出
|
||||
/// </summary>
|
||||
public string ExecutablePath { get; set; } = "";
|
||||
|
||||
/// <summary>留空时取 <see cref="ExecutablePath"/> 所在目录。SimpleLite 在 CWD 读写 simple.json / imgui.ini,CWD 选错会出意外。</summary>
|
||||
/// <summary>留空时取 <see cref="ExecutablePath"/> 所在目录。内核在 CWD 读写 simple.json / imgui.ini,CWD 选错会出意外。</summary>
|
||||
public string WorkingDirectory { get; set; } = "";
|
||||
|
||||
/// <summary>附加命令行参数(拼在 <c>--display-mode=xxx</c> 之后)。常用于本地调试时强制 autoload 某场景。</summary>
|
||||
@@ -33,7 +32,7 @@ public sealed class SimpleLiteOptions
|
||||
|
||||
/// <summary>
|
||||
/// 子进程启动后阻塞等待 Projection (:8222) 端口就绪的最长毫秒数。
|
||||
/// 0 = 不等待(登录立即返回,前端可能还连不上 SimpleLite WebApi);
|
||||
/// 0 = 不等待(登录立即返回,前端可能还连不上 Simple3 WebApi);
|
||||
/// 负值 = 无限等待(直到子进程退出或就绪)。
|
||||
/// </summary>
|
||||
public int ReadinessTimeoutMs { get; set; } = 8000;
|
||||
@@ -41,17 +40,17 @@ public sealed class SimpleLiteOptions
|
||||
/// <summary>每隔多少毫秒 poll 一次 Projection 端口可达性。</summary>
|
||||
public int ReadinessPollIntervalMs { get; set; } = 250;
|
||||
|
||||
/// <summary>SimpleLite Projection WebApi 监听端口。默认与 SimpleLite Configuration 的 `port` 一致。用于就绪检测。</summary>
|
||||
/// <summary>Simple3 Projection WebApi 监听端口。默认与 Simple3 Configuration 的 `port` 一致。用于就绪检测。</summary>
|
||||
public int ProjectionPort { get; set; } = 8222;
|
||||
|
||||
/// <summary>
|
||||
/// 是否把 SimpleLite 绑定到 MiGu.Server 生命周期,默认 <b>false</b>(会话 N+2 用户反馈)。
|
||||
/// 是否把 Simple3 绑定到 MiGu.Server 生命周期,默认 <b>false</b>(会话 N+2 用户反馈)。
|
||||
///
|
||||
/// 设计原则:SimpleLite 与 MiGu.Server 是「两个独立程序」,Platform 只是登录后顺手拉起 SimpleLite;
|
||||
/// MiGu.Server 关闭不应该带走 SimpleLite,反之亦然。所以 FollowParent 默认 false:
|
||||
/// 设计原则:Simple3 与 MiGu.Server 是「两个独立程序」,Platform 只是登录后顺手拉起 Simple3;
|
||||
/// MiGu.Server 关闭不应该带走 Simple3,反之亦然。所以 FollowParent 默认 false:
|
||||
/// - 子进程走 <c>UseShellExecute=true</c> 创建独立进程组 + 独立控制台窗口;
|
||||
/// - 不挂 JobObject,不在 ApplicationStopping / ProcessExit 时 kill 子进程;
|
||||
/// - SimpleLite 退出由用户自己负责(关窗口 / 任务管理器 / 调度内核异常退出)。
|
||||
/// - Simple3 退出由用户自己负责(关窗口 / 任务管理器 / 调度内核异常退出)。
|
||||
///
|
||||
/// true 仍可用:会启动 Windows JobObject 父子绑定(仅 Windows 有效)+ 注册 ApplicationStopping 软关闭。
|
||||
/// 一般只在临时联调期 / CI 流水线想自动清理时打开。
|
||||
@@ -59,11 +58,11 @@ public sealed class SimpleLiteOptions
|
||||
public bool FollowParent { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 平台拉起 SimpleLite 时是否以「迷榖嵌入画布」模式运行(透传命令行 <c>--migu</c>),默认 <b>true</b>。
|
||||
/// 平台拉起 Simple3 时是否以「迷榖嵌入画布」模式运行(透传命令行 <c>--migu</c>),默认 <b>true</b>。
|
||||
///
|
||||
/// true:平台 iframe 嵌入场景,SimpleLite WebTerminal 默认只显示 3D 画布、隐藏所有 ImGui panel,
|
||||
/// true:平台 iframe 嵌入场景,Simple3 WebTerminal 默认只显示 3D 画布、隐藏所有 ImGui panel,
|
||||
/// 业务 UI 全部由 Vue 平台前端接管;declare 时序失败时也安全回退到纯画布。
|
||||
/// false:平台拉起的 SimpleLite web 端默认显示完整 panel(便于把平台拉起的实例直连 :8223 调试)。
|
||||
/// false:平台拉起的 Simple3 web 端默认显示完整 panel(便于把平台拉起的实例直连 :8223 调试)。
|
||||
///
|
||||
/// 与 LaunchMode(web / web+local,是否保留本地调试窗口)正交,可任意组合。
|
||||
/// </summary>
|
||||
@@ -1,8 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>MiGu.Server</RootNamespace>
|
||||
@@ -25,13 +24,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MiGu.DB\MiGu.DB.csproj" />
|
||||
<ProjectReference Include="..\..\..\StandardSence\StandardScene.Signal\StandardScene.Signal.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="data\.gitkeep" Condition="Exists('data\.gitkeep')" />
|
||||
<Content Include="OpenApi\simplelite-projection.json" Link="OpenApi\simplelite-projection.json" CopyToOutputDirectory="PreserveNewest" Condition="Exists('OpenApi\simplelite-projection.json')" />
|
||||
<Content Include="..\..\Simple\SimpleLite\Docs\openapi\simplelite-projection.json" Link="OpenApi\simplelite-projection.json" CopyToOutputDirectory="PreserveNewest" Condition="Exists('..\..\Simple\SimpleLite\Docs\openapi\simplelite-projection.json')" />
|
||||
<Content Include="OpenApi\simple3-projection.json" Link="OpenApi\simple3-projection.json" CopyToOutputDirectory="PreserveNewest" Condition="Exists('OpenApi\simple3-projection.json')" />
|
||||
<Content Include="..\..\Simple\Simple3\Docs\openapi\simple3-projection.json" Link="OpenApi\simple3-projection.json" CopyToOutputDirectory="PreserveNewest" Condition="Exists('..\..\Simple\Simple3\Docs\openapi\simple3-projection.json')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+8
-5
@@ -5,10 +5,10 @@ using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
namespace MiGu.Server.OpenApi;
|
||||
|
||||
/// <summary>
|
||||
/// 将 SimpleLite EmbedIO WebApi 的 OpenAPI 描述合并进 MiGu.Server Swagger 文档。
|
||||
/// 源文件:Simple/SimpleLite/Docs/openapi/simplelite-projection.json
|
||||
/// 将 Simple3 EmbedIO WebApi 的 OpenAPI 描述合并进 MiGu.Server Swagger 文档。
|
||||
/// 源文件:Simple/Simple3/Docs/openapi/simple3-projection.json
|
||||
/// </summary>
|
||||
public sealed class SimpleLiteOpenApiDocumentFilter : IDocumentFilter
|
||||
public sealed class Simple3OpenApiDocumentFilter : IDocumentFilter
|
||||
{
|
||||
private static readonly Dictionary<string, OperationType> VerbMap = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -175,9 +175,12 @@ public sealed class SimpleLiteOpenApiDocumentFilter : IDocumentFilter
|
||||
|
||||
foreach (var root in roots)
|
||||
{
|
||||
yield return Path.GetFullPath(Path.Combine(root, "OpenApi", "simple3-projection.json"));
|
||||
yield return Path.GetFullPath(Path.Combine(root, "..", "..", "..", "Simple", "Simple3", "Docs", "openapi", "simple3-projection.json"));
|
||||
yield return Path.GetFullPath(Path.Combine(root, "..", "..", "Simple", "Simple3", "Docs", "openapi", "simple3-projection.json"));
|
||||
// 兼容旧文件名 / 旧目录
|
||||
yield return Path.GetFullPath(Path.Combine(root, "OpenApi", "simplelite-projection.json"));
|
||||
yield return Path.GetFullPath(Path.Combine(root, "..", "..", "..", "Simple", "SimpleLite", "Docs", "openapi", "simplelite-projection.json"));
|
||||
yield return Path.GetFullPath(Path.Combine(root, "..", "..", "Simple", "SimpleLite", "Docs", "openapi", "simplelite-projection.json"));
|
||||
yield return Path.GetFullPath(Path.Combine(root, "..", "..", "..", "Simple", "Simple3", "Docs", "openapi", "simplelite-projection.json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ namespace MiGu.Server.Ota;
|
||||
public sealed class OtaVehicleSource
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly SimpleLiteOptions _sl;
|
||||
private readonly Simple3Options _sl;
|
||||
private readonly InternalTokenStoreAccessor _token;
|
||||
private readonly ILogger<OtaVehicleSource> _log;
|
||||
|
||||
public OtaVehicleSource(
|
||||
IHttpClientFactory httpFactory,
|
||||
IOptions<SimpleLiteOptions> sl,
|
||||
IOptions<Simple3Options> sl,
|
||||
InternalTokenStoreAccessor token,
|
||||
ILogger<OtaVehicleSource> log)
|
||||
{
|
||||
|
||||
+22
-23
@@ -124,11 +124,11 @@ builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new()
|
||||
{
|
||||
Title = "MiGu.Server + SimpleLite",
|
||||
Title = "MiGu.Server + Simple3",
|
||||
Version = "v1",
|
||||
Description = "咪咕平台后端 API,以及经 YARP 反代的 SimpleLite 数据 WebApi(标签 SimpleLite/*)。详见 Simple/SimpleLite/Docs/MIGU-API.md。"
|
||||
Description = "咪咕平台后端 API,以及经 YARP 反代的 Simple3 数据 WebApi(标签 Simple3/*)。详见 Simple/Simple3/Docs/MIGU-API.md。"
|
||||
});
|
||||
c.DocumentFilter<SimpleLiteOpenApiDocumentFilter>();
|
||||
c.DocumentFilter<Simple3OpenApiDocumentFilter>();
|
||||
// Swagger 里挂 Bearer 输入框,便于手工测带鉴权的端点。
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
@@ -162,7 +162,7 @@ builder.Services.AddCors(opts => opts.AddDefaultPolicy(p =>
|
||||
// - 旧 SPA 还在用 localStorage.token + Bearer,逐步迁移到 Cookie;过渡期两路都接受;
|
||||
// - JwtIssuer 集中颁发 + 验签;secret 来自 appsettings:Jwt:Secret 或环境变量
|
||||
// PLATFORM__JWT__SECRET,占位值会被运行时随机化并强制告警。
|
||||
// - InternalTokenStore 管理 SimpleLite 8222 ↔ MiGu.Server 之间的 X-Platform-Internal-Token
|
||||
// - InternalTokenStore 管理 Simple3 8222 ↔ MiGu.Server 之间的 X-Platform-Internal-Token
|
||||
// 共享密钥(YARP transform 自动追加)。
|
||||
builder.Services.AddSingleton<RbacStore>();
|
||||
builder.Services.AddSingleton<JwtIssuer>(sp =>
|
||||
@@ -217,7 +217,7 @@ builder.Services.AddAuthorization(opts =>
|
||||
}));
|
||||
});
|
||||
|
||||
// YARP + transform:把 Platform 内部 token 透传给 SimpleLite 8222(AR-1/AR-2 配套)。
|
||||
// YARP + transform:把 Platform 内部 token 透传给 Simple3 8222(AR-1/AR-2 配套)。
|
||||
builder.Services.AddReverseProxy()
|
||||
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
|
||||
.AddTransforms(tctx =>
|
||||
@@ -255,16 +255,18 @@ builder.Services.AddReverseProxy()
|
||||
|
||||
// 单例配置仓库(内存 + JSON 文件持久化占位)
|
||||
builder.Services.AddSingleton<ConfigStore>();
|
||||
builder.Services.AddSingleton<MiGu.Server.Logs.LogCleanupService>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<MiGu.Server.Logs.LogCleanupService>());
|
||||
// 运维审计持久化存储(替代旧的纯内存队列,MiGu.Server 重启后审计不丢)。
|
||||
builder.Services.AddSingleton<OpsAuditStore>();
|
||||
// OpsController 真实转发 SimpleLite reflection execute 所需的 HttpClient 工厂。
|
||||
// OpsController 真实转发 Simple3 reflection execute 所需的 HttpClient 工厂。
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddPlatformPersistence(builder.Configuration);
|
||||
|
||||
// 会话 N+1(启动反转):把 SimpleLite 子进程拉起器接入 DI;AuthController 登录成功后按 LaunchMode 调 MaybeStart。
|
||||
// 配置段绑定 appsettings.json:SimpleLite,可被环境变量 SIMPLELITE__XXX 覆盖。
|
||||
builder.Services.Configure<SimpleLiteOptions>(builder.Configuration.GetSection("SimpleLite"));
|
||||
builder.Services.AddSingleton<SimpleLiteLauncher>();
|
||||
// 会话 N+1(启动反转):把 Simple3 子进程拉起器接入 DI;AuthController 登录成功后按 LaunchMode 调 MaybeStart。
|
||||
// 配置段绑定 appsettings.json:Simple3,可被环境变量 SIMPLE3__XXX 覆盖。
|
||||
builder.Services.Configure<Simple3Options>(builder.Configuration.GetSection("Simple3"));
|
||||
builder.Services.AddSingleton<Simple3Launcher>();
|
||||
builder.Services.AddSingleton<SignalDataStore>();
|
||||
|
||||
// OTA(WatchDog 编排):包库 / 任务 / 出站客户端
|
||||
@@ -289,35 +291,32 @@ _ = app.Services.GetRequiredService<JwtIssuer>();
|
||||
_ = app.Services.GetRequiredService<InternalTokenStore>();
|
||||
// 主动构造 RbacStore:首启时尽早 seed 默认用户 / 角色并打印 data/rbac.json 载入日志。
|
||||
_ = app.Services.GetRequiredService<RbacStore>();
|
||||
// 主动实例化 SimpleLiteLauncher(FollowParent=true 时注册 ProcessExit 软关闭钩子)。
|
||||
var simpleLiteLauncher = app.Services.GetRequiredService<SimpleLiteLauncher>();
|
||||
// 主动实例化 Simple3Launcher(FollowParent=true 时注册 ProcessExit 软关闭钩子)。
|
||||
var simple3Launcher = app.Services.GetRequiredService<Simple3Launcher>();
|
||||
{
|
||||
var sl = simpleLiteLauncher.GetDiagnostics();
|
||||
var sl = simple3Launcher.GetDiagnostics();
|
||||
app.Logger.LogInformation(
|
||||
"[MiGu.Server] SimpleLite: Enabled={Enabled}, FollowParent={FollowParent}, ConfiguredPath={Cfg}, Resolved={Resolved}, Exists={Exists}, Port:{Port} reachable={PortUp}. 配置见 appsettings.json → SimpleLite",
|
||||
"[MiGu.Server] Simple3: Enabled={Enabled}, FollowParent={FollowParent}, ConfiguredPath={Cfg}, Resolved={Resolved}, Exists={Exists}, Port:{Port} reachable={PortUp}. 配置见 appsettings.json → Simple3",
|
||||
sl.Enabled, sl.FollowParent, sl.ConfiguredExecutablePath, sl.ResolvedExecutablePath ?? "(未找到)", sl.ExecutableExists,
|
||||
sl.ProjectionPort, sl.ProjectionPortReachable);
|
||||
}
|
||||
|
||||
// FollowParent=true 时 MiGu.Server 退出会 kill SimpleLite;默认 false 时不注册停机清理(两进程独立)。
|
||||
if (builder.Configuration.GetValue("SimpleLite:FollowParent", false))
|
||||
// FollowParent=true 时 MiGu.Server 退出会 kill Simple3;默认 false 时不注册停机清理(两进程独立)。
|
||||
if (builder.Configuration.GetValue("Simple3:FollowParent", false))
|
||||
{
|
||||
app.Lifetime.ApplicationStopping.Register(() =>
|
||||
{
|
||||
try { app.Services.GetRequiredService<SimpleLiteLauncher>().Dispose(); }
|
||||
try { app.Services.GetRequiredService<Simple3Launcher>().Dispose(); }
|
||||
catch { /* shutdown best-effort */ }
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
app.Logger.LogInformation("[MiGu.Server] SimpleLite: FollowParent=false — MiGu.Server 退出不会结束 SimpleLite");
|
||||
app.Logger.LogInformation("[MiGu.Server] Simple3: FollowParent=false — MiGu.Server 退出不会结束 Simple3");
|
||||
}
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
// OTA 回传会话按 TCP 对端 IP 校验;必须在 ForwardedHeaders 改写 RemoteIpAddress 之前捕获。
|
||||
app.Use(async (ctx, next) =>
|
||||
|
||||
+34
-34
@@ -5,8 +5,8 @@
|
||||
>
|
||||
> **会话 N+1(启动反转):现在 `MiGu.Server.exe` 是主入口**。
|
||||
> 用户先启动 MiGu.Server,浏览器登录页选择「启动模式」(本地+Web / 仅Web),
|
||||
> 后端 `AuthController.Login` 调用 `SimpleLiteLauncher.MaybeStart(launchMode)` 按所选模式拉起 `SimpleLite.exe`,
|
||||
> 通过 `--display-mode=web|web+local` 透传给 SimpleLite 的 `Configuration.displayMode`。
|
||||
> 后端 `AuthController.Login` 调用 `Simple3Launcher.MaybeStart(launchMode)` 按所选模式拉起 `Simple3.exe`,
|
||||
> 通过 `--display-mode=web|web+local` 透传给 Simple3 的 `Configuration.displayMode`。
|
||||
>
|
||||
> 工程名 `MiGu.Server` / 程序集 `MiGu.Server.dll` 保持稳定,仅用户可见前端文案改为「迷毂」。
|
||||
|
||||
@@ -19,83 +19,83 @@
|
||||
cd MiGu.Server
|
||||
dotnet build MiGu.Server.csproj -c Debug
|
||||
|
||||
# 2) 启动(登录后按所选模式拉起 SimpleLite)
|
||||
# 2) 启动(登录后按所选模式拉起 Simple3)
|
||||
dotnet run
|
||||
# 或:.\bin\Debug\net8.0\MiGu.Server.exe
|
||||
```
|
||||
|
||||
**更新前端**:在 Simple 仓库执行 `build-platform-frontend.bat`,将 `frontends/apps/simple-platform-vue/dist/` 同步到本目录 `wwwroot/`。
|
||||
|
||||
## 配置 SimpleLite 路径(必读)
|
||||
## 配置 Simple3 路径(必读)
|
||||
|
||||
> **配置文件位置(无 Web 界面):**
|
||||
> **`MiGu.Server/appsettings.json`** → 搜索 **`"SimpleLite"`** 节点。
|
||||
> 登录页选「本地 + Web / 仅 Web」后,后端在此配置的路径拉起 `SimpleLite.exe`。
|
||||
> 启动后可在浏览器打开 **`http://localhost:8080/api/health/simplelite`** 查看路径是否解析成功。
|
||||
> **`MiGu.Server/appsettings.json`** → 搜索 **`"Simple3"`** 节点。
|
||||
> 登录页选「本地 + Web / 仅 Web」后,后端在此配置的路径拉起 `Simple3.exe`。
|
||||
> 启动后可在浏览器打开 **`http://localhost:8080/api/health/simple3`** 查看路径是否解析成功。
|
||||
|
||||
登录成功后,MiGu.Server 会按所选启动模式拉起 **SimpleLite.exe**。默认已在 `appsettings.json` 写好与 Simple 仓库并列的相对路径;开发机可用 `appsettings.Development.json` 覆盖为绝对路径。
|
||||
登录成功后,MiGu.Server 会按所选启动模式拉起 **Simple3.exe**。默认已在 `appsettings.json` 写好与 Simple 仓库并列的相对路径;开发机可用 `appsettings.Development.json` 覆盖为绝对路径。
|
||||
|
||||
### 本机开发(与 Simple 仓库并列)
|
||||
|
||||
仓库默认已在 `appsettings.Development.json` 中写好:
|
||||
|
||||
```json
|
||||
"SimpleLite": {
|
||||
"Simple3": {
|
||||
"Enabled": true,
|
||||
"ExecutablePath": "E:\\Work\\Core\\Simple-FR\\Simple\\SimpleLite\\bin\\Debug\\SimpleLite.exe",
|
||||
"WorkingDirectory": "E:\\Work\\Core\\Simple-FR\\Simple\\SimpleLite\\bin\\Debug",
|
||||
"ExecutablePath": "E:\\Work\\Core\\Simple-FR\\Simple\\Simple3\\bin\\Debug\\Simple3.exe",
|
||||
"WorkingDirectory": "E:\\Work\\Core\\Simple-FR\\Simple\\Simple3\\bin\\Debug",
|
||||
"ProjectionPort": 8222,
|
||||
"ReadinessTimeoutMs": 8000,
|
||||
"FollowParent": false
|
||||
}
|
||||
```
|
||||
|
||||
请先编译 SimpleLite(在 Simple 仓库):
|
||||
请先编译 Simple3(在 Simple 仓库):
|
||||
|
||||
```pwsh
|
||||
cd ..\Simple
|
||||
dotnet build SimpleLite\SimpleLite.csproj -c Debug
|
||||
dotnet build Simple3\Simple3.csproj -c Debug
|
||||
```
|
||||
|
||||
若你的 Simple 不在上述绝对路径,可改为**相对路径**(相对 `MiGu.Server` 工作目录):
|
||||
|
||||
```json
|
||||
"ExecutablePath": "..\\..\\Simple\\SimpleLite\\bin\\Debug\\SimpleLite.exe",
|
||||
"WorkingDirectory": "..\\..\\Simple\\SimpleLite\\bin\\Debug"
|
||||
"ExecutablePath": "..\\..\\Simple\\Simple3\\bin\\Debug\\Simple3.exe",
|
||||
"WorkingDirectory": "..\\..\\Simple\\Simple3\\bin\\Debug"
|
||||
```
|
||||
|
||||
### 配置项说明
|
||||
|
||||
| 键 | 含义 |
|
||||
|----|------|
|
||||
| `Enabled` | `false` 时永不拉起 SimpleLite(只调试平台 UI) |
|
||||
| `ExecutablePath` | `SimpleLite.exe` 绝对或相对路径;留空则自动探测(见 `Launcher/SimpleLiteOptions.cs`) |
|
||||
| `Enabled` | `false` 时永不拉起 Simple3(只调试平台 UI) |
|
||||
| `ExecutablePath` | `Simple3.exe` 绝对或相对路径;留空则自动探测(见 `Launcher/Simple3Options.cs`) |
|
||||
| `WorkingDirectory` | 子进程工作目录;留空则用 exe 所在目录(读写 `simple.json` / `imgui.ini`) |
|
||||
| `ProjectionPort` | 就绪检测端口,默认 `8222` |
|
||||
| `ReadinessTimeoutMs` | 登录后等待 SimpleLite WebAPI 就绪的最长时间(毫秒) |
|
||||
| `FollowParent` | `true` 时 MiGu.Server 退出会结束 SimpleLite;默认 `false`(两进程独立) |
|
||||
| `ReadinessTimeoutMs` | 登录后等待 Simple3 WebAPI 就绪的最长时间(毫秒) |
|
||||
| `FollowParent` | `true` 时 MiGu.Server 退出会结束 Simple3;默认 `false`(两进程独立) |
|
||||
|
||||
### 环境变量覆盖
|
||||
|
||||
```pwsh
|
||||
$env:SimpleLite__ExecutablePath = "D:\apps\SimpleLite.exe"
|
||||
$env:SimpleLite__WorkingDirectory = "D:\apps"
|
||||
$env:SimpleLite__Enabled = "true"
|
||||
$env:Simple3__ExecutablePath = "D:\apps\Simple3.exe"
|
||||
$env:Simple3__WorkingDirectory = "D:\apps"
|
||||
$env:Simple3__Enabled = "true"
|
||||
```
|
||||
|
||||
### 生产 / 合并发布
|
||||
|
||||
将 `SimpleLite.exe` 与依赖 DLL 放到 `MiGu.Server` 同目录,并清空 `ExecutablePath`(走自动探测 `./SimpleLite.exe`),或在 `appsettings.Production.json` 写死部署路径。
|
||||
将 `Simple3.exe` 与依赖 DLL 放到 `MiGu.Server` 同目录,并清空 `ExecutablePath`(走自动探测 `./Simple3.exe`),或在 `appsettings.Production.json` 写死部署路径。
|
||||
|
||||
浏览器访问 `http://localhost:8080/login`:
|
||||
1. 用户名 / 密码(`appsettings.json:Auth.Users` 默认 admin/admin、ops/ops);
|
||||
2. 选 scope(管理员 / 运营);
|
||||
3. 选 **SimpleLite 启动模式**:
|
||||
3. 选 **Simple3 启动模式**:
|
||||
- **本地 + Web** → 透传 `--display-mode=web+local`,桌面 ImGui 窗口 + 浏览器同时启动;
|
||||
- **仅 Web** → 透传 `--display-mode=web`,只起 WebTerminal,不弹本地窗口;
|
||||
4. 点登录。`AuthController.Login` 调用 `SimpleLiteLauncher.MaybeStart(...)` 阻塞等 Projection (`:8222`) 就绪后返回 LoginResponse。
|
||||
4. 点登录。`AuthController.Login` 调用 `Simple3Launcher.MaybeStart(...)` 阻塞等 Projection (`:8222`) 就绪后返回 LoginResponse。
|
||||
|
||||
> 如果只想跑 MiGu.Server 单进程调试(不拉 SimpleLite),把 `appsettings.json:SimpleLite.Enabled` 改为 `false` 即可。
|
||||
> 如果只想跑 MiGu.Server 单进程调试(不拉 Simple3),把 `appsettings.json:Simple3.Enabled` 改为 `false` 即可。
|
||||
|
||||
## 数据库启动流程
|
||||
|
||||
@@ -198,7 +198,7 @@ SchemaMode = Migrate
|
||||
>
|
||||
> 详见代码审核 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。
|
||||
> **Simple3 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。
|
||||
|
||||
## 能力清单(本轮)
|
||||
|
||||
@@ -208,11 +208,11 @@ SchemaMode = Migrate
|
||||
- 兜底/部署模式:找不到 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);
|
||||
- `/api/sl/{**catch-all}` → `http://127.0.0.1:8222/`(Simple3 WebAPI;当前 Simple3 未启 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`;
|
||||
- Mock 鉴权:`/api/auth/login` 返回 Mock JWT + `EffectivePermissions`;LoginRequest 新增 `launchMode: "WebOnly" | "DesktopAndWeb"` 字段,登录成功后 `Simple3Launcher` 据此拉起子进程;
|
||||
- Simple3 子进程编排:`Launcher/Simple3Launcher.cs` 幂等 / Stdout 转发 / Windows JobObject 父子绑定 / 端口就绪等待,配置见 `appsettings.json:Simple3`;
|
||||
- 运维白名单网关占位:`/api/sl/ops/execute`、`/api/sl/ops/audits`;
|
||||
- 投影 API 占位:`/api/projection/{sites,tracks,cars,missions}`;
|
||||
- 健康检查:`/api/health`;
|
||||
@@ -255,7 +255,7 @@ MiGu.Server/
|
||||
└── wwwroot/ # 部署兜底(dev 模式优先用 frontends/.../dist;assets/ 与 index.html 已 ignore)
|
||||
```
|
||||
|
||||
## 独立启动(仅当你不通过 SimpleLite 自启时)
|
||||
## 独立启动(仅当你不通过 Simple3 自启时)
|
||||
|
||||
```pwsh
|
||||
# 1) 还原 + 编译 + 运行
|
||||
@@ -278,7 +278,7 @@ dotnet run
|
||||
> 启动时控制台会打印 `[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;
|
||||
> Simple3 自启 MiGu.Server 时,Program.cs 会从 `bin\<Config>\net8.0\` 沿目录向上找 dist;
|
||||
> 找到则使用源码区 dist,找不到则使用 bin 同级的 wwwroot——两种启动方式共用同一份前端产物。
|
||||
|
||||
## 与前端联调
|
||||
@@ -292,7 +292,7 @@ dotnet run
|
||||
- 若上级目录存在 `frontends/apps/simple-platform-vue/dist/`,Program.cs 会优先挂 dist;
|
||||
- 浏览器访问 `http://localhost:8080/login` 等路径由 vue-router 解析。
|
||||
|
||||
## YARP 与 SimpleLite
|
||||
## YARP 与 Simple3
|
||||
|
||||
YARP 路由配置见 `appsettings.json`:
|
||||
|
||||
@@ -312,7 +312,7 @@ YARP 路由配置见 `appsettings.json`:
|
||||
```
|
||||
|
||||
- `/api/sl/ops/{execute,audits}` 在本工程被 `OpsController` 显式接住(更具体的路由),用于占位测试;
|
||||
- 其他 `/api/sl/*` 由 YARP 透传到 SimpleLite `:8222`,SimpleLite 未启时会得到 502/连接被拒。
|
||||
- 其他 `/api/sl/*` 由 YARP 透传到 Simple3 `:8222`,Simple3 未启时会得到 502/连接被拒。
|
||||
|
||||
## iframe 嵌入 webVRender 的两种方式
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
"Database": {
|
||||
"SchemaMode": "EnsureCreated"
|
||||
},
|
||||
"SimpleLite": {
|
||||
"Simple3": {
|
||||
"Enabled": true,
|
||||
"ExecutablePath": "D:\\工作\\stand\\SimpleLite\\SimpleLite.exe",
|
||||
"WorkingDirectory": "D:\\工作\\stand\\SimpleLite",
|
||||
"ExecutablePath": "D:\\MDCS\\Release\\Simple\\Simple3\\Simple3.exe",
|
||||
"WorkingDirectory": "D:\\MDCS\\Release\\Simple\\Simple3",
|
||||
"ProjectionPort": 8222,
|
||||
"ReadinessTimeoutMs": 8000,
|
||||
"FollowParent": false
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
"Internal": {
|
||||
"Token": "REPLACE_ME"
|
||||
},
|
||||
"_comment_SimpleLite": "登录后拉起 SimpleLite;改路径请编辑下方 SimpleLite 节点。诊断: http://localhost:8080/api/health/simplelite",
|
||||
"SimpleLite": {
|
||||
"_comment_Simple3": "登录后拉起调度内核 Simple3.exe;改路径请编辑下方 Simple3 节点。诊断: http://localhost:8080/api/health/simple3",
|
||||
"Simple3": {
|
||||
"Enabled": true,
|
||||
"ExecutablePath": "D:\\工作\\stand\\SimpleLite\\SimpleLite.exe",
|
||||
"WorkingDirectory": "D:\\工作\\stand\\SimpleLite",
|
||||
"ExecutablePath": "D:\\MDCS\\Release\\Simple\\Simple3\\Simple3.exe",
|
||||
"WorkingDirectory": "D:\\MDCS\\Release\\Simple\\Simple3",
|
||||
"Arguments": "",
|
||||
"ReadinessTimeoutMs": 15000,
|
||||
"ReadinessPollIntervalMs": 250,
|
||||
@@ -39,7 +39,7 @@
|
||||
"ops": { "Password": "ops" }
|
||||
}
|
||||
},
|
||||
"_comment_Ops": "运维操作真实下发映射(M4)。opCode → 'kind:Method',Method 必须是 SimpleLite 反射 [MethodMember] 的真实方法名(车辆 kind=car,见 Car.cs:OnlineCar/OfflineCar/DisableCar/EnableCar/Repair/Blown/Reset 等;任务 kind=mission)。运营白名单的 pause/resume/gohome/manualCharge 内核暂无一一对应方法——留空则仅记审计并向前端如实返回『未下发』,按现场内核能力填写后即真实生效。示例: 'ops.car.gohome': 'car:Reset'",
|
||||
"_comment_Ops": "运维操作真实下发映射(M4)。opCode → 'kind:Method',Method 必须是 Simple3 反射 [MethodMember] 的真实方法名(车辆 kind=car,见 Car.cs:OnlineCar/OfflineCar/DisableCar/EnableCar/Repair/Blown/Reset 等;任务 kind=mission)。运营白名单的 pause/resume/gohome/manualCharge 内核暂无一一对应方法——留空则仅记审计并向前端如实返回『未下发』,按现场内核能力填写后即真实生效。示例: 'ops.car.gohome': 'car:Reset'",
|
||||
"Ops": {
|
||||
"Dispatch": {
|
||||
}
|
||||
|
||||
+26
-26
@@ -1,43 +1,43 @@
|
||||
# 平台 v3 重构 — 阶段 0~6 实际改动
|
||||
|
||||
> 范围:把 SimpleLite WebTerminal 由"全功能浮动 Panel"模式收敛到"纯 3D Canvas",所有 UI 改由 Vue 平台前端 (`frontends/apps/simple-platform-vue`) 接管;同时补齐反射 / 编辑 / 监控 / 编排 4 大业务面板与撤销栈、AI 生图、报警 SSE 等关键能力。
|
||||
> 范围:把 Simple3 WebTerminal 由"全功能浮动 Panel"模式收敛到"纯 3D Canvas",所有 UI 改由 Vue 平台前端 (`frontends/apps/simple-platform-vue`) 接管;同时补齐反射 / 编辑 / 监控 / 编排 4 大业务面板与撤销栈、AI 生图、报警 SSE 等关键能力。
|
||||
>
|
||||
> 基线:上一份方案见 `ARCHITECTURE.md` v2/v3 章节;后续以本文件为准。
|
||||
|
||||
## 编译验证
|
||||
|
||||
- `dotnet build SimpleLite/SimpleLite.csproj` —— 0 警告 0 错误
|
||||
- `dotnet build Simple3/Simple3.csproj` —— 0 警告 0 错误
|
||||
- `dotnet build MiGu.Server/MiGu.Server.csproj` —— 0 警告 0 错误
|
||||
- `build-platform-frontend.bat` —— vite build 46.5s,无 vue-tsc / ts 错误,robocopy /MIR 已同步 MiGu.Server/wwwroot
|
||||
- 旧 `SimpleComposer` (WinForms) 在 v3 之外,不在本次构建目标里
|
||||
- 旧 `HttpClientHelper` 提升 LangVersion=latest 修通
|
||||
|
||||
## 阶段 0:后端 SimpleLite 平台化(已完成)
|
||||
## 阶段 0:后端 Simple3 平台化(已完成)
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `SimpleLite/Web/MapEditApiController.cs` | 写操作 8 路 API:create/delete/batch/copy-to/pick/dashboard/upload/ai-generate;阶段 6 又补:**project/save**、**project/load**、**project/current**、**sample-sites-along-track** |
|
||||
| `SimpleLite/Web/ReflectionApiController.cs` | 只读 + execute API;listKinds / listObjects / methods-by-type / setField / getBundle / selection 等 |
|
||||
| `SimpleLite/Web/ReflectionApiDtos.cs` | 反射 API DTO + 统一信封 `ReflectionEnvelope<T>` |
|
||||
| `SimpleLite/Web/AiCompletionService.cs` | OpenAI Function Calling 调用 + map-generate 工具桥(真实 LLM,不再桩) |
|
||||
| `SimpleLite/Web/AiConfigController.cs` | AI 服务配置(endpoint/apiKey/model/system prompt/temperature/maxTokens/timeout)读写 |
|
||||
| `SimpleLite/Web/AlarmStreamService.cs` | 1s 周期 diff `car.status.enums.AlarmInfo`(语义与 `E:\Work\FRLD\StandardScene\Forklift.cs` 的 `Commons.GetCarStatus(this, "AlarmInfo")` 等价),变化时通过 SSE `alarm` 事件推送 raise/update/clear |
|
||||
| `SimpleLite/Web/ProjectionStreamModule.cs` | 平台 SSE 总线,对外广播 `object-created` / `object-deleted` / `object-patched` / `object-batch-changed` / `alarm` / `pick-result` / `project-saved` / `project-loaded` |
|
||||
| `SimpleLite/Web/PluginAssemblyScan.cs` | 反射扫描 plugins/*.dll,把 `MethodMember` 标注的方法 + Car/Mission/CarProgram/Site/Track 子类型暴露给反射 API |
|
||||
| `Simple3/Web/MapEditApiController.cs` | 写操作 8 路 API:create/delete/batch/copy-to/pick/dashboard/upload/ai-generate;阶段 6 又补:**project/save**、**project/load**、**project/current**、**sample-sites-along-track** |
|
||||
| `Simple3/Web/ReflectionApiController.cs` | 只读 + execute API;listKinds / listObjects / methods-by-type / setField / getBundle / selection 等 |
|
||||
| `Simple3/Web/ReflectionApiDtos.cs` | 反射 API DTO + 统一信封 `ReflectionEnvelope<T>` |
|
||||
| `Simple3/Web/AiCompletionService.cs` | OpenAI Function Calling 调用 + map-generate 工具桥(真实 LLM,不再桩) |
|
||||
| `Simple3/Web/AiConfigController.cs` | AI 服务配置(endpoint/apiKey/model/system prompt/temperature/maxTokens/timeout)读写 |
|
||||
| `Simple3/Web/AlarmStreamService.cs` | 1s 周期 diff `car.status.enums.AlarmInfo`(语义与 `E:\Work\FRLD\StandardScene\Forklift.cs` 的 `Commons.GetCarStatus(this, "AlarmInfo")` 等价),变化时通过 SSE `alarm` 事件推送 raise/update/clear |
|
||||
| `Simple3/Web/ProjectionStreamModule.cs` | 平台 SSE 总线,对外广播 `object-created` / `object-deleted` / `object-patched` / `object-batch-changed` / `alarm` / `pick-result` / `project-saved` / `project-loaded` |
|
||||
| `Simple3/Web/PluginAssemblyScan.cs` | 反射扫描 plugins/*.dll,把 `MethodMember` 标注的方法 + Car/Mission/CarProgram/Site/Track 子类型暴露给反射 API |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 关键改动 |
|
||||
|------|----------|
|
||||
| `SimpleLite/Startup.cs` | EntryPoint 第一步 `EnsureImguiIniFromEmbedded()`;启动 AlarmStreamService、ProjectionStreamModule、AiConfig 加载 |
|
||||
| `SimpleLite/UI/SimpleUI.cs` | `CreateRemotePanelSetForTerminal` 检测 CanvasOnly 旗标:不创建 Workbench / Toolbar / BottomBar / CAD / Welcome;保留 SelectionAction、SceneInput、SceneDrawable、2DMapView |
|
||||
| `SimpleLite/Workspace/WorkspaceWebUiContext.cs` | 增加 `IsCanvasOnly(terminal)` 旗标 + `/declareCanvasOnly` 注册器(与 `/declareEmbedUi` 互斥) |
|
||||
| `SimpleLite/Web/ProjectionWebHost.cs` | `/declareCanvasOnly` 路由注册 |
|
||||
| `SimpleLite/Web/ProjectionWebApiController.cs` | 现有 projection API 上挂 `selection-detail` SSE hook |
|
||||
| `SimpleLite/UI/ComposerDockPanel.cs` | CanvasOnly 时 PrepareWelcomePanel 返回空 |
|
||||
| `SimpleLite/Props/UIHelpers.cs` | 增加 `UI_Image` / `UI_Text` / `UI_Model` PropType。`UI_Model` 用于 CycleGUI 装饰物挂模型(obj/glb/gltf/cyclemodel) |
|
||||
| `Simple3/Startup.cs` | EntryPoint 第一步 `EnsureImguiIniFromEmbedded()`;启动 AlarmStreamService、ProjectionStreamModule、AiConfig 加载 |
|
||||
| `Simple3/UI/SimpleUI.cs` | `CreateRemotePanelSetForTerminal` 检测 CanvasOnly 旗标:不创建 Workbench / Toolbar / BottomBar / CAD / Welcome;保留 SelectionAction、SceneInput、SceneDrawable、2DMapView |
|
||||
| `Simple3/Workspace/WorkspaceWebUiContext.cs` | 增加 `IsCanvasOnly(terminal)` 旗标 + `/declareCanvasOnly` 注册器(与 `/declareEmbedUi` 互斥) |
|
||||
| `Simple3/Web/ProjectionWebHost.cs` | `/declareCanvasOnly` 路由注册 |
|
||||
| `Simple3/Web/ProjectionWebApiController.cs` | 现有 projection API 上挂 `selection-detail` SSE hook |
|
||||
| `Simple3/UI/ComposerDockPanel.cs` | CanvasOnly 时 PrepareWelcomePanel 返回空 |
|
||||
| `Simple3/Props/UIHelpers.cs` | 增加 `UI_Image` / `UI_Text` / `UI_Model` PropType。`UI_Model` 用于 CycleGUI 装饰物挂模型(obj/glb/gltf/cyclemodel) |
|
||||
|
||||
### 关键 API 路径
|
||||
|
||||
@@ -119,7 +119,7 @@ SSE /projection/stream # 所有 SSE 事件
|
||||
- 报警来源走 `useMapEditStream.onAlarm`(订阅 SSE 的 `alarm` 事件,由 AlarmStreamService 1s diff 推送)
|
||||
- KPI 3 卡:在线车辆 / 运行中任务 / 排队任务
|
||||
- 实时列表:左侧 Workspace3D + 右侧 VehicleMonitorPanel + SelectionDetailPanel
|
||||
- 「定位」按钮通过 `reflectionApi.setSelection('car', id)` 让 SimpleLite 3D 高亮该车(视觉上等效 flyTo)
|
||||
- 「定位」按钮通过 `reflectionApi.setSelection('car', id)` 让 Simple3 3D 高亮该车(视觉上等效 flyTo)
|
||||
|
||||
## 阶段 5:OrchestrationView 设计与编排(已完成)
|
||||
|
||||
@@ -159,9 +159,9 @@ Tab 状态缓存到 sessionStorage。
|
||||
|
||||
### 编译相关清理
|
||||
|
||||
- `SimpleLite/Recording/`(之前的录制/回放实验目录)误用 `SimpleLite.Rendering` namespace、`Car` 找不到、`Start(string, out)` 默认参数顺序等 3 个错误 → 修
|
||||
- `SimpleLite/Props/UISite.cs` 在 `SimpleLite.Props` namespace 下 `Workspace.AddProp` 被解析成 `SimpleLite.Workspace.AddProp` → 改为全限定 `CycleGUI.Workspace.AddProp`
|
||||
- `SimpleLite/Workspace/WorkspaceBottomBar.cs` 缺 `using SimpleLite.Workspace;` → 补
|
||||
- `Simple3/Recording/`(之前的录制/回放实验目录)误用 `Simple3.Rendering` namespace、`Car` 找不到、`Start(string, out)` 默认参数顺序等 3 个错误 → 修
|
||||
- `Simple3/Props/UISite.cs` 在 `Simple3.Props` namespace 下 `Workspace.AddProp` 被解析成 `Simple3.Workspace.AddProp` → 改为全限定 `CycleGUI.Workspace.AddProp`
|
||||
- `Simple3/Workspace/WorkspaceBottomBar.cs` 缺 `using Simple3.Workspace;` → 补
|
||||
- `HttpClientHelper.csproj` LangVersion 7.3 → latest
|
||||
|
||||
## 已知 TODO(不阻塞 v3 上线)
|
||||
@@ -176,16 +176,16 @@ Tab 状态缓存到 sessionStorage。
|
||||
## 启动方式
|
||||
|
||||
```
|
||||
# 1. 后端 build(SimpleLite + MiGu.Server)
|
||||
dotnet build SimpleLite\SimpleLite.csproj
|
||||
# 1. 后端 build(Simple3 + MiGu.Server)
|
||||
dotnet build Simple3\Simple3.csproj
|
||||
dotnet build MiGu.Server\MiGu.Server.csproj
|
||||
|
||||
# 2. 前端打包并同步到 wwwroot
|
||||
.\build-platform-frontend.bat
|
||||
|
||||
# 3. 启动平台 + SimpleLite
|
||||
# 3. 启动平台 + Simple3
|
||||
dotnet run --project MiGu.Server # 监听 0.0.0.0:8080
|
||||
# SimpleLite 端口 8222/8223 由 SimpleLite 自己启动
|
||||
# Simple3 端口 8222/8223 由 Simple3 自己启动
|
||||
|
||||
# 4. 访问
|
||||
http://127.0.0.1:8080/admin/map-editor # 地图编辑(v3 四栏壳)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Migu2.0
|
||||
|
||||
迷毂智能调度平台主仓库(平台后端 + 平台前端)。
|
||||
该仓库承接原 `Simple` 仓库中的平台层能力,当前以 `MiGu.Server` 作为统一入口;调度内核 `SimpleLite` 仍在相邻仓库 `../Simple` 中维护。
|
||||
该仓库承接原 `Simple` 仓库中的平台层能力,当前以 `MiGu.Server` 作为统一入口;调度内核 `Simple3` 仍在相邻仓库 `../Simple` 中维护。
|
||||
|
||||
## 仓库定位
|
||||
|
||||
- **平台后端**:`MiGu.Server`(ASP.NET Core 8 + YARP,默认 `:8080`)
|
||||
- **平台前端**:`frontends/apps/simple-platform-vue`(Vue 3 + Vite)
|
||||
- **调度内核**:`SimpleLite`(不在本仓库,位于 `../Simple/SimpleLite`)
|
||||
- **运行模式**:用户登录后由 `MiGu.Server` 按启动模式拉起 `SimpleLite.exe`
|
||||
- **调度内核**:`Simple3`(不在本仓库,位于 `../Simple/Simple3`)
|
||||
- **运行模式**:用户登录后由 `MiGu.Server` 按启动模式拉起 `Simple3.exe`
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
Migu2.0/
|
||||
├── MiGu.Server/ # 平台后端入口工程
|
||||
│ ├── Controllers/ # Auth/Config/Ops/Health 等 API
|
||||
│ ├── Launcher/ # SimpleLite 子进程拉起与健康探测
|
||||
│ ├── Launcher/ # Simple3 子进程拉起与健康探测
|
||||
│ ├── Auth/ # JWT / 用户鉴权 / 内部 token
|
||||
│ ├── Configs/ # 平台配置中心模型与存取
|
||||
│ ├── wwwroot/ # 已构建前端静态资源(部署用)
|
||||
│ ├── appsettings.json # 启动、YARP、SimpleLite 路径配置
|
||||
│ ├── appsettings.json # 启动、YARP、Simple3 路径配置
|
||||
│ └── README.md # 后端详细说明
|
||||
├── frontends/ # 平台前端源码 workspace
|
||||
│ └── apps/simple-platform-vue/
|
||||
@@ -31,35 +31,35 @@ Migu2.0/
|
||||
└── Doc/
|
||||
```
|
||||
|
||||
## 与 SimpleLite 的关系
|
||||
## 与 Simple3 的关系
|
||||
|
||||
`Migu2.0` 与 `SimpleLite` 是分仓协作关系:
|
||||
`Migu2.0` 与 `Simple3` 是分仓协作关系:
|
||||
|
||||
- `MiGu.Server` 提供登录、权限、配置中心、平台 UI 托管与网关能力
|
||||
- `SimpleLite` 提供调度内核、投影接口、3D/仿真运行时能力
|
||||
- `Simple3` 提供调度内核、投影接口、3D/仿真运行时能力
|
||||
- 两者通过以下链路协作:
|
||||
- **进程编排**:`MiGu.Server` 登录后调用 `SimpleLiteLauncher` 拉起 `SimpleLite.exe`
|
||||
- **进程编排**:`MiGu.Server` 登录后调用 `Simple3Launcher` 拉起 `Simple3.exe`
|
||||
- **接口代理**:`/api/sl/*` 通过 YARP 转发到 `http://127.0.0.1:8222/`
|
||||
- **3D 页面代理**:`/vr/*` 转发到 `http://127.0.0.1:8223/`
|
||||
|
||||
默认开发路径(可改)在 `MiGu.Server/appsettings.json`:
|
||||
|
||||
```json
|
||||
"SimpleLite": {
|
||||
"Simple3": {
|
||||
"Enabled": true,
|
||||
"ExecutablePath": "..\\..\\Simple\\SimpleLite\\bin\\Debug\\SimpleLite.exe",
|
||||
"WorkingDirectory": "..\\..\\Simple\\SimpleLite\\bin\\Debug",
|
||||
"ExecutablePath": "..\\..\\Simple\\Simple3\\bin\\Debug\\Simple3.exe",
|
||||
"WorkingDirectory": "..\\..\\Simple\\Simple3\\bin\\Debug",
|
||||
"ProjectionPort": 8222
|
||||
}
|
||||
```
|
||||
|
||||
## 快速启动(开发机)
|
||||
|
||||
### 1) 先编译 SimpleLite
|
||||
### 1) 先编译 Simple3
|
||||
|
||||
```powershell
|
||||
cd ..\Simple
|
||||
dotnet build SimpleLite\SimpleLite.csproj -c Debug
|
||||
dotnet build Simple3\Simple3.csproj -c Debug
|
||||
```
|
||||
|
||||
### 2) 启动 MiGu.Server
|
||||
@@ -74,7 +74,7 @@ dotnet run
|
||||
- `admin / admin`
|
||||
- `ops / ops`
|
||||
|
||||
> 仅调试平台页面、不拉起内核:将 `MiGu.Server/appsettings.json` 中 `SimpleLite.Enabled` 改为 `false`。
|
||||
> 仅调试平台页面、不拉起内核:将 `MiGu.Server/appsettings.json` 中 `Simple3.Enabled` 改为 `false`。
|
||||
|
||||
## 前端构建与发布
|
||||
|
||||
@@ -106,7 +106,7 @@ cd E:\Work\Core\Simple-FR\Migu2.0
|
||||
- `Jwt.Secret`
|
||||
- `Internal.Token`
|
||||
- `Auth.Users.*.Password`
|
||||
- `SimpleLite.ExecutablePath` / `WorkingDirectory`(按部署实际路径)
|
||||
- `Simple3.ExecutablePath` / `WorkingDirectory`(按部署实际路径)
|
||||
|
||||
推荐通过环境变量覆盖(`__` 表示层级):
|
||||
|
||||
@@ -119,8 +119,8 @@ $env:Auth__Users__admin__Password = "<strong-password>"
|
||||
## 常见检查点
|
||||
|
||||
- `http://localhost:8080/api/health`:后端健康
|
||||
- `http://localhost:8080/api/health/simplelite`:SimpleLite 路径与端口诊断
|
||||
- 访问平台时若出现 `502`:通常表示 `SimpleLite` 的 `8222` 未就绪或未启动
|
||||
- `http://localhost:8080/api/health/simple3`:Simple3 路径与端口诊断
|
||||
- 访问平台时若出现 `502`:通常表示 `Simple3` 的 `8222` 未就绪或未启动
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
||||
+6
-6
@@ -5,7 +5,7 @@
|
||||
> 通过路由前缀 `/admin/*` 与 `/monitor/*` 切换,后续可按 ARCHITECTURE.md §13 拆分为独立工程。
|
||||
>
|
||||
> 落地范围与依据:本目录是 [ARCHITECTURE.md](../ARCHITECTURE.md) v1.7 §6/§9/§13/§17/§18 中前端形态的初版骨架;
|
||||
> 真实业务接入由 `Platform.Server`(同仓库新增的 .NET 8 工程)反代 SimpleLite 完成。
|
||||
> 真实业务接入由 `Platform.Server`(同仓库新增的 .NET 8 工程)反代 Simple3 完成。
|
||||
>
|
||||
> 品牌主色:默认 **工业紫** `#6a1b9a`(6 套主题可切换),详见 ARCHITECTURE.md §17.7。
|
||||
> 工程包名 `simple-platform-vue` / 仓库目录 `frontends/apps/simple-platform-vue` 保持不动,避免破坏 pnpm/CI 脚本,仅用户可见文案统一为「迷毂」。
|
||||
@@ -82,7 +82,7 @@ pnpm dev
|
||||
## 地图监控页与 webVRender
|
||||
|
||||
地图监控页(`/admin/map-monitor` 与 `/monitor/map`)通过 `<Workspace3D>` 组件以 iframe 嵌入
|
||||
SimpleLite WebTerminal 提供的 webVRender,默认 URL:
|
||||
Simple3 WebTerminal 提供的 webVRender,默认 URL:
|
||||
|
||||
```
|
||||
http://localhost:8223/?scope=Platform&token=<jwt>&ro=0 # 管理员,可交互
|
||||
@@ -93,10 +93,10 @@ http://localhost:8223/?scope=RCSMonitor&token=<jwt>&ro=1 # 运营,只读
|
||||
|
||||
### iframe 嵌入注意事项(CORS / X-Frame-Options)
|
||||
|
||||
- 当前 SimpleLite WebTerminal 暂未声明 `X-Frame-Options`/`Content-Security-Policy: frame-ancestors`,
|
||||
- 当前 Simple3 WebTerminal 暂未声明 `X-Frame-Options`/`Content-Security-Policy: frame-ancestors`,
|
||||
浏览器多数情况下会按默认策略放行同源 / 局域网嵌入。
|
||||
- 若浏览器控制台报「Refused to display 'http://localhost:8223/' in a frame」,请在 SimpleLite WebTerminal
|
||||
侧加上 `X-Frame-Options: ALLOWALL` 或 `Content-Security-Policy: frame-ancestors *`(本轮代码未改动 SimpleLite)。
|
||||
- 若浏览器控制台报「Refused to display 'http://localhost:8223/' in a frame」,请在 Simple3 WebTerminal
|
||||
侧加上 `X-Frame-Options: ALLOWALL` 或 `Content-Security-Policy: frame-ancestors *`(本轮代码未改动 Simple3)。
|
||||
- 若需通过 `Platform.Server` 走同源代理,可以让 iframe 指向 `http://localhost:8080/vr/`,由 YARP
|
||||
转发到 `http://127.0.0.1:8223/`(见 `Platform.Server/appsettings.json` 的 `vrender-route`)。
|
||||
|
||||
@@ -107,7 +107,7 @@ http://localhost:8223/?scope=RCSMonitor&token=<jwt>&ro=1 # 运营,只读
|
||||
- `/api/config/{section}` → `Platform.Server` `ConfigController`(14 维度占位)
|
||||
- `/api/health` → `Platform.Server` `HealthController`
|
||||
- `/api/projection/*` → `Platform.Server` `ProjectionController`(占位)
|
||||
- `/api/sl/*` → YARP 反代 SimpleLite WebAPI `http://127.0.0.1:8222/`
|
||||
- `/api/sl/*` → YARP 反代 Simple3 WebAPI `http://127.0.0.1:8222/`
|
||||
- `/api/sl/ops/execute` 与 `/api/sl/ops/audits` → `Platform.Server` `OpsController`(白名单网关占位)
|
||||
|
||||
### Mock 开关:`VITE_USE_MOCK`
|
||||
|
||||
@@ -3,7 +3,7 @@ import http from './http'
|
||||
/**
|
||||
* AI 助手 API:会话/工具走 axios(带鉴权拦截器);对话走原生 fetch 流式(SSE),
|
||||
* 因为 EventSource 只能 GET、且无法设置 Authorization header。fetch 这里手动对齐
|
||||
* axios 的双轨鉴权(Cookie + Bearer + X-Scope)。后端见 SimpleLite `Web/Assistant/AssistantApi.cs`。
|
||||
* axios 的双轨鉴权(Cookie + Bearer + X-Scope)。后端见 Simple3 `Web/Assistant/AssistantApi.cs`。
|
||||
*/
|
||||
|
||||
const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api'
|
||||
@@ -120,7 +120,7 @@ export async function streamChat(
|
||||
if (!resp.ok || !resp.body) {
|
||||
let msg = `请求失败(HTTP ${resp.status})`
|
||||
if (resp.status === 403) msg = '无权使用 AI 助手(需要 Platform 权限)。'
|
||||
else if (resp.status === 502 || resp.status === 504) msg = 'SimpleLite 未连接:请先启动后端(端口 8222)。'
|
||||
else if (resp.status === 502 || resp.status === 504) msg = 'Simple3 未连接:请先启动后端(端口 8222)。'
|
||||
else {
|
||||
try {
|
||||
const t = await resp.text()
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function fetchFleetHealth(): Promise<FleetHealthRow[]> {
|
||||
await new Promise((r) => setTimeout(r, 120))
|
||||
return mockFleetHealth()
|
||||
}
|
||||
// 平台侧聚合:SimpleLite 指标 + WatchDog(:9776) TCP RTT。
|
||||
// 平台侧聚合:Simple3 指标 + WatchDog(:9776) TCP RTT。
|
||||
// 不再直打 /sl/projection/fleet/health(其探测车载 :8081,现场多数未开导致假超时 2000ms)。
|
||||
try {
|
||||
const { data } = await http.get<FleetHealthRow[]>('/fleet/health')
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface HealthInfo {
|
||||
uptimeSec: number
|
||||
}
|
||||
|
||||
export interface SimpleLiteDiagnostics {
|
||||
export interface Simple3Diagnostics {
|
||||
enabled: boolean
|
||||
isRunning: boolean
|
||||
lastLaunchMode?: string | null
|
||||
@@ -18,7 +18,7 @@ export interface SimpleLiteDiagnostics {
|
||||
deployHint?: string | null
|
||||
}
|
||||
|
||||
export interface SimpleLiteLaunchResult {
|
||||
export interface Simple3LaunchResult {
|
||||
started: boolean
|
||||
status: string
|
||||
detail: string
|
||||
@@ -30,29 +30,26 @@ export function getHealth() {
|
||||
return http.get<HealthInfo>('/health')
|
||||
}
|
||||
|
||||
export function getSimpleLiteDiagnostics() {
|
||||
return http.get<SimpleLiteDiagnostics>('/health/simplelite')
|
||||
export function getSimple3Diagnostics() {
|
||||
return http.get<Simple3Diagnostics>('/health/simple3')
|
||||
}
|
||||
|
||||
export function stopSimpleLite() {
|
||||
return http.post<{ killed: number; diagnostics: SimpleLiteDiagnostics }>('/health/simplelite/stop')
|
||||
export function stopSimple3() {
|
||||
return http.post<{ killed: number; diagnostics: Simple3Diagnostics }>('/health/simple3/stop')
|
||||
}
|
||||
|
||||
export function restartSimpleLite(launchMode: LaunchMode) {
|
||||
return http.post<{ restart: SimpleLiteLaunchResult; diagnostics: SimpleLiteDiagnostics }>(
|
||||
'/health/simplelite/restart',
|
||||
export function restartSimple3(launchMode: LaunchMode) {
|
||||
return http.post<{ restart: Simple3LaunchResult; diagnostics: Simple3Diagnostics }>(
|
||||
'/health/simple3/restart',
|
||||
null,
|
||||
{ params: { launchMode }, timeout: 60_000 }
|
||||
)
|
||||
}
|
||||
|
||||
/** 从诊断/会话 runMode 推断重启时使用的 launchMode。 */
|
||||
/** Simple3 仅 Web;重启一律 WebOnly(保留函数签名供旧调用方)。 */
|
||||
export function resolveRestartLaunchMode(
|
||||
lastLaunchMode: string | null | undefined,
|
||||
runMode: RunMode | null | undefined
|
||||
_lastLaunchMode?: string | null,
|
||||
_runMode?: RunMode | null
|
||||
): LaunchMode {
|
||||
const mode = (lastLaunchMode ?? '').toLowerCase()
|
||||
if (mode === 'web') return 'WebOnly'
|
||||
if (mode === 'web+local') return 'DesktopAndWeb'
|
||||
return runMode === 'WebOnly' ? 'WebOnly' : 'DesktopAndWeb'
|
||||
return 'WebOnly'
|
||||
}
|
||||
|
||||
@@ -31,21 +31,21 @@ http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* 把 axios 异常翻译成更友好的中文文案,特别是 SimpleLite 链路:
|
||||
* 把 axios 异常翻译成更友好的中文文案,特别是 Simple3 链路:
|
||||
*
|
||||
* /api/sl/projection/** → Platform.Server YARP → SimpleLite EmbedIO :8222
|
||||
* /api/sl/projection/** → Platform.Server YARP → Simple3 EmbedIO :8222
|
||||
*
|
||||
* 如果 SimpleLite 没启动 / 端口未监听,YARP 一定回 502;超时一般是 504。把这两种
|
||||
* 情况单独识别,写成「SimpleLite 未连接,请先启动 SimpleLite (端口 8222)」之类,
|
||||
* 如果 Simple3 没启动 / 端口未监听,YARP 一定回 502;超时一般是 504。把这两种
|
||||
* 情况单独识别,写成「Simple3 未连接,请先启动 Simple3 (端口 8222)」之类,
|
||||
* 比直接抛 `Request failed with status code 502` 友好得多,也避免用户怀疑前端 bug。
|
||||
*/
|
||||
function describeError(err: AxiosError): string {
|
||||
const status = err.response?.status
|
||||
const url = err.config?.url ?? ''
|
||||
const isSimpleLite = url.startsWith('/sl/') || url.startsWith('sl/')
|
||||
const isSimple3 = url.startsWith('/sl/') || url.startsWith('sl/')
|
||||
if (status === 502 || status === 504 || err.code === 'ECONNABORTED' || err.code === 'ERR_NETWORK') {
|
||||
if (isSimpleLite) {
|
||||
return 'SimpleLite 未连接:请先启动 SimpleLite 后端(监听端口 8222)后重试。'
|
||||
if (isSimple3) {
|
||||
return 'Simple3 未连接:请先启动 Simple3 后端(监听端口 8222)后重试。'
|
||||
}
|
||||
return `网关无法连接到下游服务(${status ?? err.code ?? 'network'})。`
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { AxiosError } from 'axios'
|
||||
import http from './http'
|
||||
|
||||
/**
|
||||
* 与 SimpleLite `MapEditApiController` 对应的前端胶水。
|
||||
* 路径前缀:`/sl/projection/map-edit`(YARP → SimpleLite `/projection/map-edit`)。
|
||||
* 与 Simple3 `MapEditApiController` 对应的前端胶水。
|
||||
* 路径前缀:`/sl/projection/map-edit`(YARP → Simple3 `/projection/map-edit`)。
|
||||
*
|
||||
* 也覆盖 AI 服务配置 `/sl/projection/ai-config`(GET / POST),由 AiConfigController 提供。
|
||||
*
|
||||
@@ -205,7 +205,7 @@ export const mapEditApi = {
|
||||
unwrap<{ count: number; results: unknown[] }>(http.post(`${BASE}/objects/batch`, { ops })),
|
||||
|
||||
/**
|
||||
* CAD 相对包围盒对齐(SimpleLite CadAlignService)。
|
||||
* CAD 相对包围盒对齐(Simple3 CadAlignService)。
|
||||
* mode: left|right|top|bottom|centerH|centerV|center|distributeH|distributeV
|
||||
*/
|
||||
cadAlign: (mode: string, targets: Array<{ kind: string; id: number }>) =>
|
||||
@@ -264,8 +264,15 @@ export const mapEditApi = {
|
||||
),
|
||||
|
||||
// 拾取 / 仪表盘。snap=false 时落点用原始鼠标坐标(文本/图片/站点);布线取端点默认 true。
|
||||
pick: (opts?: { snap?: boolean }) =>
|
||||
unwrap<PickResult>(http.post(`${BASE}/pick`, { snap: opts?.snap ?? true })),
|
||||
pick: (opts?: { snap?: boolean; signal?: AbortSignal }) =>
|
||||
unwrap<PickResult>(http.post(
|
||||
`${BASE}/pick`,
|
||||
{ snap: opts?.snap ?? true },
|
||||
{ timeout: 0, signal: opts?.signal }
|
||||
)),
|
||||
|
||||
cancelPick: () =>
|
||||
unwrap<{ cancelled: boolean }>(http.post(`${BASE}/pick/cancel`)),
|
||||
|
||||
dashboardSummary: () =>
|
||||
unwrap<DashboardSummary>(http.get(`${BASE}/dashboard/summary`)),
|
||||
@@ -300,9 +307,9 @@ export const mapEditApi = {
|
||||
}>(http.get(`${BASE}/project/browse`, { params: dir ? { dir } : {} })),
|
||||
|
||||
/**
|
||||
* 在 SimpleLite 桌面进程上弹 Windows 原生「打开文件」对话框,
|
||||
* 在 Simple3 桌面进程上弹 Windows 原生「打开文件」对话框,
|
||||
* 让用户挑一个项目 JSON。返回 { path, cancelled };cancelled=true 时 path 为 null。
|
||||
* initialDir 默认 SimpleLite 程序工作目录。
|
||||
* initialDir 默认 Simple3 程序工作目录。
|
||||
*/
|
||||
projectNativePickOpen: (initialDir?: string) =>
|
||||
unwrap<{ path: string | null; cancelled: boolean }>(
|
||||
@@ -328,7 +335,7 @@ export const mapEditApi = {
|
||||
|
||||
/**
|
||||
* 读取后端「过滤」菜单当前的三组开关。编辑器载入时拉一次,让前端 reactive 状态
|
||||
* 与 SimpleLite 真实开关一致(避免「前端勾着、后端没改」的偏差)。
|
||||
* 与 Simple3 真实开关一致(避免「前端勾着、后端没改」的偏差)。
|
||||
*/
|
||||
getViewFilter: () => unwrap<ViewFilterSnapshot>(http.get(`${BASE}/view-filter`)),
|
||||
|
||||
@@ -348,7 +355,7 @@ export const aiConfigApi = {
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// 地图管理(固定文件夹 + 按名称列表 / 保存 / 删除 / 使用 / 编辑打开)
|
||||
// 对应 SimpleLite MapEditApiController 的 /map-edit/maps* 接口。
|
||||
// 对应 Simple3 MapEditApiController 的 /map-edit/maps* 接口。
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MapListItem {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Mission, MissionStatus } from '@/types/mission'
|
||||
import { mockSites, mockTracks, mockCars, mockMissions } from '@/mock/server'
|
||||
import { applyRuntimeEnrichment, deriveCarState } from '@/utils/carRuntime'
|
||||
|
||||
/** 设为 true 时使用本地 Mock;默认走 YARP → SimpleLite :8222。
|
||||
/** 设为 true 时使用本地 Mock;默认走 YARP → Simple3 :8222。
|
||||
* 与 auth.ts / config.ts / ops.ts 统一走 VITE_USE_MOCK 开关(替代旧的 VITE_PROJECTION_MOCK)。 */
|
||||
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
|
||||
|
||||
@@ -114,7 +114,7 @@ async function enrichCarsRuntime(cars: Car[]): Promise<Car[]> {
|
||||
)
|
||||
}
|
||||
|
||||
/** 优先走 SimpleLite /projection/cars;502 或空列表时回退 reflection 对象列表(与 3D 场景同源)。 */
|
||||
/** 优先走 Simple3 /projection/cars;502 或空列表时回退 reflection 对象列表(与 3D 场景同源)。 */
|
||||
export async function listCars(): Promise<Car[]> {
|
||||
if (MOCK) return mockCars()
|
||||
try {
|
||||
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
|
||||
|
||||
/**
|
||||
* 与 SimpleLite `ReflectionApiController` 一一对应的前端胶水。
|
||||
* 路径前缀:`/sl/projection/reflection`(迷榖平台 YARP 反代下游 → SimpleLite EmbedIO)。
|
||||
* 与 Simple3 `ReflectionApiController` 一一对应的前端胶水。
|
||||
* 路径前缀:`/sl/projection/reflection`(迷榖平台 YARP 反代下游 → Simple3 EmbedIO)。
|
||||
*
|
||||
* 该客户端覆盖了 SimpleLite 主体以及通过 plugins/*.dll 动态加载的 Standard 等
|
||||
* 该客户端覆盖了 Simple3 主体以及通过 plugins/*.dll 动态加载的 Standard 等
|
||||
* 插件中所有标注了 `MethodMember` 的方法与所有继承自 Car/Mission/CarProgram/Site/Track
|
||||
* 的子类型,平台前端可据此动态渲染列表、属性、动作按钮。
|
||||
*/
|
||||
@@ -39,7 +39,7 @@ export type ReflectionKind =
|
||||
| 'map'
|
||||
| 'special'
|
||||
| 'script'
|
||||
// 顶级合成 kind:等于 site + track + special 的并集,由 SimpleLite 后端 /objects/scene 直接返回,
|
||||
// 顶级合成 kind:等于 site + track + special 的并集,由 Simple3 后端 /objects/scene 直接返回,
|
||||
// 每行带 subKind 字段,前端 bundle / execute / setField 走对应底层 kind。
|
||||
| 'scene'
|
||||
|
||||
@@ -167,7 +167,7 @@ export interface ScriptExceptionStatusPayload {
|
||||
// 地图监控可见性配置(与后端 MonitorVisibilityConfig / MonitorVisibilityForKind 对应)
|
||||
//
|
||||
// `config` = 管理员勾选的白名单。空数组 = 显示全部;非空 = 仅显示列表中的 key。
|
||||
// `available` = 当前 SimpleLite 进程里能扫描到的全部可勾选项(基类 + 已加载子类 +
|
||||
// `available` = 当前 Simple3 进程里能扫描到的全部可勾选项(基类 + 已加载子类 +
|
||||
// 运行时实例的 Prop.fields / status 反射键)。是 GET 返回的副产物,
|
||||
// POST 写入时不需要、也不该回传。
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -253,7 +253,7 @@ export function formatReflectionExecuteMessage(
|
||||
): string {
|
||||
if (result.returnValue) return `已执行:${result.returnValue}`
|
||||
if (result.accepted && result.completed === false) {
|
||||
return `已受理:${label}(后台继续执行,请稍后在 SimpleLite 查看结果)`
|
||||
return `已受理:${label}(后台继续执行,请稍后在 Simple3 查看结果)`
|
||||
}
|
||||
if (result.accepted) return `已执行 ${label}`
|
||||
return `已执行 ${label}`
|
||||
@@ -367,7 +367,7 @@ export const reflectionApi = {
|
||||
: del<{ kind: string; id: number; deleted: boolean }>(`/objects/${kind}/${id}`),
|
||||
|
||||
/**
|
||||
* 触发 SimpleLite 重新扫描 ./plugins 目录、加载新增 dll 并重建 UiDiscoveryCache。
|
||||
* 触发 Simple3 重新扫描 ./plugins 目录、加载新增 dll 并重建 UiDiscoveryCache。
|
||||
* 已加载的 dll 不会重复加载。
|
||||
* 返回:本次发现的 dll 总数、新加载的 assembly 数、当前可创建的 mission/car 类型计数。
|
||||
*/
|
||||
@@ -515,7 +515,7 @@ export const reflectionApi = {
|
||||
return data.data as MonitorConfigSaveBody
|
||||
},
|
||||
|
||||
// 选中同步:让 SimpleLite 3D 场景同步高亮被点击对象
|
||||
// 选中同步:让 Simple3 3D 场景同步高亮被点击对象
|
||||
getSelection: () => MOCK
|
||||
? Promise.resolve<ReflectionSelection>({ kind: null, id: 0, name: '' })
|
||||
: get<ReflectionSelection>('/selection'),
|
||||
@@ -685,7 +685,7 @@ export const reflectionApi = {
|
||||
: post<{ path: string }>('/car-style/save')
|
||||
}
|
||||
|
||||
/** 与 SimpleLite 视口样式对话框 / Configuration.conf.viewport 同步。 */
|
||||
/** 与 Simple3 视口样式对话框 / Configuration.conf.viewport 同步。 */
|
||||
export interface ViewportStyleSite {
|
||||
drawScale: number
|
||||
dotRadiusNormal: number
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import http from './http'
|
||||
|
||||
/**
|
||||
* 工作区画布工具栏 API(对应 SimpleLite `WorkspaceToolbarApiController`)。
|
||||
* 工作区画布工具栏 API(对应 Simple3 `WorkspaceToolbarApiController`)。
|
||||
*
|
||||
* 历史背景(会话33):原 SimpleLite iframe 内 ImGui 底栏 (`Panel_4` /
|
||||
* 历史背景(会话33):原 Simple3 iframe 内 ImGui 底栏 (`Panel_4` /
|
||||
* `WorkspaceBottomBar.DefineForTerminalEmbedMinimal`) 提供 8 个按钮,平台 iframe
|
||||
* 嵌入时该底栏会盖在画布上影响交互。本会话把这条底栏整体迁到 Vue 端
|
||||
* `WorkspaceCanvasToolbar.vue` 渲染,状态读写改走该 HTTP API。
|
||||
*
|
||||
* 路径前缀:`/sl/projection/toolbar` (Platform.Server YARP → SimpleLite EmbedIO :8222)
|
||||
* 路径前缀:`/sl/projection/toolbar` (Platform.Server YARP → Simple3 EmbedIO :8222)
|
||||
*/
|
||||
|
||||
const BASE = '/sl/projection/toolbar'
|
||||
@@ -155,7 +155,7 @@ export const workspaceToolbarApi = {
|
||||
|
||||
/**
|
||||
* 一次性把地图相机定位(居中 + 2D 俯视)到指定车辆,不开启持续跟随。
|
||||
* 对应 SimpleLite `WorkspaceToolbarApiController.LocateCamera`(与原生「双击车辆行 = 选中+定位」一致)。
|
||||
* 对应 Simple3 `WorkspaceToolbarApiController.LocateCamera`(与原生「双击车辆行 = 选中+定位」一致)。
|
||||
*/
|
||||
locateCamera: (carId: number) =>
|
||||
unwrap<ToolbarState>(
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<template>
|
||||
<PermissionGuard widget-id="ConfigCenter">
|
||||
<el-card shadow="never">
|
||||
<slot
|
||||
v-if="$slots.chrome"
|
||||
name="chrome"
|
||||
:payload="payload"
|
||||
:update="update"
|
||||
:save="save"
|
||||
:reload="reload"
|
||||
:saving="saving"
|
||||
:loading="loading"
|
||||
:envelope="envelope"
|
||||
:relative-time="relativeTime"
|
||||
/>
|
||||
<el-card v-else shadow="never">
|
||||
<template #header>
|
||||
<div class="cpb-header">
|
||||
<span class="cpb-title">{{ title }}</span>
|
||||
@@ -9,7 +21,7 @@
|
||||
<el-tag size="small" type="success" effect="plain" v-if="envelope?.updatedAt">{{ relativeTime }}</el-tag>
|
||||
<div class="spacer" />
|
||||
<el-button size="small" :icon="Refresh" :loading="loading" @click="reload(true)">重载</el-button>
|
||||
<el-button size="small" type="primary" :icon="Check" :loading="saving" @click="save">保存</el-button>
|
||||
<el-button size="small" type="primary" :icon="Check" :loading="saving" @click="save()">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -50,11 +62,11 @@ const props = defineProps<{
|
||||
defaults: T
|
||||
/** 加载/保存前规范化 payload(如 ops 补全 monitor 字段) */
|
||||
normalizePayload?: (payload: T) => T
|
||||
/** 加载完成后二次合并(如从 SimpleLite 拉 monitor) */
|
||||
/** 加载完成后二次合并(如从 Simple3 拉 monitor) */
|
||||
afterLoad?: (payload: T, update: (next: T) => void) => void | Promise<void>
|
||||
/** 写入 Platform 之前先执行(如先落盘 SimpleLite monitor) */
|
||||
/** 写入 Platform 之前先执行(如先落盘 Simple3 monitor) */
|
||||
beforeSave?: (payload: T) => void | Promise<void>
|
||||
/** 保存成功后副作用(如再次同步 SimpleLite) */
|
||||
/** 保存成功后副作用(如再次同步 Simple3) */
|
||||
afterSave?: (payload: T) => void | Promise<void>
|
||||
}>()
|
||||
|
||||
@@ -98,7 +110,8 @@ async function reload(force = false) {
|
||||
await reloadFromServer(force, force)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
async function save(opts?: { toast?: boolean }): Promise<boolean> {
|
||||
const showToast = typeof opts === 'object' && opts && 'toast' in opts ? opts.toast !== false : true
|
||||
saving.value = true
|
||||
let body = payload.value
|
||||
if (props.normalizePayload) body = props.normalizePayload(body)
|
||||
@@ -109,7 +122,7 @@ async function save() {
|
||||
} catch (e) {
|
||||
ElMessage.error(`地图监控配置保存失败:${e instanceof Error ? e.message : String(e)}`)
|
||||
saving.value = false
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +135,16 @@ async function save() {
|
||||
try {
|
||||
await props.afterSave(body)
|
||||
} catch (e) {
|
||||
monitorSyncWarn = `(平台配置已保存,但 SimpleLite 同步失败:${e instanceof Error ? e.message : String(e)})`
|
||||
monitorSyncWarn = `(平台配置已保存,但 Simple3 同步失败:${e instanceof Error ? e.message : String(e)})`
|
||||
}
|
||||
}
|
||||
await reloadFromServer(true, false)
|
||||
const extra = props.beforeSave || props.afterSave ? ',地图监控配置已写入 SimpleLite' : ''
|
||||
ElMessage.success(`已保存 v${env.version}${extra}${monitorSyncWarn}`)
|
||||
const extra = props.beforeSave || props.afterSave ? ',地图监控配置已写入 Simple3' : ''
|
||||
if (showToast) ElMessage.success(`已保存 v${env.version}${extra}${monitorSyncWarn}`)
|
||||
return true
|
||||
} catch (e) {
|
||||
ElMessage.error(`保存失败:${e instanceof Error ? e.message : String(e)}`)
|
||||
return false
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div v-if="!loaded" class="workspace-3d-mask">
|
||||
<el-icon class="is-loading" size="32"><Loading /></el-icon>
|
||||
<span>{{ loadingHint }}</span>
|
||||
<span class="muted">如长时间未加载,请确认 SimpleLite 已以 Web-Enabled 模式启动且 8223 端口可达。</span>
|
||||
<span class="muted">如长时间未加载,请确认 Simple3 已以 Web-Enabled 模式启动且 8223 端口可达。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@ const props = withDefaults(defineProps<{
|
||||
scope?: Scope
|
||||
token?: string
|
||||
readOnly?: boolean
|
||||
/** 嵌入平台页时传 true:SimpleLite 仅显示底栏(对齐/选择/右击/图层/内容/录制回放) */
|
||||
/** 嵌入平台页时传 true:Simple3 仅显示底栏(对齐/选择/右击/图层/内容/录制回放) */
|
||||
embedUi?: boolean
|
||||
/**
|
||||
* 地图编辑器纯画布模式:比 embedUi 更裸,连底栏都不创建。
|
||||
@@ -116,34 +116,73 @@ function declareEndpoint() {
|
||||
return null
|
||||
}
|
||||
|
||||
/** 单次 HTTP 探测(带超时),用于 declare / vrenderReady。 */
|
||||
async function pingVrender(path: string): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${vrenderBaseUrl()}${path}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
credentials: 'omit',
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
})
|
||||
return res.ok
|
||||
} catch {
|
||||
return false
|
||||
/** 单次 HTTP 探测(带超时)。Simple3 把 declare* 挂在 Projection :8222,不再走 FairyView :8223。 */
|
||||
async function pingDeclare(path: string): Promise<boolean> {
|
||||
const token = localStorage.getItem('simple.auth.token')
|
||||
// 优先走平台 YARP → :8222;兼容 /projection/ui 前缀;最后回退旧 Simple3 的 :8223 LeastServer。
|
||||
const urls = [
|
||||
`/api/sl${path}`,
|
||||
`/api/sl/projection/ui${path}`,
|
||||
`http://${resolvedHost.value}${path}`
|
||||
]
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const headers: Record<string, string> = {}
|
||||
if (token && url.startsWith('/')) headers.Authorization = `Bearer ${token}`
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
credentials: url.startsWith('/') ? 'include' : 'omit',
|
||||
headers,
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
})
|
||||
if (res.ok) return true
|
||||
} catch {
|
||||
/* try next */
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** 画布就绪探测:仍优先打 FairyView :8223(iframe 同源),失败再试 Projection 镜像。 */
|
||||
async function pingVrender(path: string): Promise<boolean> {
|
||||
const candidates = [
|
||||
`http://${resolvedHost.value}${path}`,
|
||||
`/api/sl${path}`,
|
||||
`/api/sl/projection/ui${path}`
|
||||
]
|
||||
const token = localStorage.getItem('simple.auth.token')
|
||||
for (const url of candidates) {
|
||||
try {
|
||||
const headers: Record<string, string> = {}
|
||||
if (token && url.startsWith('/')) headers.Authorization = `Bearer ${token}`
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
credentials: url.startsWith('/') ? 'include' : 'omit',
|
||||
headers,
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
})
|
||||
if (res.ok) return true
|
||||
} catch {
|
||||
/* try next */
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 尽力 declare(不阻塞 iframe)。SimpleLite 侧 WebTerminal 已默认 canvas-only,
|
||||
* declare 主要用于多 tab 显式标记;失败也不影响平台纯画布模式。
|
||||
* 尽力 declare。Simple3 须打 Projection :8222(经 /api/sl),不能只打 :8223。
|
||||
*/
|
||||
async function postDeclareBestEffort() {
|
||||
const endpoint = declareEndpoint()
|
||||
if (!endpoint) return
|
||||
|
||||
for (let attempt = 0; attempt < DECLARE_MAX_ATTEMPTS; attempt++) {
|
||||
if (await pingVrender(endpoint)) return
|
||||
if (await pingDeclare(endpoint)) return
|
||||
await sleep(80 * (attempt + 1))
|
||||
}
|
||||
console.warn(`[Workspace3D] ${endpoint} 未成功,将依赖 SimpleLite 默认 canvas-only 模式`)
|
||||
console.warn(`[Workspace3D] ${endpoint} 未成功,将依赖内核默认 canvas-only 模式`)
|
||||
}
|
||||
|
||||
/** 非嵌入模式:短轮询等待 8223 就绪后再加载 iframe。 */
|
||||
@@ -169,7 +208,7 @@ function startLoadWatchdog(generation: number) {
|
||||
clearLoadWatchdog()
|
||||
loadWatchdog = setTimeout(() => {
|
||||
if (generation !== prepareGeneration || loaded.value) return
|
||||
loadingHint.value = `连接超时 (http://${resolvedHost.value}),请确认 SimpleLite 已启动`
|
||||
loadingHint.value = `连接超时 (http://${resolvedHost.value}),请确认 Simple3 已启动`
|
||||
}, LOAD_WATCHDOG_MS)
|
||||
}
|
||||
|
||||
@@ -199,7 +238,7 @@ async function prepareAndLoad(forceReload = false) {
|
||||
}
|
||||
iframeSrc.value = buildIframeSrc(forceReload || !!iframeSrc.value)
|
||||
} else {
|
||||
loadingHint.value = '正在等待 SimpleLite webVRender 就绪 ...'
|
||||
loadingHint.value = '正在等待 Simple3 webVRender 就绪 ...'
|
||||
await waitForVrenderReady()
|
||||
if (generation !== prepareGeneration) return
|
||||
iframeSrc.value = buildIframeSrc(forceReload || !!iframeSrc.value)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div v-if="!collapsed">
|
||||
<el-empty
|
||||
v-if="!loading && plugins.length === 0"
|
||||
description="尚未通过 PluginManager 加载任何插件 (重启 SimpleLite 后会自动迁移现有 plugins/ 内的 dll)"
|
||||
description="尚未通过 PluginManager 加载任何插件 (重启 Simple3 后会自动迁移现有 plugins/ 内的 dll)"
|
||||
/>
|
||||
<el-table
|
||||
v-else
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-tag v-if="row.collectible" size="small" type="success">可卸载</el-tag>
|
||||
<el-tooltip
|
||||
v-else
|
||||
content="此插件被加到 default ALC(旧路径加载),重启 SimpleLite 后会自动迁移到 collectible"
|
||||
content="此插件被加到 default ALC(旧路径加载),重启 Simple3 后会自动迁移到 collectible"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag size="small" type="warning">不可卸载</el-tag>
|
||||
@@ -98,7 +98,7 @@ async function refresh() {
|
||||
try {
|
||||
plugins.value = await reflectionApi.listPlugins()
|
||||
} catch (err) {
|
||||
ElMessage.warning(`插件列表加载失败:${(err as Error).message}(旧版 SimpleLite 可能未带 /plugins 接口,请重启)`)
|
||||
ElMessage.warning(`插件列表加载失败:${(err as Error).message}(旧版 Simple3 可能未带 /plugins 接口,请重启)`)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
+3
-3
@@ -150,7 +150,7 @@
|
||||
>
|
||||
查看对象
|
||||
</el-button>
|
||||
<!-- 脚本管理专用:与 SimpleLite 工作台「脚本」表格行内按钮组对齐,
|
||||
<!-- 脚本管理专用:与 Simple3 工作台「脚本」表格行内按钮组对齐,
|
||||
选不选中都能直接点行内「查看脚本 / 查看异常状态」弹窗。 -->
|
||||
<template v-if="showScriptActions && kind === 'script'">
|
||||
<el-button
|
||||
@@ -492,7 +492,7 @@
|
||||
* site/track/image/text/model 走弹窗表单收集 x/y/siteA/siteB 等
|
||||
* - 删除:reflectionApi.deleteObject
|
||||
* - 方法调用:reflectionApi.execute(带参时弹 prompt 逐项收集)
|
||||
* - 3D 高亮:reflectionApi.setSelection 让 SimpleLite 工作台同步选中
|
||||
* - 3D 高亮:reflectionApi.setSelection 让 Simple3 工作台同步选中
|
||||
*
|
||||
* SSE 订阅:object-created/deleted/patched/batch-changed 都会自动 refresh。
|
||||
*/
|
||||
@@ -556,7 +556,7 @@ const props = defineProps<{
|
||||
* 列表状态列取自运行状态反射中的 key(如进程 Mission 的 status)。
|
||||
*/
|
||||
statusReflectionKey?: string
|
||||
/** 脚本管理专用:在动作区追加 SimpleLite 工作台同款「查看脚本 / 查看异常状态」。 */
|
||||
/** 脚本管理专用:在动作区追加 Simple3 工作台同款「查看脚本 / 查看异常状态」。 */
|
||||
showScriptActions?: boolean
|
||||
/** 增删改后是否自动写回项目 JSON。默认车辆/场景/进程启用。 */
|
||||
autoSaveProject?: boolean
|
||||
|
||||
+1
-1
@@ -363,7 +363,7 @@ async function loadActions() {
|
||||
async function onExecute(m: ReflectionMethod) {
|
||||
// 无 methodName = workbench 兜底 action,保留旧的"提示"语义,避免误导用户以为已执行。
|
||||
if (!m.methodName) {
|
||||
ElMessage.info(`动作「${m.label || m.methodName}」需在 SimpleLite 工作台执行(当前对象类型未接入反射 execute)。`)
|
||||
ElMessage.info(`动作「${m.label || m.methodName}」需在 Simple3 工作台执行(当前对象类型未接入反射 execute)。`)
|
||||
return
|
||||
}
|
||||
const rk = reflectionKind.value
|
||||
|
||||
@@ -65,7 +65,7 @@ const search = ref('')
|
||||
const selectedId = ref<string | null>(null)
|
||||
|
||||
const navHints: Record<WorkbenchNav, string> = {
|
||||
map: '地图/装饰对象列表(对齐 SimpleLite 工作台「地图」)',
|
||||
map: '地图/装饰对象列表(对齐 Simple3 工作台「地图」)',
|
||||
process: '自定义进程 Mission 列表',
|
||||
vehicle: '场景车辆列表(ID / 地址 / 名称 / 概况)',
|
||||
scene: '站点、路径、装饰物',
|
||||
|
||||
+3
-3
@@ -260,10 +260,10 @@ function publishState(next: ToolbarState | null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台 iframe 画布工具栏(对应 SimpleLite `Panel_4` / `WorkspaceBottomBar.DefineForTerminalEmbedMinimal`)。
|
||||
* 平台 iframe 画布工具栏(对应 Simple3 `Panel_4` / `WorkspaceBottomBar.DefineForTerminalEmbedMinimal`)。
|
||||
*
|
||||
* 设计原则:
|
||||
* - 所有状态都通过 `/sl/projection/toolbar/*` 走 SimpleLite 全局状态,多 tab 同源。
|
||||
* - 所有状态都通过 `/sl/projection/toolbar/*` 走 Simple3 全局状态,多 tab 同源。
|
||||
* - `state` 是后端权威单一真值;本地不维护"乐观"切换:每次 toggle 都拿后端最新返回回写。
|
||||
* - 录制 / 回放期间 1Hz 轮询刷新 elapsed / 帧计数,避免页面打开后停留在初值。
|
||||
* - 错误用 ElMessage.error 提示,不阻断画布主操作。
|
||||
@@ -386,7 +386,7 @@ async function toggleLayer(name: string, visible: boolean) {
|
||||
}
|
||||
|
||||
function onContextMenuPlaceholder() {
|
||||
ElMessage.info('寻路右键动作尚未接入 SimpleLite;请使用 SegmentPlan.FindRoute / 工作台脚本。')
|
||||
ElMessage.info('寻路右键动作尚未接入 Simple3;请使用 SegmentPlan.FindRoute / 工作台脚本。')
|
||||
}
|
||||
|
||||
async function toggleRecording() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
|
||||
/**
|
||||
* 与 Workspace3D iframe(SimpleLite WebTerminal)的 postMessage 双向桥。
|
||||
* 与 Workspace3D iframe(Simple3 WebTerminal)的 postMessage 双向桥。
|
||||
*
|
||||
* 当前 SimpleLite WebTerminal 默认会通过 window.postMessage 抛出 `workspace.pick`
|
||||
* 当前 Simple3 WebTerminal 默认会通过 window.postMessage 抛出 `workspace.pick`
|
||||
* 与 `workspace.select` 事件给父窗口(Workspace3D.vue 收到后再 emit 给业务页)。
|
||||
* 反向的 vue→iframe 通道(设置工具、应用对齐等)走 SimpleLite 反射 API(HTTP/SSE),
|
||||
* 反向的 vue→iframe 通道(设置工具、应用对齐等)走 Simple3 反射 API(HTTP/SSE),
|
||||
* 这里的桥只承担**坐标 / 选中 / hover** 三类 iframe→vue 数据。
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ref, computed } from 'vue'
|
||||
*
|
||||
* 工具切换有两个层面:
|
||||
* - **前端切换**:仅影响 Vue 端的按钮高亮 + 状态栏 + 下一次画布点击的本地处理(如 pick 后调反射 API 创建对象)。
|
||||
* - **后端切换**:通过反射 API `/execute/...` 触发 SimpleLite 的 CAD action(吸附 / 对齐 / 框选等)。
|
||||
* - **后端切换**:通过反射 API `/execute/...` 触发 Simple3 的 CAD action(吸附 / 对齐 / 框选等)。
|
||||
*
|
||||
* 简化起见:本 composable 只负责前端状态,CAD action 由调用方决定何时触发。
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ import { onBeforeUnmount, onMounted } from 'vue'
|
||||
import { useProjectionStream, type StreamEvent } from './useProjectionStream'
|
||||
|
||||
/**
|
||||
* 编辑器 / 监控页对 SimpleLite SSE 的事件订阅封装,把以下 5 类事件转给业务回调:
|
||||
* 编辑器 / 监控页对 Simple3 SSE 的事件订阅封装,把以下 5 类事件转给业务回调:
|
||||
*
|
||||
* - `alarm` (来自 AlarmStreamService):报警新增/更新/解除
|
||||
* - `object-created` (来自 MapEditApiController):对象创建后
|
||||
@@ -38,14 +38,14 @@ export interface PickResultEvent {
|
||||
}
|
||||
|
||||
export interface SelectionDetailEvent {
|
||||
/** 主选中 kind (site|track|car|null);当多选时取首个非空集合的 kind。 */
|
||||
kind: 'site' | 'track' | 'car' | null
|
||||
/** 主选中 kind;当多选时取首个非空集合的 kind。 */
|
||||
kind: 'site' | 'track' | 'car' | 'special' | null
|
||||
/** 主选中 id;多选时取首个。 */
|
||||
id: number
|
||||
siteIds: number[]
|
||||
trackIds: number[]
|
||||
carIds: number[]
|
||||
/** 完整对象名列表,例如 ["UISite-12", "UITrack-7", "Car-3"]。 */
|
||||
/** 完整对象名列表,例如 ["UISite-12", "UITrack-7", "Car-3", "UIText-5"]。 */
|
||||
names: string[]
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export interface MapEditStreamHandlers {
|
||||
onObjectBatchChanged?: (e: { count: number; source?: string }) => void
|
||||
onPickResult?: (e: PickResultEvent) => void
|
||||
onSelectionDetail?: (e: SelectionDetailEvent) => void
|
||||
/** iframe 画布聚焦时由 SimpleLite 经 SSE 转发的撤销/重做/删除。 */
|
||||
/** iframe 画布聚焦时由 Simple3 经 SSE 转发的撤销/重做/删除。 */
|
||||
onWorkspaceShortcut?: (e: WorkspaceShortcutEvent) => void
|
||||
/** 视口样式(站点/路径 Painter)在 Web 或桌面对话框中变更后广播。 */
|
||||
onViewportStyleUpdated?: () => void
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { onUnmounted, ref, type Ref } from 'vue'
|
||||
|
||||
/**
|
||||
* 与 SimpleLite `/projection/stream`(SSE)通信的轻量封装。
|
||||
* 与 Simple3 `/projection/stream`(SSE)通信的轻量封装。
|
||||
*
|
||||
* 服务器以 `text/event-stream` 推送 JSON 行:
|
||||
* ```
|
||||
@@ -33,7 +33,7 @@ export interface ProjectionStreamOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleLite 当前后端会广播的全部 SSE 事件名。新增后端事件时务必同步追加,
|
||||
* Simple3 当前后端会广播的全部 SSE 事件名。新增后端事件时务必同步追加,
|
||||
* 否则 EventSource 拿不到该事件 —— 这是历史上 alarm / object-* 被静默丢弃的根因。
|
||||
*/
|
||||
export const KNOWN_EVENT_KINDS = [
|
||||
@@ -123,7 +123,7 @@ export function useProjectionStream(opts: ProjectionStreamOptions = {}) {
|
||||
|
||||
// 命名事件:EventSource 默认只把"无 event: 头"的消息派发给 onmessage,
|
||||
// 任何 `event: foo` 都必须通过 addEventListener('foo', ...) 才能收到。
|
||||
// 必须覆盖 SimpleLite 后端可能广播的所有事件名 —— 漏一个就等于该事件被丢弃。
|
||||
// 必须覆盖 Simple3 后端可能广播的所有事件名 —— 漏一个就等于该事件被丢弃。
|
||||
// 当前后端事件源:
|
||||
// - ProjectionStreamModule: snapshot-tick (1Hz 心跳/计数)
|
||||
// - SimpleUI / ReflectionApiController: selection-detail
|
||||
|
||||
@@ -5,8 +5,7 @@ import type { ReflectionObject, ReflectionKind } from '@/api/reflection'
|
||||
* 编辑器内的多选集合 + 类型过滤工具。
|
||||
* 选中集是 `(kind, id)` 二元组集合:用 `${kind}:${id}` 作 key 去重。
|
||||
*
|
||||
* 与 SimpleLite 后端的同步:单选时把 selection 同步给 ReflectionApi(高亮 3D 场景),
|
||||
* 多选不同步(高亮不展开是因为反射 API 当前只支持单选;如需多选高亮以后再扩展)。
|
||||
* 选中集由画布 postMessage / SSE 写入。编辑页不要在 watch 里回写 SetSelection。
|
||||
*/
|
||||
|
||||
export interface SelectionItem extends ReflectionObject {
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<ThemeSwitcher />
|
||||
<ScopeSwitcher />
|
||||
<span class="runmode-tag" :class="auth.runMode">{{ runModeLabel }}</span>
|
||||
<el-dropdown trigger="click" @command="onUserCommand">
|
||||
<span class="user-link">
|
||||
@@ -91,6 +90,7 @@
|
||||
</el-header>
|
||||
|
||||
<el-main class="main mg-content">
|
||||
<WizardSetupBar />
|
||||
<!-- 不用 mode="out-in":懒加载子路由切换时易出现「离场后新组件未挂载」→ 主区全白,刷新才恢复。 -->
|
||||
<router-view v-slot="{ Component, route: rv }">
|
||||
<component :is="Component" v-if="Component" :key="rv.fullPath" class="route-page" />
|
||||
@@ -115,9 +115,9 @@ import { Fold, Expand, CaretBottom } from '@element-plus/icons-vue'
|
||||
import { ADMIN_MENU, MONITOR_MENU, type NavMenuItem } from '@/config/navMenu'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useUiStore } from '@/stores/ui'
|
||||
import ScopeSwitcher from '@/components/ScopeSwitcher.vue'
|
||||
import ThemeSwitcher from '@/components/ThemeSwitcher.vue'
|
||||
import AiAssistantDrawer from '@/components/assistant/AiAssistantDrawer.vue'
|
||||
import WizardSetupBar from '@/components/wizard/WizardSetupBar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -129,13 +129,13 @@ const apiBase = computed(() => (import.meta.env.VITE_API_BASE as string | undefi
|
||||
|
||||
const initial = computed(() => (auth.user?.displayName ?? auth.user?.username ?? '?').slice(0, 1).toUpperCase())
|
||||
|
||||
const scopeLabel = computed(() => (auth.scope === 'Platform' ? '管理员 (Platform)' : '运营 (RCSMonitor)'))
|
||||
const scopeLabel = computed(() => (auth.scope === 'Platform' ? '管理' : '运营'))
|
||||
|
||||
const runModeLabel = computed(() => {
|
||||
switch (auth.runMode) {
|
||||
case 'WebOnly': return 'Web-Only'
|
||||
case 'WebEnabled': return 'Web + Desktop'
|
||||
case 'Detached': return 'SimpleLite 未连接'
|
||||
case 'Detached': return 'Simple3 未连接'
|
||||
default: return String(auth.runMode ?? 'Unknown')
|
||||
}
|
||||
})
|
||||
@@ -498,7 +498,7 @@ function onUserCommand(cmd: string) {
|
||||
border-color: rgba(var(--mg-primary-hover-rgb), 0.45);
|
||||
color: var(--mg-accent);
|
||||
}
|
||||
/* 会话 N+1 增量:SimpleLite 未拉起时的降级展示,用警示色而非成功色,引导用户感知"实际只有 Platform"。 */
|
||||
/* 会话 N+1 增量:Simple3 未拉起时的降级展示,用警示色而非成功色,引导用户感知"实际只有 Platform"。 */
|
||||
.runmode-tag.Detached {
|
||||
background: rgba(var(--mg-status-warning-rgb), 0.18);
|
||||
border-color: rgba(var(--mg-status-warning-rgb), 0.45);
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { Car } from '@/types/car'
|
||||
const NOW = new Date().toISOString()
|
||||
|
||||
export const CARS: Car[] = [
|
||||
{ id: 'C01', rawId: 1, name: 'AGV-001', typeName: 'SimpleLite.RCS.CarTypes.DummyCar', x: 1200, y: 2100, theta: 0, batterySoc: 0.86, state: 'running', missionId: 'M01', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.11', onboardUrl: 'http://10.0.1.11:8081', lstatus: '运行' },
|
||||
{ id: 'C02', rawId: 2, name: 'AGV-002', typeName: 'SimpleLite.RCS.CarTypes.DummyCar', x: 2900, y: 2100, theta: 90, batterySoc: 0.42, state: 'running', missionId: 'M02', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.12', onboardUrl: 'http://10.0.1.12:8081', lstatus: '运行' },
|
||||
{ id: 'C03', rawId: 3, name: 'AGV-003', typeName: 'SimpleLite.RCS.CarTypes.DummyCar', x: 1000, y: 4900, theta: 180, batterySoc: 1.0, state: 'charging', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.13', onboardUrl: 'http://10.0.1.13:8081', lstatus: '充电' },
|
||||
{ id: 'C04', rawId: 4, name: 'AGV-004', typeName: 'SimpleLite.RCS.CarTypes.GhostCar', x: 7000, y: 2050, theta: 0, batterySoc: 0.71, state: 'idle', lastUpdate: NOW, group: 'B 区', ip: '10.0.1.14', onboardUrl: 'http://10.0.1.14:8081', lstatus: '空闲' },
|
||||
{ id: 'C05', rawId: 5, name: 'AGV-005', typeName: 'SimpleLite.RCS.CarTypes.GhostCar', x: 7050, y: 4000, theta: 270, batterySoc: 0.18, state: 'fault', lastUpdate: NOW, group: 'B 区', ip: '10.0.1.15', onboardUrl: 'http://10.0.1.15:8081', lstatus: '故障' },
|
||||
{ id: 'C06', rawId: 6, name: 'AGV-006', typeName: 'SimpleLite.RCS.CarTypes.DummyCar', x: 9000, y: 2000, theta: 0, batterySoc: 0.95, state: 'offline', lastUpdate: NOW, group: '维护', ip: '10.0.1.16', onboardUrl: 'http://10.0.1.16:8081', lstatus: '下线' }
|
||||
{ id: 'C01', rawId: 1, name: 'AGV-001', typeName: 'Simple3.RCS.CarTypes.DummyCar', x: 1200, y: 2100, theta: 0, batterySoc: 0.86, state: 'running', missionId: 'M01', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.11', onboardUrl: 'http://10.0.1.11:8081', lstatus: '运行' },
|
||||
{ id: 'C02', rawId: 2, name: 'AGV-002', typeName: 'Simple3.RCS.CarTypes.DummyCar', x: 2900, y: 2100, theta: 90, batterySoc: 0.42, state: 'running', missionId: 'M02', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.12', onboardUrl: 'http://10.0.1.12:8081', lstatus: '运行' },
|
||||
{ id: 'C03', rawId: 3, name: 'AGV-003', typeName: 'Simple3.RCS.CarTypes.DummyCar', x: 1000, y: 4900, theta: 180, batterySoc: 1.0, state: 'charging', lastUpdate: NOW, group: 'A 区', ip: '10.0.1.13', onboardUrl: 'http://10.0.1.13:8081', lstatus: '充电' },
|
||||
{ id: 'C04', rawId: 4, name: 'AGV-004', typeName: 'Simple3.RCS.CarTypes.GhostCar', x: 7000, y: 2050, theta: 0, batterySoc: 0.71, state: 'idle', lastUpdate: NOW, group: 'B 区', ip: '10.0.1.14', onboardUrl: 'http://10.0.1.14:8081', lstatus: '空闲' },
|
||||
{ id: 'C05', rawId: 5, name: 'AGV-005', typeName: 'Simple3.RCS.CarTypes.GhostCar', x: 7050, y: 4000, theta: 270, batterySoc: 0.18, state: 'fault', lastUpdate: NOW, group: 'B 区', ip: '10.0.1.15', onboardUrl: 'http://10.0.1.15:8081', lstatus: '故障' },
|
||||
{ id: 'C06', rawId: 6, name: 'AGV-006', typeName: 'Simple3.RCS.CarTypes.DummyCar', x: 9000, y: 2000, theta: 0, batterySoc: 0.95, state: 'offline', lastUpdate: NOW, group: '维护', ip: '10.0.1.16', onboardUrl: 'http://10.0.1.16:8081', lstatus: '下线' }
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ const NOW = new Date().toISOString()
|
||||
|
||||
export const MISSIONS: Mission[] = [
|
||||
{
|
||||
id: 'M01', name: 'A 区送料 #1', typeName: 'SimpleLite.RCS.Missions.MoveMission',
|
||||
id: 'M01', name: 'A 区送料 #1', typeName: 'Simple3.RCS.Missions.MoveMission',
|
||||
priority: 50, status: 'running', carId: 'C01', createdAt: NOW,
|
||||
steps: [
|
||||
{ id: 'S1', action: 'pickup', targetSiteId: 'S001', status: 'completed' },
|
||||
@@ -13,7 +13,7 @@ export const MISSIONS: Mission[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'M02', name: 'A→B 缓存搬运', typeName: 'SimpleLite.RCS.Missions.MoveMission',
|
||||
id: 'M02', name: 'A→B 缓存搬运', typeName: 'Simple3.RCS.Missions.MoveMission',
|
||||
priority: 60, status: 'running', carId: 'C02', createdAt: NOW,
|
||||
steps: [
|
||||
{ id: 'S1', action: 'pickup', targetSiteId: 'S002', status: 'completed' },
|
||||
@@ -21,7 +21,7 @@ export const MISSIONS: Mission[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'M03', name: 'B 区工位 W1 投料', typeName: 'SimpleLite.RCS.Missions.MoveMission',
|
||||
id: 'M03', name: 'B 区工位 W1 投料', typeName: 'Simple3.RCS.Missions.MoveMission',
|
||||
priority: 70, status: 'queued', createdAt: NOW,
|
||||
steps: [
|
||||
{ id: 'S1', action: 'pickup', targetSiteId: 'S003', status: 'queued' },
|
||||
@@ -29,7 +29,7 @@ export const MISSIONS: Mission[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'M04', name: 'W2→维护检修', typeName: 'SimpleLite.RCS.Missions.MaintenanceMission',
|
||||
id: 'M04', name: 'W2→维护检修', typeName: 'Simple3.RCS.Missions.MaintenanceMission',
|
||||
priority: 30, status: 'paused', carId: 'C05', createdAt: NOW,
|
||||
steps: [
|
||||
{ id: 'S1', action: 'goto', targetSiteId: 'S010', status: 'paused' }
|
||||
|
||||
@@ -254,7 +254,7 @@ export function mockReflectionKinds(): { kinds: ReflectionKindMeta[]; subKinds:
|
||||
export function mockReflectionAssemblies(): ReflectionAssembly[] {
|
||||
return [
|
||||
{ name: 'SimpleCore', version: '1.0.0' },
|
||||
{ name: 'SimpleLite', version: '1.0.0' },
|
||||
{ name: 'Simple3', version: '1.0.0' },
|
||||
{ name: 'StandardScene', version: '1.4.0', location: 'plugins/StandardScene.dll' },
|
||||
{ name: 'CustomFlightDeck', version: '0.1.0', location: 'plugins/CustomFlightDeck.dll' }
|
||||
]
|
||||
@@ -273,7 +273,7 @@ export function mockReflectionMethodsByType(kind: ReflectionKind): ReflectionTyp
|
||||
if (kind === 'car' || kind === 'vehicle' || kind === 'script') {
|
||||
return [
|
||||
{ typeName: 'GhostCar', assemblyName: 'StandardScene', methods: ms },
|
||||
{ typeName: 'DummyCar', assemblyName: 'SimpleLite', methods: ms.slice(0, 2) }
|
||||
{ typeName: 'DummyCar', assemblyName: 'Simple3', methods: ms.slice(0, 2) }
|
||||
]
|
||||
}
|
||||
if (kind === 'mission' || kind === 'process') {
|
||||
@@ -282,7 +282,7 @@ export function mockReflectionMethodsByType(kind: ReflectionKind): ReflectionTyp
|
||||
{ typeName: 'PatrolMission', assemblyName: 'CustomFlightDeck', methods: ms.slice(0, 2) }
|
||||
]
|
||||
}
|
||||
return [{ typeName: kind, assemblyName: 'SimpleLite', methods: ms }]
|
||||
return [{ typeName: kind, assemblyName: 'Simple3', methods: ms }]
|
||||
}
|
||||
|
||||
export function mockReflectionStatus(kind: ReflectionKind, id: number) {
|
||||
|
||||
@@ -2,20 +2,19 @@ export type Scope = 'Platform' | 'RCSMonitor'
|
||||
|
||||
/**
|
||||
* 启动后实际运行模式(由后端在登录响应中告知,仅作展示用)。
|
||||
* - `WebEnabled`:SimpleLite 同时启动了 LocalTerminal(本地桌面窗口)+ WebTerminal(浏览器端)。
|
||||
* - `WebOnly`:SimpleLite 只启动了 WebTerminal,没有本地桌面窗口,全程浏览器使用。
|
||||
* - `Detached`:会话 N+1 增量字段。Platform.Server 未能拉起 SimpleLite(appsettings 关闭 / exe 找不到 /
|
||||
* Process.Start 失败 / 启动异常),前端应该提示「SimpleLite 未连接」并降级 /api/sl/* 调用。
|
||||
* - `WebEnabled`:Simple3 同时启动了 LocalTerminal(本地桌面窗口)+ WebTerminal(浏览器端)。
|
||||
* - `WebOnly`:Simple3 只启动了 WebTerminal,没有本地桌面窗口,全程浏览器使用。
|
||||
* - `Detached`:会话 N+1 增量字段。Platform.Server 未能拉起 Simple3(appsettings 关闭 / exe 找不到 /
|
||||
* Process.Start 失败 / 启动异常),前端应该提示「Simple3 未连接」并降级 /api/sl/* 调用。
|
||||
*/
|
||||
export type RunMode = 'WebOnly' | 'WebEnabled' | 'Detached'
|
||||
|
||||
/**
|
||||
* 登录时用户在登录页选择的「启动模式」(请求字段)。
|
||||
* - `DesktopAndWeb`:希望本地桌面窗口 + Web 都启动(功能最完整)。
|
||||
* - `WebOnly`:希望只起 Web,不要弹本地桌面窗口(远程办公 / 服务器部署)。
|
||||
* - `WebOnly`:仅 Web(Simple3 默认;登录页不再提供选择)。
|
||||
* - `DesktopAndWeb`:历史兼容(本地桌面 + Web);Simple3 无本地端,一般不再使用。
|
||||
*
|
||||
* 与 RunMode 的区别:LaunchMode 是「期望」(前端 → 后端),RunMode 是「事实」(后端 → 前端)。
|
||||
* 正常情况下二者一一映射:DesktopAndWeb → WebEnabled,WebOnly → WebOnly。
|
||||
*/
|
||||
export type LaunchMode = 'DesktopAndWeb' | 'WebOnly'
|
||||
|
||||
@@ -50,8 +49,9 @@ export interface AuthUser {
|
||||
export interface LoginRequest {
|
||||
username: string
|
||||
password: string
|
||||
scope: Scope
|
||||
/** 会话 N+1:可选;不传时后端按 DesktopAndWeb 处理(向后兼容历史前端)。 */
|
||||
/** 已废弃:登录入口由账号角色决定,客户端不再挑选管理/运营。 */
|
||||
scope?: Scope
|
||||
/** 可选;不传时后端按 WebOnly(Simple3)。 */
|
||||
launchMode?: LaunchMode
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export interface LoginResponse {
|
||||
effectivePermissions: EffectivePermissions
|
||||
runMode: RunMode
|
||||
/**
|
||||
* 会话 N+1 增量字段:SimpleLiteLauncher.LaunchResult.Status 字符串枚举。
|
||||
* 会话 N+1 增量字段:Simple3Launcher.LaunchResult.Status 字符串枚举。
|
||||
* 可能值:Disabled / AlreadyRunning / ReusingExisting / PortOccupied / Ready / StartedButNotReady /
|
||||
* ExecutableNotFound / ProcessStartFailed / Exception。
|
||||
* 字段缺失说明后端没有附带该字段(兼容旧后端)。
|
||||
@@ -70,7 +70,7 @@ export interface LoginResponse {
|
||||
launchStatus?: string
|
||||
/**
|
||||
* 会话 N+1 增量字段:可选告警文本。非空时前端应该用消息条提示用户
|
||||
* (比如「检测到 SimpleLite 已经在端口上运行,本次选择的启动模式未生效」)。
|
||||
* (比如「检测到 Simple3 已经在端口上运行,本次选择的启动模式未生效」)。
|
||||
*/
|
||||
launchWarning?: string
|
||||
/**
|
||||
@@ -92,4 +92,6 @@ export interface MeResponse {
|
||||
effectivePermissions: EffectivePermissions
|
||||
/** 部署配置向导是否待完成(与 LoginResponse.needsWizard 同义)。 */
|
||||
needsWizard?: boolean
|
||||
/** 历史「切运营端」残留 token 被纠正时,后端重发的新 JWT。 */
|
||||
token?: string
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface DeploymentProfileDto {
|
||||
navigationKinds: string[]
|
||||
scenarios: string[]
|
||||
updatedBy: string
|
||||
/** 由导航 + 业务场景推导的 SimpleLite 激活场景 id(如 scene.mag / scene.qrlidar;scene.signal 仅 SPS / Pack)。 */
|
||||
/** 由导航 + 业务场景推导的 Simple3 激活场景 id(如 scene.mag / scene.qrlidar;scene.signal 仅 SPS / Pack)。 */
|
||||
activeSceneIds: string[]
|
||||
/** 被部署画像裁剪隐藏的页面 Key。 */
|
||||
hiddenPages: string[]
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function loadSitePickOptions(): Promise<SitePickOption[]> {
|
||||
.sort((a, b) => a.id - b.id)
|
||||
}
|
||||
|
||||
/** 在地图上拾取目标站点(调用 SimpleLite pick 会话)。 */
|
||||
/** 在地图上拾取目标站点(调用 Simple3 pick 会话)。 */
|
||||
export async function pickTargetSiteOnMap(): Promise<number | null> {
|
||||
try {
|
||||
ElMessage.info({ message: '请在 3D 地图上点击目标站点附近', duration: 4000 })
|
||||
@@ -77,7 +77,7 @@ export async function executeCarGotoSite(carId: number, siteId: number): Promise
|
||||
/WebGotoSite|goto-site|不存在可调用方法/i.test(msg2)
|
||||
ElMessage.error(
|
||||
needDeploy
|
||||
? `${msg2}。当前 SimpleLite 仍是旧版 DLL:请先完全关闭 SimpleLite 窗口,再在 PowerShell 执行 Migu2.0/scripts/redeploy-simplelite.ps1,然后重新登录;或调用 POST /api/health/simplelite/restart-for-update`
|
||||
? `${msg2}。当前 Simple3 仍是旧版 DLL:请先完全关闭 Simple3 窗口,再在 PowerShell 执行 Migu2.0/scripts/redeploy-simple3.ps1,然后重新登录;或调用 POST /api/health/simple3/restart-for-update`
|
||||
: `前往站点失败:${msg2}`
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -43,7 +43,7 @@ function compactJsonList(raw: string, maxItems = 3): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleLite /projection/cars 会把「正常但未初始化」「导航失联」等一律标成 fault,
|
||||
* Simple3 /projection/cars 会把「正常但未初始化」「导航失联」等一律标成 fault,
|
||||
* 与地图标签/车体 AlarmLevel 不一致。这里用 lstatus + 车体 status 重新推导。
|
||||
*/
|
||||
export function deriveCarState(car: Pick<Car, 'state' | 'lstatus'>, statusRows?: Kv[] | null): CarState {
|
||||
@@ -93,7 +93,7 @@ export function deriveCarState(car: Pick<Car, 'state' | 'lstatus'>, statusRows?:
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 SimpleLite 车体浮窗 CarVisual.FormatTrafficStatus 对齐:
|
||||
* 与 Simple3 车体浮窗 CarVisual.FormatTrafficStatus 对齐:
|
||||
* 1) TCStat 含 BeforeLock → 互锁
|
||||
* 2) blockedBy 有车 →「被 X、Y 车阻塞」;仅非车阻塞 →「阻塞中」
|
||||
* 3) 有 holdingLocks → 正常拿锁;否则 → 畅通
|
||||
|
||||
@@ -35,7 +35,7 @@ export function normalizeOpsConfig(raw?: Partial<OpsConfig> | null): OpsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/** 深拷贝车型动作表,避免与 SimpleLite 返回对象共享引用导致勾选 UI 不同步。 */
|
||||
/** 深拷贝车型动作表,避免与 Simple3 返回对象共享引用导致勾选 UI 不同步。 */
|
||||
export function cloneCarActionByType(
|
||||
src?: Record<string, string[] | readonly string[]> | null
|
||||
): Record<string, string[]> {
|
||||
@@ -57,12 +57,12 @@ function pickMonitorRoot(cfg: MonitorConfigRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 SimpleLite simple.json 加载地图监控配置(运行时唯一可信源)。
|
||||
* Platform ops 里的 monitor 仅作备份;以 SimpleLite 为准覆盖表单。
|
||||
* 从 Simple3 simple.json 加载地图监控配置(运行时唯一可信源)。
|
||||
* Platform ops 里的 monitor 仅作备份;以 Simple3 为准覆盖表单。
|
||||
*/
|
||||
export interface LoadMonitorSettingsResult {
|
||||
config: OpsConfig
|
||||
/** 与 SimpleLite 对齐的 monitor-config 快照,供保存时合并 fields/status,避免每次 POST 前再 GET。 */
|
||||
/** 与 Simple3 对齐的 monitor-config 快照,供保存时合并 fields/status,避免每次 POST 前再 GET。 */
|
||||
cache: MonitorConfigRoot | null
|
||||
}
|
||||
|
||||
@@ -80,13 +80,13 @@ export async function loadMonitorSettingsIntoOps(
|
||||
next.monitor.site.actionKeys = picked.siteActionKeys
|
||||
next.monitor.track.actionKeys = picked.trackActionKeys
|
||||
} catch {
|
||||
// SimpleLite 未连接:退回 Platform payload 中已有的 monitor(若有)
|
||||
// Simple3 未连接:退回 Platform payload 中已有的 monitor(若有)
|
||||
}
|
||||
return { config: next, cache }
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运营维护表单中的地图监控配置写入 SimpleLite(simple.json)。
|
||||
* 将运营维护表单中的地图监控配置写入 Simple3(simple.json)。
|
||||
* 保留「地图监控配置」页面对 fields/status 的既有设置,只更新动作相关字段。
|
||||
*/
|
||||
export interface SaveMonitorSettingsOptions {
|
||||
@@ -132,7 +132,7 @@ export async function saveMonitorSettingsFromOps(
|
||||
}
|
||||
|
||||
/** @deprecated 使用 loadMonitorSettingsIntoOps */
|
||||
export const mergeMonitorFromSimpleLite = loadMonitorSettingsIntoOps
|
||||
export const mergeMonitorFromSimple3 = loadMonitorSettingsIntoOps
|
||||
|
||||
/** @deprecated 使用 saveMonitorSettingsFromOps */
|
||||
export const syncOpsMonitorToSimpleLite = saveMonitorSettingsFromOps
|
||||
export const syncOpsMonitorToSimple3 = saveMonitorSettingsFromOps
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* webVRender (SimpleLite 3D 视口, 默认 :8223) 的 host 解析。
|
||||
* webVRender (Simple3 3D 视口, 默认 :8223) 的 host 解析。
|
||||
*
|
||||
* 优先级:显式 VITE_VRENDER_HOST > 当前页面 hostname:8223。
|
||||
* 不能写死 localhost —— 从远程浏览器访问平台时 iframe 会去连访问者本机而非服务器。
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<el-descriptions-item label="运行时长" :span="2">{{ serverUptime || '—' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions v-if="sl" :column="2" border title="SimpleLite" class="status-block">
|
||||
<el-descriptions v-if="sl" :column="2" border title="Simple3" class="status-block">
|
||||
<el-descriptions-item label="托管启用">
|
||||
<el-tag :type="sl.enabled ? 'success' : 'info'" size="small">{{ sl.enabled ? '是' : '否' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
@@ -43,15 +43,15 @@
|
||||
|
||||
<el-alert v-if="error" :title="error" type="warning" :closable="false" class="status-block" />
|
||||
|
||||
<div v-if="canControlSimpleLite" class="status-block sl-control">
|
||||
<div class="sl-control-title">SimpleLite 操作</div>
|
||||
<div v-if="canControlSimple3" class="status-block sl-control">
|
||||
<div class="sl-control-title">Simple3 操作</div>
|
||||
<div class="sl-control-actions">
|
||||
<el-button
|
||||
type="warning"
|
||||
:loading="actionLoading === 'restart'"
|
||||
:disabled="!!actionLoading"
|
||||
@click="onRestart">
|
||||
重启 SimpleLite
|
||||
重启 Simple3
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@@ -59,10 +59,10 @@
|
||||
:loading="actionLoading === 'stop'"
|
||||
:disabled="!!actionLoading"
|
||||
@click="onStop">
|
||||
关闭 SimpleLite
|
||||
关闭 Simple3
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="sl-control-hint">将终止本机全部 SimpleLite 进程;重启后按当前启动模式重新拉起。</p>
|
||||
<p class="sl-control-hint">将终止本机全部 Simple3 进程;重启后按当前启动模式重新拉起。</p>
|
||||
</div>
|
||||
|
||||
<div class="status-actions">
|
||||
@@ -79,12 +79,11 @@ import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getHealth,
|
||||
getSimpleLiteDiagnostics,
|
||||
restartSimpleLite,
|
||||
resolveRestartLaunchMode,
|
||||
stopSimpleLite,
|
||||
getSimple3Diagnostics,
|
||||
restartSimple3,
|
||||
stopSimple3,
|
||||
type HealthInfo,
|
||||
type SimpleLiteDiagnostics
|
||||
type Simple3Diagnostics
|
||||
} from '@/api/health'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
@@ -94,10 +93,10 @@ const auth = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const actionLoading = ref<'stop' | 'restart' | null>(null)
|
||||
const health = ref<HealthInfo | null>(null)
|
||||
const sl = ref<SimpleLiteDiagnostics | null>(null)
|
||||
const sl = ref<Simple3Diagnostics | null>(null)
|
||||
const error = ref('')
|
||||
|
||||
const canControlSimpleLite = computed(() => auth.token && auth.scope === 'Platform')
|
||||
const canControlSimple3 = computed(() => auth.token && auth.scope === 'Platform')
|
||||
|
||||
let timer: number | undefined
|
||||
|
||||
@@ -132,16 +131,16 @@ async function refresh() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
// /status 是 public 页:未登录只拉匿名 /health,不调需登录的 simplelite 诊断
|
||||
// /status 是 public 页:未登录只拉匿名 /health,不调需登录的 simple3 诊断
|
||||
// (401 会触发全局拦截器强制跳转登录页)。
|
||||
const [h, d] = await Promise.allSettled([
|
||||
getHealth(),
|
||||
auth.token ? getSimpleLiteDiagnostics() : Promise.reject(new Error('skipped'))
|
||||
auth.token ? getSimple3Diagnostics() : Promise.reject(new Error('skipped'))
|
||||
])
|
||||
health.value = h.status === 'fulfilled' ? h.value.data : null
|
||||
sl.value = d.status === 'fulfilled' ? d.value.data : null
|
||||
if (h.status === 'rejected') error.value = '无法连接 MiGu.Server(/api/health)'
|
||||
else if (auth.token && d.status === 'rejected') error.value = 'SimpleLite 诊断获取失败(/api/health/simplelite)'
|
||||
else if (auth.token && d.status === 'rejected') error.value = 'Simple3 诊断获取失败(/api/health/simple3)'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -150,8 +149,8 @@ async function refresh() {
|
||||
async function onStop() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'将终止本机全部 SimpleLite 进程,地图监控与 /api/sl/* 功能将不可用,直到重新登录或手动重启。',
|
||||
'关闭 SimpleLite',
|
||||
'将终止本机全部 Simple3 进程,地图监控与 /api/sl/* 功能将不可用,直到重新登录或手动重启。',
|
||||
'关闭 Simple3',
|
||||
{ type: 'warning', confirmButtonText: '关闭', cancelButtonText: '取消' }
|
||||
)
|
||||
} catch {
|
||||
@@ -160,24 +159,21 @@ async function onStop() {
|
||||
|
||||
actionLoading.value = 'stop'
|
||||
try {
|
||||
const { data } = await stopSimpleLite()
|
||||
const { data } = await stopSimple3()
|
||||
sl.value = data.diagnostics
|
||||
ElMessage.success(data.killed > 0 ? `已关闭 ${data.killed} 个 SimpleLite 进程` : '当前没有运行中的 SimpleLite 进程')
|
||||
ElMessage.success(data.killed > 0 ? `已关闭 ${data.killed} 个 Simple3 进程` : '当前没有运行中的 Simple3 进程')
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '关闭 SimpleLite 失败')
|
||||
ElMessage.error(e instanceof Error ? e.message : '关闭 Simple3 失败')
|
||||
} finally {
|
||||
actionLoading.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function onRestart() {
|
||||
const launchMode = resolveRestartLaunchMode(sl.value?.lastLaunchMode, auth.runMode)
|
||||
const modeLabel = launchMode === 'WebOnly' ? '仅 Web' : '本地 + Web'
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`将关闭本机全部 SimpleLite 并按「${modeLabel}」模式重新拉起,期间 /api/sl/* 可能短暂不可用。`,
|
||||
'重启 SimpleLite',
|
||||
'将关闭本机全部 Simple3 并以 Web 模式重新拉起,期间 /api/sl/* 可能短暂不可用。',
|
||||
'重启内核',
|
||||
{ type: 'warning', confirmButtonText: '重启', cancelButtonText: '取消' }
|
||||
)
|
||||
} catch {
|
||||
@@ -186,14 +182,14 @@ async function onRestart() {
|
||||
|
||||
actionLoading.value = 'restart'
|
||||
try {
|
||||
const { data } = await restartSimpleLite(launchMode)
|
||||
const { data } = await restartSimple3('WebOnly')
|
||||
sl.value = data.diagnostics
|
||||
const r = data.restart
|
||||
if (r.warning) ElMessage.warning(r.warning)
|
||||
else if (r.started) ElMessage.success('SimpleLite 已重新拉起')
|
||||
else if (r.started) ElMessage.success('内核已重新拉起')
|
||||
else ElMessage.error(`重启未完成:${r.status}`)
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '重启 SimpleLite 失败')
|
||||
ElMessage.error(e instanceof Error ? e.message : '重启失败')
|
||||
} finally {
|
||||
actionLoading.value = null
|
||||
void refresh()
|
||||
@@ -202,7 +198,7 @@ async function onRestart() {
|
||||
|
||||
onMounted(() => {
|
||||
void refresh()
|
||||
// 10s 轮询:服务端对 goto-site 探测有 60s 缓存,此频率不会对 SimpleLite 产生压力。
|
||||
// 10s 轮询:服务端对 goto-site 探测有 60s 缓存,此频率不会对 Simple3 产生压力。
|
||||
timer = window.setInterval(() => void refresh(), 10_000)
|
||||
})
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="am-offline"
|
||||
title="SimpleLite 未连接:以下为平台最近一次采集的报警快照。"
|
||||
title="Simple3 未连接:以下为平台最近一次采集的报警快照。"
|
||||
:description="lastSyncAt ? `最近同步:${formatTime(lastSyncAt)}` : '暂无同步记录'"
|
||||
/>
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-empty v-if="!loading && types.length === 0" description="未发现任何 Car 子类(请确认 SimpleLite 已加载相应插件 dll)" />
|
||||
<el-empty v-if="!loading && types.length === 0" description="未发现任何 Car 子类(请确认 Simple3 已加载相应插件 dll)" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -161,7 +161,7 @@ async function refresh() {
|
||||
loading.value = true
|
||||
try {
|
||||
// 会话 N+2:car-types 与 alarm-colors 独立 await,避免一边失败把另一边的成功也吞掉。
|
||||
// 任意一边 SimpleLite 未启动 / 返回了非预期结构(YARP 502 / null payload),都不要让整个面板白屏崩。
|
||||
// 任意一边 Simple3 未启动 / 返回了非预期结构(YARP 502 / null payload),都不要让整个面板白屏崩。
|
||||
const carTypesResp = await reflectionApi.getCarStyleTypes().catch((err) => {
|
||||
console.warn('[CarStyleEditor] getCarStyleTypes failed:', err)
|
||||
return null
|
||||
@@ -196,7 +196,7 @@ async function refresh() {
|
||||
}
|
||||
|
||||
if (!carTypesResp || !paletteResp) {
|
||||
ElMessage.warning('车型样式 / 报警颜色加载部分失败,请确认 SimpleLite 已启动(端口 8222)。')
|
||||
ElMessage.warning('车型样式 / 报警颜色加载部分失败,请确认 Simple3 已启动(端口 8222)。')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error(`加载车型样式失败:${(err as Error).message}`)
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
<p class="cse-model-hint">
|
||||
现版本仅记录文件路径作为元信息。3D 渲染替代矩形将在后续阶段实现(CycleGUI Workspace LoadModel)。
|
||||
如需上传文件到服务器请先用其它方式将模型放到 SimpleLite 可访问的相对路径。
|
||||
如需上传文件到服务器请先用其它方式将模型放到 Simple3 可访问的相对路径。
|
||||
</p>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ import { fetchMonitorConfigCached, invalidateMonitorConfigCache } from '@/utils/
|
||||
import type { MapFocusKind } from '@/utils/mapObjectFocus'
|
||||
import { defaultVrHost } from '@/utils/vrender'
|
||||
defineProps<{
|
||||
/** 只读模式(运营端复用 MapMonitorView 时传 true):3D 不可编辑,选中信息面板动作改用运维白名单。 */
|
||||
/** 只读:3D 不可编辑,站点/路径编辑区隐藏。车辆动作一律走运维网关并记审计。 */
|
||||
readOnly?: boolean
|
||||
}>()
|
||||
|
||||
@@ -513,7 +513,7 @@ function onPick(_p: { x: number; y: number }) {
|
||||
// Workspace3D pick 坐标事件;监控页侧栏选中走 SSE selection-detail / select
|
||||
}
|
||||
|
||||
// SimpleLite 3D 端发送的对象命名规则(见 SimpleUI.cs / UISite.cs / TrackUiHelper):
|
||||
// Simple3 3D 端发送的对象命名规则(见 SimpleUI.cs / UISite.cs / TrackUiHelper):
|
||||
// - 车辆:`Car-{id}` 或 `UICar-{id}`(不同入口不同前缀,正则同时兼容)
|
||||
// - 站点:`UISite-{id}`
|
||||
// - 路径:`UITrack-{id}` / `UICircularArcTrack-{id}` / `UIBezierTrack-{id}` / `UINurbsTrack-{id}`
|
||||
@@ -596,7 +596,7 @@ function applySelectionFromKindId(kind: 'site' | 'track' | 'car', id: number, na
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleLite 画布点选后通过 SSE `selection-detail` 广播(见 SimpleUI.BroadcastSelectionToWeb)。
|
||||
* Simple3 画布点选后通过 SSE `selection-detail` 广播(见 SimpleUI.BroadcastSelectionToWeb)。
|
||||
* embed 模式通常不会 postMessage `workspace.select`,因此必须在此同步侧栏。
|
||||
*/
|
||||
function onSelectionDetailFromStream(e: SelectionDetailEvent) {
|
||||
@@ -619,10 +619,14 @@ function onSelectionDetailFromStream(e: SelectionDetailEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
if (e.kind && e.id) {
|
||||
if (e.kind === 'site' || e.kind === 'track' || e.kind === 'car') {
|
||||
applySelectionFromKindId(e.kind, e.id, e.names)
|
||||
return
|
||||
}
|
||||
if (e.kind === 'special' && e.id) {
|
||||
const next = { kind: 'special', id: String(e.id), name: `装饰物 ${e.id}` }
|
||||
if (!sameSelection(selection.value, next)) selection.value = next
|
||||
}
|
||||
|
||||
if (siteN > 0) {
|
||||
applySelectionFromKindId('site', e.siteIds![0]!, e.names)
|
||||
@@ -727,7 +731,7 @@ async function applyPendingPlayback() {
|
||||
if (!fileName || !workspaceReady.value) return
|
||||
pendingPlayback.value = null
|
||||
try {
|
||||
// iframe 终端就绪后再启动:SimpleLite StartPlayback 会把回放控制条投影到该 webVRender 终端。
|
||||
// iframe 终端就绪后再启动:Simple3 StartPlayback 会把回放控制条投影到该 webVRender 终端。
|
||||
await workspaceToolbarApi.startPlayback(fileName)
|
||||
ElMessage.success({ message: `已开始回放:${fileName}`, grouping: true })
|
||||
} catch (err) {
|
||||
@@ -759,7 +763,7 @@ async function fallbackSelectionFromBackend() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 选中并定位到指定车辆:① 同步 SimpleLite 3D 场景高亮;② 立即把相机定位(居中 + 2D 俯视)
|
||||
* 选中并定位到指定车辆:① 同步 Simple3 3D 场景高亮;② 立即把相机定位(居中 + 2D 俯视)
|
||||
* 到该车,与原生「双击车辆行 = 选中+定位」一致。两个请求互不阻塞,各自独立提示错误
|
||||
* (例如 iframe 未连接时相机定位会 503,但高亮仍可成功)。
|
||||
*/
|
||||
@@ -872,10 +876,10 @@ async function refreshAll() {
|
||||
// - snapshot-tick:后端 1Hz 心跳,仅含 cars/missions/sites/tracks 数字,不带实际状态。**不要**
|
||||
// 在这里触发 refreshAll,否则会等价"1s 全量轮询 listCars+listMissions",比原来的 3s 轮询还
|
||||
// 重。snapshot-tick 仅用于 `stream.connected` 心跳判定。
|
||||
// - selection-detail:画布点选后 SimpleLite 广播,驱动右侧「选中信息」面板(embed 模式主路径)。
|
||||
// - selection-detail:画布点选后 Simple3 广播,驱动右侧「选中信息」面板(embed 模式主路径)。
|
||||
// - 其它 object-* / pick-result / project-*:本页不处理,由地图编辑页订阅。
|
||||
//
|
||||
// 实时通道未连接(旧 SimpleLite / 离线)时退回 3s 轮询保底。
|
||||
// 实时通道未连接(旧 Simple3 / 离线)时退回 3s 轮询保底。
|
||||
const stream = useProjectionStream({ autoConnect: true })
|
||||
const streamConnected = computed(() => stream.connected.value)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* (UiDiscoveryCache.MissionCreatableTypes 同源),这里直接用 ReflectionManagerPanel
|
||||
* 接入,按下「新建任务」会列出所有 MissionType 子类(含 taskflow / trigger)。
|
||||
*
|
||||
* 旧版本的 DataTablePro + listMissions mock 已经下线,所有数据走 SimpleLite 反射 API。
|
||||
* 旧版本的 DataTablePro + listMissions mock 已经下线,所有数据走 Simple3 反射 API。
|
||||
*/
|
||||
import ReflectionManagerPanel from '@/components/reflection/ReflectionManagerPanel.vue'
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-if="activeTab === 'project'"
|
||||
ref="projectEditor"
|
||||
target-label="项目属性"
|
||||
description="对应 SimpleLite 桌面端主菜单「项目属性…」。编辑字段后点「保存」即可一次完成内存生效 + 写回项目 JSON 文件(含全部场景/车辆/路径/属性)。"
|
||||
description="对应 Simple3 桌面端主菜单「项目属性…」。编辑字段后点「保存」即可一次完成内存生效 + 写回项目 JSON 文件(含全部场景/车辆/路径/属性)。"
|
||||
:loader="loadProject"
|
||||
:on-set="setProjectField"
|
||||
:on-save="saveProject"
|
||||
@@ -18,7 +18,7 @@
|
||||
v-if="activeTab === 'app-config'"
|
||||
ref="appConfigEditor"
|
||||
target-label="核心配置"
|
||||
description="对应 SimpleLite 桌面端主菜单「核心配置 → 编辑配置…」。修改后点「保存」即可一次完成内存生效 + 写入可执行目录下的 simple.json。"
|
||||
description="对应 Simple3 桌面端主菜单「核心配置 → 编辑配置…」。修改后点「保存」即可一次完成内存生效 + 写入可执行目录下的 simple.json。"
|
||||
:loader="loadAppConfig"
|
||||
:on-set="setAppConfigField"
|
||||
:on-save="saveAppConfig"
|
||||
@@ -48,7 +48,7 @@ async function loadProject(): Promise<FieldMemberEditorLoadResult> {
|
||||
return {
|
||||
fields: r.fields,
|
||||
pathInfos: [
|
||||
{ label: '最近加载/保存的项目 JSON', value: r.lastLoadedPath, emptyHint: '尚未通过 SimpleLite 加载或保存过项目' },
|
||||
{ label: '最近加载/保存的项目 JSON', value: r.lastLoadedPath, emptyHint: '尚未通过 Simple3 加载或保存过项目' },
|
||||
{ label: 'simple.json autoload', value: r.autoloadPath, emptyHint: '未配置' }
|
||||
],
|
||||
saveButtonLabel: '保存(写回项目 JSON)'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="script-page">
|
||||
<!--
|
||||
平台「脚本管理」= SimpleLite 工作台「脚本」页(ComposerDockPanel.RenderScriptTable)的 Web 镜像:
|
||||
平台「脚本管理」= Simple3 工作台「脚本」页(ComposerDockPanel.RenderScriptTable)的 Web 镜像:
|
||||
- 列表 = CarProgram.GetPrograms(),最多 300 条(含历史),由 Mission 调度运行时自动产生与回收。
|
||||
- 列:ID / 名称(任务名) / 类型(CarProgram 子类) / 车辆(plans[0].usingCar) / 状态(ProgramStatus.state)。
|
||||
- 不渲染「新建」按钮:CarProgram 无法手动 new,由 Mission 调度运行时自动产生(在 SimpleLite 工作台新建 Mission 后生成)。
|
||||
- 不渲染「新建」按钮:CarProgram 无法手动 new,由 Mission 调度运行时自动产生(在 Simple3 工作台新建 Mission 后生成)。
|
||||
- 不渲染「删除」按钮:CarProgram 由调度器在环形队列里维护,删除一律会被后端拒。
|
||||
右侧详情面板沿用通用 ReflectionManagerPanel 渲染(fields 多为 typed/locked,只读 status 通过反射)。
|
||||
-->
|
||||
@@ -12,7 +12,7 @@
|
||||
kind="script"
|
||||
kind-label="脚本"
|
||||
title="脚本管理"
|
||||
empty-text="当前无 CarProgram 实例。CarProgram 是 Mission 运行时编译用户脚本后产生的对象,请在 SimpleLite 工作台新建 Mission(任务)后由调度运行时自动生成。"
|
||||
empty-text="当前无 CarProgram 实例。CarProgram 是 Mission 运行时编译用户脚本后产生的对象,请在 Simple3 工作台新建 Mission(任务)后由调度运行时自动生成。"
|
||||
disable-create
|
||||
disable-delete
|
||||
show-summary-column
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>字段管理</h2>
|
||||
<p>「默认」从 SimpleLite 拉取全部车型字段;「刷新」从数据库读取;「保存」将全部字段写入数据库。</p>
|
||||
<p>「默认」从 Simple3 拉取全部车型字段;「刷新」从数据库读取;「保存」将全部字段写入数据库。</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button :loading="initializing || loadingDefaults" @click="loadDefaults">默认</el-button>
|
||||
@@ -339,7 +339,7 @@ function allCarsToRows(cars: CarTypeCoderFieldsRow[]): FieldRow[] {
|
||||
return cars.flatMap(carTypeToRows)
|
||||
}
|
||||
|
||||
/** 从已加载的数据库记录推导车型下拉(不请求 SimpleLite)。 */
|
||||
/** 从已加载的数据库记录推导车型下拉(不请求 Simple3)。 */
|
||||
function syncCarTypesFromFieldRows() {
|
||||
const labelByKey = new Map(
|
||||
carTypes.value.map((c) => [buildCarType(c.assemblyName, c.shortName), c.label])
|
||||
@@ -363,7 +363,7 @@ function syncCarTypesFromFieldRows() {
|
||||
carTypes.value = options
|
||||
}
|
||||
|
||||
/** 从 SimpleLite 补全车型中文名(轻量接口,不拉字段定义)。 */
|
||||
/** 从 Simple3 补全车型中文名(轻量接口,不拉字段定义)。 */
|
||||
async function enrichCarTypeLabels() {
|
||||
try {
|
||||
const types = await reflectionApi.listCreatableTypes('car')
|
||||
@@ -376,7 +376,7 @@ async function enrichCarTypeLabels() {
|
||||
return label ? { ...c, label } : c
|
||||
})
|
||||
} catch {
|
||||
/* SimpleLite 未连接时保留现有显示 */
|
||||
/* Simple3 未连接时保留现有显示 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ async function loadDefaults() {
|
||||
try {
|
||||
const cars = await reflectionApi.getCarTypeCoderFields()
|
||||
if (!cars.length) {
|
||||
ElMessage.warning('未获取到车型列表,请确认 SimpleLite 已启动')
|
||||
ElMessage.warning('未获取到车型列表,请确认 Simple3 已启动')
|
||||
return
|
||||
}
|
||||
carTypes.value = cars.map((c) => ({
|
||||
@@ -434,7 +434,7 @@ async function loadDefaults() {
|
||||
}))
|
||||
ensureDefaultCarType()
|
||||
fieldRows.value = allCarsToRows(cars)
|
||||
ElMessage.success(`已从 SimpleLite 加载 ${cars.length} 种车型、共 ${fieldRows.value.length} 条默认字段(请点击保存写入数据库)`)
|
||||
ElMessage.success(`已从 Simple3 加载 ${cars.length} 种车型、共 ${fieldRows.value.length} 条默认字段(请点击保存写入数据库)`)
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '加载默认字段失败')
|
||||
} finally {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="tm-offline"
|
||||
title="SimpleLite 未连接:以下为平台最近一次快照;取消/重发/新建等操作需 SimpleLite 在线,离线执行会提示失败。"
|
||||
title="Simple3 未连接:以下为平台最近一次快照;取消/重发/新建等操作需 Simple3 在线,离线执行会提示失败。"
|
||||
:description="lastSyncAt ? `最近同步:${formatTime(lastSyncAt)}` : '暂无同步记录'"
|
||||
/>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<p class="mmc-desc">
|
||||
勾选「地图监控」右侧选中信息面板要展示的内容白名单(按对象类型 - 车辆 / 站点 / 路径 分组)。
|
||||
三组分别控制 <b>属性</b>、<b>状态</b>、<b>动作</b> Tab 里可见的字段 / 状态项 / 可执行方法。
|
||||
每组留空 = 显示全部;勾选任何一项即进入白名单模式(仅显示已勾选)。保存后直接写入 SimpleLite 的
|
||||
每组留空 = 显示全部;勾选任何一项即进入白名单模式(仅显示已勾选)。保存后直接写入 Simple3 的
|
||||
<code>simple.json</code> (<code>Configuration.conf.monitorVisibility</code>)。
|
||||
</p>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
title="属性字段(Properties)"
|
||||
:available="available[k.value].fields"
|
||||
:selected="config[k.value].fields"
|
||||
empty-tip="该类型暂未发现任何 [FieldMember]。在 SimpleLite 端添加字段后点重载会刷新。"
|
||||
empty-tip="该类型暂未发现任何 [FieldMember]。在 Simple3 端添加字段后点重载会刷新。"
|
||||
@update:selected="(v: string[]) => onUpdate(k.value, 'fields', v)" />
|
||||
|
||||
<MapMonitorConfigGroup
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<el-option v-for="s in displayedSites" :key="s.id" :label="s.label" :value="s.id" />
|
||||
</el-select>
|
||||
<span v-if="sitesLoadError" class="field-hint field-hint-warn">{{ sitesLoadError }}</span>
|
||||
<span v-else class="field-hint">主数据来自 SimpleLite,保存值为站点 id</span>
|
||||
<span v-else class="field-hint">主数据来自 Simple3,保存值为站点 id</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="条码"><el-input v-model="storageForm.barcode" /></el-form-item>
|
||||
<el-form-item label="优先级"><el-input-number v-model="storageForm.priority" :min="0" /></el-form-item>
|
||||
@@ -155,7 +155,7 @@ import {
|
||||
AREA_TYPES, AREA_LAYOUT_MODES, STORAGE_TYPES, LOCATION_KINDS, CONTAINER_TYPES,
|
||||
CONTAINER_LOCATION_STATUSES, MATERIAL_UNITS, MATERIAL_LIFECYCLES
|
||||
} from './wmsOptions'
|
||||
import { useSimpleLiteSites } from './useSimpleLiteSites'
|
||||
import { useSimple3Sites } from './useSimple3Sites'
|
||||
|
||||
export type DialogKind = 'area' | 'storage' | 'container' | 'materialType' | 'material' | 'location' | 'containerMaterial' | 'rule'
|
||||
|
||||
@@ -186,7 +186,7 @@ const emit = defineEmits<{
|
||||
const formRef = ref<FormInstance>()
|
||||
const areaLayoutMode = defineModel<string>('areaLayoutMode', { default: 'Flat' })
|
||||
const containerBarcode = defineModel<string>('containerBarcode', { default: '' })
|
||||
const { sites: simpleLiteSites, loading: sitesLoading, loadError: sitesLoadError, load: loadSimpleLiteSites } = useSimpleLiteSites()
|
||||
const { sites: simple3Sites, loading: sitesLoading, loadError: sitesLoadError, load: loadSimple3Sites } = useSimple3Sites()
|
||||
const siteSearch = ref('')
|
||||
|
||||
function onSiteChange(id: string) {
|
||||
@@ -203,10 +203,10 @@ function onMaterialTypeChange(code: string) {
|
||||
}
|
||||
|
||||
const siteOptionsWithCurrent = computed(() => {
|
||||
const opts = [...simpleLiteSites.value]
|
||||
const opts = [...simple3Sites.value]
|
||||
const current = props.storageForm.siteId?.trim()
|
||||
if (current && !opts.some((o) => o.id === current)) {
|
||||
opts.unshift({ id: current, name: '', label: `#${current}(未在 SimpleLite 列表中)` })
|
||||
opts.unshift({ id: current, name: '', label: `#${current}(未在 Simple3 列表中)` })
|
||||
}
|
||||
return opts
|
||||
})
|
||||
@@ -289,7 +289,7 @@ watch(() => props.visible, (v) => {
|
||||
formRef.value?.clearValidate()
|
||||
if (props.kind === 'storage') {
|
||||
siteSearch.value = ''
|
||||
void loadSimpleLiteSites()
|
||||
void loadSimple3Sites()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+6
-6
@@ -2,13 +2,13 @@ import { ref } from 'vue'
|
||||
import { reflectionApi } from '@/api/reflection'
|
||||
import { listSites } from '@/api/projection'
|
||||
|
||||
export interface SimpleLiteSiteOption {
|
||||
export interface Simple3SiteOption {
|
||||
id: string
|
||||
name: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export async function fetchSimpleLiteSites(): Promise<SimpleLiteSiteOption[]> {
|
||||
export async function fetchSimple3Sites(): Promise<Simple3SiteOption[]> {
|
||||
try {
|
||||
const rows = await reflectionApi.listObjects('site')
|
||||
if (rows.length > 0) {
|
||||
@@ -33,8 +33,8 @@ export async function fetchSimpleLiteSites(): Promise<SimpleLiteSiteOption[]> {
|
||||
.sort((a, b) => Number(a.id) - Number(b.id) || a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
export function useSimpleLiteSites() {
|
||||
const sites = ref<SimpleLiteSiteOption[]>([])
|
||||
export function useSimple3Sites() {
|
||||
const sites = ref<Simple3SiteOption[]>([])
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
|
||||
@@ -42,9 +42,9 @@ export function useSimpleLiteSites() {
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
sites.value = await fetchSimpleLiteSites()
|
||||
sites.value = await fetchSimple3Sites()
|
||||
} catch (e) {
|
||||
loadError.value = e instanceof Error ? e.message : '加载 SimpleLite 站点失败'
|
||||
loadError.value = e instanceof Error ? e.message : '加载 Simple3 站点失败'
|
||||
sites.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -9,11 +9,10 @@
|
||||
* 只读模式下:
|
||||
* - 3D 画布不可编辑(Workspace3D read-only),scope 由 MapMonitorView 内部按 auth.scope 取,
|
||||
* 运营端登录后即 RCSMonitor。
|
||||
* - 右侧「选中信息」面板的车辆动作改用运维白名单(OPS_WHITELIST + executeOp 审计 + 二次确认,
|
||||
* 按当前账号 hasOp 过滤),站点/路径的编辑动作区隐藏。
|
||||
* - 右侧「选中信息」车辆动作与管理端相同(monitor-config 按车型勾选),经 /api/sl/ops/execute
|
||||
* (ops.car.execute)转发 Simple3,站点/路径的编辑动作区隐藏。
|
||||
*
|
||||
* 备注:原运营端地图页内的「任务」白名单动作(暂停/取消/重派等)请走 /monitor/ops 运维操作页,
|
||||
* 地图监控页聚焦于地图对象(车/站/路)的监视与车辆运维动作。
|
||||
* 备注:任务白名单动作(暂停/取消/重派等)请走 /monitor/ops 运维操作页。
|
||||
*/
|
||||
import MapMonitorView from '@/views/admin/MapMonitorView.vue'
|
||||
</script>
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<FleetAllocationPanel :cars="cardModels" :can-write="canWrite" @saved="onFleetSaved" />
|
||||
|
||||
<p class="footnote">
|
||||
延迟 = 本机到车辆 WatchDog(:9776) 的 TCP 往返;故障率 = 报警占用时长 ÷ 自上线以来运行时长(SimpleLite 进程内累计)
|
||||
延迟 = 本机到车辆 WatchDog(:9776) 的 TCP 往返;故障率 = 报警占用时长 ÷ 自上线以来运行时长(Simple3 进程内累计)
|
||||
</p>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -32,7 +32,7 @@ export function assignCarParams(): ParamFieldDef[] {
|
||||
label: '车辆类型',
|
||||
type: 'string',
|
||||
required: true,
|
||||
placeholder: '如 Forklift / SimpleLite.RCS.Cars.AgvCar',
|
||||
placeholder: '如 Forklift / Simple3.RCS.Cars.AgvCar',
|
||||
when: BY_CAR_TYPE
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# 将最新编译的 Simple3 部署到 bin\Debug 并提示重启。
|
||||
# 用法:先关闭 Simple3(任务管理器或托盘退出),再运行本脚本。
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$slProj = Join-Path $repoRoot 'Simple\Simple3\Simple3.csproj'
|
||||
$outDir = Join-Path $repoRoot 'Simple\Simple3\bin\Debug'
|
||||
$objDll = Join-Path $repoRoot 'Simple\Simple3\obj\Debug\Simple3.dll'
|
||||
|
||||
Write-Host '==> 编译 Simple3 (DisableFody)...' -ForegroundColor Cyan
|
||||
dotnet build $slProj -c Debug -p:DisableFody=true
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet build 失败 (exit $LASTEXITCODE)" }
|
||||
|
||||
$builtDll = if (Test-Path $objDll) { $objDll } else { Join-Path $outDir 'Simple3.dll' }
|
||||
|
||||
if ((Get-Process Simple3 -ErrorAction SilentlyContinue) -or (Get-Process SimpleLite -ErrorAction SilentlyContinue)) {
|
||||
Write-Host ''
|
||||
Write-Host 'Simple3 仍在运行,无法覆盖 DLL。请先关闭 Simple3.exe,再重新运行本脚本。' -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Copy-Item $builtDll (Join-Path $outDir 'Simple3.dll') -Force
|
||||
$objExe = Join-Path $repoRoot 'Simple\Simple3\obj\Debug\Simple3.exe'
|
||||
if (Test-Path $objExe) {
|
||||
Copy-Item $objExe (Join-Path $outDir 'Simple3.exe') -Force
|
||||
}
|
||||
|
||||
Write-Host '==> 已更新' $outDir -ForegroundColor Green
|
||||
Write-Host '请重新登录迷毂平台(或手动启动 Simple3.exe)以加载新 API。'
|
||||
@@ -1,29 +0,0 @@
|
||||
# 将最新编译的 SimpleLite 部署到 bin\Debug 并提示重启。
|
||||
# 用法:先关闭 SimpleLite(任务管理器或托盘退出),再运行本脚本。
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$slProj = Join-Path $repoRoot 'Simple\SimpleLite\SimpleLite.csproj'
|
||||
$outDir = Join-Path $repoRoot 'Simple\SimpleLite\bin\Debug'
|
||||
$objDll = Join-Path $repoRoot 'Simple\SimpleLite\obj\Debug\SimpleLite.dll'
|
||||
|
||||
Write-Host '==> 编译 SimpleLite (DisableFody)...' -ForegroundColor Cyan
|
||||
dotnet build $slProj -c Debug -p:DisableFody=true
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet build 失败 (exit $LASTEXITCODE)" }
|
||||
|
||||
$builtDll = if (Test-Path $objDll) { $objDll } else { Join-Path $outDir 'SimpleLite.dll' }
|
||||
|
||||
if (Get-Process SimpleLite -ErrorAction SilentlyContinue) {
|
||||
Write-Host ''
|
||||
Write-Host 'SimpleLite 仍在运行,无法覆盖 DLL。请先关闭 SimpleLite.exe,再重新运行本脚本。' -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Copy-Item $builtDll (Join-Path $outDir 'SimpleLite.dll') -Force
|
||||
$objExe = Join-Path $repoRoot 'Simple\SimpleLite\obj\Debug\SimpleLite.exe'
|
||||
if (Test-Path $objExe) {
|
||||
Copy-Item $objExe (Join-Path $outDir 'SimpleLite.exe') -Force
|
||||
}
|
||||
|
||||
Write-Host '==> 已更新' $outDir -ForegroundColor Green
|
||||
Write-Host '请重新登录迷榖平台(或手动启动 SimpleLite.exe)以加载新 API。'
|
||||
Reference in New Issue
Block a user