# 多网关第一阶段:Gateway 验收报告 ## 结论与范围 2026-09-09:第一阶段 Gateway 插件能力已实现,自动化回归通过。多网关待办 G1–G4 已勾选;App A1–A4、第二/三阶段及真实多机人工验收保持未完成。 本次只修改插件仓库,未修改 App,未部署到用户的服务器/PC,未发布 npm 新版本。当前 package.json 仍为 0.7.2,因此验收应使用本次工作区构建/打包产物,不能直接用 npm 上的同版本发布包推断已包含改动。 ## 交付内容 | 能力 | 实现位置 | 结果 | |---|---|---| | 三种模式、界面选择、状态持久化及启动优先级 | `lib/index.mjs`、`lib/client.js`、`lib/gateway-state.mjs` | 已实现 | | 稳定网关 UUID、配置名称、损坏时拒绝启动 | `lib/gateway-state.mjs`、`lib/index.mjs` | 已实现 | | 配对候选地址合并、去重、URL 校验 | `lib/index.mjs` | 已实现 | | 配对/paired/hello 身份扩展及旧协议保留 | `lib/index.mjs`、`PROTOCOL.md` | 已实现 | | App 字段、迁移、路由及安全存储约定 | [App 对接说明](multi-gateway-app-integration.md) | 已交付文档;App 尚未实现 | | 实施范围及勾选状态 | [多网关待办](multi-gateway-todo.md) | 仅勾选已完成的 Gateway 项 | ## 自动化验证 执行环境:macOS arm64、Node.js v24.19.0;兼容性样例使用 Apple Swift 6.3.3。集成测试启动本地 HTTP/WebSocket 服务,DSH Host 使用测试替身,未连接真实 DSH 服务。测试因沙箱禁止监听本机端口,使用经批准的沙箱外执行完成。 | 验证 | 结果 | 证据/范围 | |---|---|---| | `npm test` | 通过 | setup-ip、host-adapter、gateway、auth、lan、multi-gateway 六个测试脚本 | | 原业务分发回归 | 通过 | `FULL GATEWAY DISPATCH TESTS PASSED (115)` | | 新增多网关验证 | 通过 | `multi-gateway: 11 checks passed`,见下方明细 | | LAN 与主监听身份一致 | 通过 | LAN 配对取得 token 后连接本机监听,比较 gatewayId;验证两入口仍按原规则鉴权 | | 旧 iOS 配对解码兼容 | 通过 | 从兄弟仓库提取实际 `GatewayPairingPayload` 和 `PairingPayloadParser`,使用 Swift 执行带 gatewayId/gatewayName/endpoints 的 v2 样例 | | JavaScript 语法与补丁空白检查 | 通过 | `node --check` 检查修改后的 JS/MJS;`git diff --check` | | 真机扫码、真实界面布局及真实多机联调 | 未执行 | 按下方步骤人工验收 | 新增 11 组行为检查: 1. Schema 模式校验、身份及模式持久化、文件权限 0600、损坏文件拒绝加载且保留原文件。 2. 旧启动配置 true 仍表示常驻;不同实例 ID 不同。 3. 主动关闭后连接返回 503,重启及更改启动配置不会覆盖已保存关闭状态;非法请求返回 400。 4. 旧 enabled 接口映射临时模式,临时模式重启重新计时,自动关闭结果持久化。 5. 切换常驻取消旧计时器,重启后模式与身份保持;名称变化不改变身份。 6. v2 Base64URL 配对载荷保留旧字段,新增地址规范化/去重;非法地址和过量列表返回 400。 7. 配对载荷、paired、控制 hello、会话 hello 身份一致,后续 token 连接不重复发送 paired。 8. 有客户端时切换临时模式不会设置等待计时器,断线后保持开启;重新配对轮换 token 并复用本机设备记录。 9. 同一客户端安装 ID 在两个网关分别配对,token 不能跨网关使用;撤销 A 不影响 B。 10. 主动关闭断开现有 WebSocket,close code 为 4004。 11. 模拟持久化失败返回 500,当前运行模式不被改变。 说明:临时超时测试直接调用插件时使用 120ms 等短测试值以缩短执行时间;真实 Cordis Schema 最小值仍为 30000ms。常驻测试证明其不会被等待首次连接的计时器关闭,不代表完成了长时间运行稳定性或压力测试。 复验命令(在插件仓库执行): ```bash npm test node --check lib/index.mjs node --check lib/client.js node --check lib/gateway-state.mjs git diff --check ``` ## 人工验收准备 1. 准备两台可访问的机器 A/B;完整目标验收再增加第三台 C(例如一台服务器、两台 PC)。每台运行包含本次改动的插件及兼容 DSH。 2. 使用独立的测试设备注册文件和 `gatewayStateFile`;不要与日常使用实例共用文件。给每台配置不同的 `gatewayName`。 3. 为测试实例设置 `gatewayWaitTimeoutMs: 30000`,保留 `requireAuth: true`;实际超时等待 35 秒。测试结束恢复所需生产值。 4. 打开各自本机 WebUI 的“移动设备”。准备旧版 App 做兼容验收;多网关客户端步骤需等待 App 按对接说明实现。 5. 若使用公网入口,先准备有效 WSS 和手机可达的网络。不要为了测试把 `/mgw/*` 管理接口开放到公网。 6. 记录插件来源、DSH/App 版本、机器与网络、执行时间。下方全部人工项尚未执行,应由验收者填写实测结果。 可在运行该实例的电脑上检查状态。默认示例端口为 3080,实际端口不同时替换: ```bash curl --fail-with-body http://127.0.0.1:3080/mgw/status ``` ### M1. 常驻选择、无连接等待与重启 - [ ] 已人工通过 操作:所有手机断开 → 面板选择“常驻开启” → 读取状态并记录 gatewayId → 等待 35 秒 → 再读取状态 → 停止并重启测试 DSH → 再读取状态。 预期:每次 `gatewayMode=persistent`、`gatewayEnabled=true`、`waitExpiresAt=null`,gatewayId 不变;面板仍显示常驻。重新扫码或使用已有可信凭证可连接。 ### M2. 主动关闭持久化 - [ ] 已人工通过 操作:手机保持连接 → 面板选择“关闭” → 观察手机断线 → 读取状态 → 保持启动配置为 persistent,重启 DSH → 再连接。 预期:连接关闭码为 4004;状态 disabled/false/null;重启后仍关闭,重新连接 Upgrade 返回 503。面板改回常驻后恢复。 ### M3. 临时模式的三个分支 - [ ] 已人工通过 操作 A:没有手机连接时选临时开启,等待 35 秒,再重启 DSH。 预期 A:先显示等待截止时间,超时自动变关闭,重启仍关闭。 操作 B:选临时开启,在 30 秒内完成手机连接,再断开手机,等待 35 秒。 预期 B:首次连接清除等待计时器,断开后本次运行仍开启。 操作 C:在 B 的状态重启 DSH,确保手机关闭自动重连,等待 35 秒。 预期 C:重启后临时模式重新计时,未连接则自动关闭。再次开启临时后立即切为常驻,等待 35 秒,确认旧计时器没有将常驻关闭。 ### M4. 协议身份及旧 App 兼容 - [ ] 已人工通过 操作:在 A 面板生成新二维码并复制配对字符串;检查解码结果为 version 2,包含原四字段及 gatewayId/gatewayName/endpoints。用旧版 App 扫码,查看工作区、会话并发一条测试消息。 预期:旧版 App 能扫码并正常完成原有业务;服务端使用原子协议和 hello protocol 3。用带帧调试能力的测试客户端核对 paired、control hello、conversation hello 与二维码中的 gatewayId 全部相同。不要把配对码或 token 贴入验收记录。 ### M5. 同一网关多个地址 - [ ] 已人工通过 操作:给 A 配置已实际可达的 LAN 和 WSS 地址;重新启动后生成配对码。用测试客户端或新版 App 完成首次配对,然后以所得同一 token 分别连接两个地址。 预期:二维码保留所选 publicUrl,候选地址去重;两地址返回同一 gatewayId。LAN 地址不可达时只影响该地址,不生成另一条网关身份。 反例:在本机配对请求传入下方错误地址,应返回 HTTP 400,不生成可用二维码: ```bash curl -i http://127.0.0.1:3080/mgw/pair \ -H 'Content-Type: application/json' \ --data '{"publicUrl":"wss://0.0.0.0/ws/mobile"}' ``` ### M6. 不同网关凭证及撤销隔离 - [ ] 已人工通过 操作:测试客户端使用同一个安装级 `X-DSH-Device-ID` 分别配对 A、B → 确认 ID 不同 → 使用 A token 连接 B(反向也测试)→ 用各自 token 正常连接 → 在 A 面板撤销测试设备。 预期:跨网关 token 均返回 401;A 连接以 4003 关闭且旧 token 不再可用;B 连接、token、业务访问不受影响。 ### M7. 名称、配置和文件故障 - [ ] 已人工通过 操作 A:记录 A 身份,修改 gatewayName 与候选地址配置,重启。 预期 A:名称和地址更新,gatewayId 不变。 操作 B(仅独立测试实例):停止 DSH,备份其状态文件,临时写入无效 JSON,再启动;随后停止失败实例、恢复原备份并重启。 预期 B:损坏时明确报 `failed to load gateway state`,没有静默覆盖文件或换 ID;恢复后原身份仍可用。 操作 C:在独立测试实例模拟状态文件不可写,修改运行模式。 预期 C:管理请求返回 500,界面显示失败,当前模式不变。恢复写权限后可以正常保存。若模拟的是自动超时保存失败,当前进程仍关闭并记录错误;应修复存储并重新保存,不能据此保证重启后的模式。 ### M8. 多网关 App 验收(待 App 实现) - [ ] 已人工通过 操作:新版 App 依次添加 A/B/C → 重启 App → 在三台网关之间切换 → 每台创建不同测试会话并检查工作区、模型配置、审批、文件 → 让 A 离线再切到 B/C → 再次扫描 B 的二维码更新配对。 预期:资料及凭证保留;会话及操作始终属于正确网关;A 离线不影响 B/C;重复扫码更新 B 而不是新增重复资料或覆盖 A/C。进一步构造跨网关相同资源 ID 和切换时旧事件迟到,验证缓存和回调隔离。 本项不是 Gateway 单仓库可完成的验收,本报告不勾选。 ### M9. 未知地址与身份不匹配(待 App 实现) - [ ] 已人工通过 操作:在独立测试环境让 A 资料指向 B 或一个不可信新地址,尝试恢复连接;再验证同一 gatewayId 的正常地址更新流程。 预期:App 不自动向未经确认的地址发送凭证;可信地址返回错误身份时停止业务且保留原记录。不能通过同名或自报 gatewayId 绕过确认与 TLS 校验。 ## 人工记录模板 | 编号 | 实际结果 | 通过/失败/未执行 | 证据(脱敏) | 执行人/时间 | |---|---|---|---|---| | M1–M9(每项独立填写) | 待填写 | 未执行 | 待填写 | 待填写 | 发布前至少完成 M1–M7。宣称 App 第一阶段整体完成前,还需完成 A1–A4 及 M8–M9。后台推送、并行控制连接、mDNS 发现、中心目录和网络中转不在本次交付范围。