局域网访问模式(LAN Access)
局域网访问模式(LAN Access)是指 SpinningMomo 的 Vue 3 Web 前端不通过嵌入式 WebView 加载,而是由局域网内的任意浏览器直接访问 C++ 后端 HTTP 服务(默认端口 51206)的一种前端访问形态。本文基于仓库源码,说明该模式所依赖的前后端通信拓扑、端点面(/rpc、/static、/downloads、/sse)、开发期代理配置与 WebView 嵌入桥接的差异。
目的与范围(Purpose and Scope)
本页面覆盖 web-frontend 目录下的 LAN 访问模式 子主题,具体包括:
- 前端如何与后端 HTTP 服务通信:
/rpc、/static、/downloads、/sse四类端点前缀的职责划分 - 开发模式下 Vite Dev Server 的代理规则(web/vite.config.ts)
- 嵌入式 WebView 模式与浏览器直连模式的桥接差异(web/src/types/webview.d.ts)
- 与访问模式相关的设置项与前端入口(
web/src/main.ts、features/settings/api)
以下内容有意留给兄弟页面,不在本页展开:
- RPC 协议本身的请求/响应编解码细节 → 参见 RPC 通信相关页面
- 设置界面(Settings)各功能项的完整实现 → 参见设置界面相关页面
- C++ 后端 HTTP 服务器的启动、路由注册与生命周期 → 属于后端文档范围
源码覆盖度说明(诚实声明):在本页的源码探索预算内,未检索到名为
lan/LanAccess的独立前端模块,也未发现0.0.0.0显式绑定或 LAN 开关的前端代码。仓库中可验证的核心事实是:后端固定在localhost:51206提供 HTTP 服务,前端所有流量(RPC、静态资源、下载、SSE 事件)都经由该端口的后端端点承载。因此本页以"已验证的通信拓扑"为主线展开;凡属推测之处均明确标注,不做虚构。
概述(Overview)
SpinningMomo 是一个 C++23 桌面应用(使用 xmake 构建,见 xmake.lua),其界面层是一套独立的 Vue 3 单页应用(web/ 目录)。整体架构是"本地 HTTP 后端 + Web 前端"的组合:
- C++ 后端在本地
51206端口提供 HTTP 服务,暴露/rpc(远程过程调用)、/static(静态资源)、/downloads(下载)、/sse(服务端推送事件)四类端点前缀 - 前端(Vue 3 SPA)在三种形态下运行:
- 嵌入模式:桌面应用内嵌 WebView 窗口加载前端,通过
postMessage桥接原生能力 - 开发模式:Vite Dev Server 承载前端,
/rpc、/static、/downloads、/sse被代理转发到后端 - LAN 浏览器直连模式:局域网内其他设备的浏览器直接访问后端 HTTP 服务获取前端资源
- 嵌入模式:桌面应用内嵌 WebView 窗口加载前端,通过
这一"前后端同源、以端口 51206 为唯一入口"的设计,使得 LAN 访问在拓扑上是开发代理模式的自然延伸——只要后端服务可被局域网访问,浏览器即可完整驱动界面。仓库的 AGENTS.md 对该拓扑有一句话概括:
web/uses a Vite dev server and proxies/rpcand/staticto the backend atlocalhost:51206.docs/is a separate VitePress site and is not part of the runtime bundle.
同时 docs/ 是独立的 VitePress 站点,不属于运行时打包产物——这明确了 LAN 访问的载体只有 web/ 构建出的前端与 C++ 后端。
架构(Architecture)
下图展示三种访问形态共用同一套后端端点面的真实拓扑(节点均对应仓库中实际存在的文件/端点):
架构要点说明:
- 唯一后端入口:无论哪种访问形态,前端业务数据的出口都是
localhost:51206上的端点。LAN 模式与开发模式的区别仅在于"谁来充当前端资源的提供方"——前者由后端直接托管前端构建产物,后者由 Vite Dev Server 提供并转发。 - 端点分工清晰:
/rpc承载命令式调用(如设置读写),/static承载静态资源,/downloads承载文件下载,/sse承载事件流。web/src/composables/useRpc.ts中存在专门用于"监听 RPC 事件"的 Vue Composable(源码注释),对应/sse的事件订阅通道。 - WebView 桥是嵌入模式独有路径:
types/webview.d.ts声明了postMessageWithAdditionalObjects等 API(见下文 API 参考),这是桌面嵌入窗口与前端之间的原生消息通道;LAN 浏览器直连时不经过该桥,完全依赖 HTTP 端点。这意味着任何依赖 WebView 桥的能力在 LAN 模式下需要通过 RPC 端点获得等效替代。
核心流程(Core Flow)
以下时序图展示 LAN 模式下,局域网浏览器从进入页面到完成一次设置读写的完整交互:
流程说明:
- 资源获取:LAN 浏览器直接向
:51206请求前端资源,后端返回 SPA 入口(web/index.html与web/src构建产物)。这一步不需要 Vite Dev Server 参与,是 LAN 模式与开发模式的分界点。 - SPA 启动与主题初始化:
main.ts在启动时读取settingsStore.appSettings.ui.webTheme.mode并 watch 其变化(web/src/main.ts),保证 LAN 浏览器侧的主题与桌面端设置保持一致。 - 命令式调用走
/rpc:所有设置读写等业务操作通过/rpc端点完成。设置模块的 API(如adbModeApi、DiscoveredAdbDevice类型)即由此路径承载(web/src/components/AdbDeviceInput.vue)。 - 事件订阅走
/sse:前端通过 SSE 长连接接收后端推送的事件;useRpc.ts提供的 Composable 封装了事件监听的生命周期(web/src/composables/useRpc.ts)。
使用示例(Usage Examples)
示例一:Vite 开发代理配置(LAN 拓扑的镜像实现)
以下配置把四类端点前缀全部代理到本地后端 51206。这条规则集中体现了"前后端以 51206 为唯一业务入口"的架构约束——LAN 直连模式下浏览器直接命中这些端点,开发模式下由代理转发到同一批端点:
1// vite.config.ts 中的 server.proxy 配置(节选)
2'/rpc': {
3 target: 'http://localhost:51206',
4 changeOrigin: true,
5},
6'/static': {
7 target: 'http://localhost:51206',
8 changeOrigin: true,
9},
10'/downloads': {
11 target: 'http://localhost:51206',
12 changeOrigin: true,
13},
14'/sse': {
15 target: 'http://localhost:51206',
16 changeOrigin: true,
17},Source: web/vite.config.ts
设计意图:changeOrigin: true 使代理请求的 Host 头改写为目标地址,避免后端对 Host 校验失败;四个前缀统一指向同一后端,保证前端代码在任何模式下都使用相对路径请求(如 /rpc、/sse),无需感知自身运行形态——这是 LAN 模式"零配置切换"的关键。
示例二:WebView 桥接类型声明(嵌入模式与 LAN 模式的差异点)
1// web/src/types/webview.d.ts(节选)
2postMessageWithAdditionalObjects(message: any, additionalObjects: object[]): void
3addEventListener(event: 'message', handler: (event: MessageEvent) => void): void
4removeEventListener(event: 'message', handler: (event: MessageEvent) => void): voidSource: web/src/types/webview.d.ts
这段声明定义了嵌入模式下的原生桥。LAN 模式下这些 API 不存在(浏览器环境),前端功能必须全部落在 HTTP 端点上;识别"是否处于嵌入环境"以及相应的降级逻辑应基于该类型存在性判断。
示例三:设置 API 的消费方式(跨模式共用的调用面)
1import { adbModeApi, type DiscoveredAdbDevice } from '@/features/settings/api'
2
3const devices = ref<DiscoveredAdbDevice[]>([])
4
5const getDeviceDisplayName = (device: DiscoveredAdbDevice): string => {
6 if (device.kind === 'mumu') return t('settings.adbMode.targetDevice.deviceKind.mumu')
7 // ...
8}Source: web/src/components/AdbDeviceInput.vue
这类 xxApi 调用是 LAN 模式下所有交互的统一出口。由于请求使用相对路径,无论前端由后端托管(LAN)还是由 Vite 代理(开发),调用代码完全一致——无需修改任何一行前端代码即可切换访问形态。
配置选项(Configuration Options)
| 选项 / 项 | 类型 | 默认值 / 位置 | 说明 |
|---|---|---|---|
| 后端服务端口 | number | 51206 | C++ 后端 HTTP 服务端口,前端所有端点前缀的目标地址(AGENTS.md) |
/rpc 代理 | proxy rule | target: http://localhost:51206 | RPC 调用代理,changeOrigin: true(vite.config.ts L22-25) |
/static 代理 | proxy rule | target: http://localhost:51206 | 静态资源代理(vite.config.ts L28-31) |
/downloads 代理 | proxy rule | target: http://localhost:51206 | 下载端点代理(vite.config.ts L33-36) |
/sse 代理 | proxy rule | target: http://localhost:51206 | SSE 事件流代理(vite.config.ts L38-41) |
ui.webTheme.mode | string(设置项) | 见设置存储 | 界面主题模式,main.ts 对其建立响应式监听(main.ts) |
说明:仓库中未发现可配置的 LAN 监听地址(如
host/0.0.0.0)设置项。端口51206是仓库文档与代理配置中唯一可验证的服务地址。
API 参考(API Reference)
WebView 桥接 API(window.chrome.webview,仅嵌入模式可用)
以下签名摘自 web/src/types/webview.d.ts:
postMessageWithAdditionalObjects(message: any, additionalObjects: object[]): void- 参数:
message为序列化消息体;additionalObjects为附带的原始对象(如文件句柄) - 返回:无
- 说明:嵌入模式下前端 → 原生的消息通道;LAN 模式下不可用
- 参数:
addEventListener(event: 'message', handler: (event: MessageEvent) => void): void- 监听原生 → 前端的消息
removeEventListener(event: 'message', handler: (event: MessageEvent) => void): void- 移除消息监听,用于组件卸载时清理
前端内部 API 面(web/src/features/settings/api)
adbModeApi:ADB 模式相关 RPC 调用集合,被AdbDeviceInput.vue等组件消费(引用位置)DiscoveredAdbDevice(类型):已发现设备描述,含kind字段(如'mumu')等判别字段(用法示例)
该模块的完整方法签名未在本次源码探索预算内读取,此处仅记录已验证的导出成员与消费方式。完整定义请直接查阅
web/src/features/settings/api。
失败模式、边界情况与并发(Failure Modes, Edge Cases & Concurrency)
- WebView 桥能力缺失:LAN 浏览器环境没有
window.chrome.webview。任何依赖postMessageWithAdditionalObjects的功能在 LAN 模式下会失败;从架构看,等效能力需通过/rpc端点提供。前端可通过检测该对象是否存在实现能力降级。 - SSE 连接生命周期:
/sse是长连接。LAN 环境下浏览器与后端之间可能经过更多网络跳数,连接更易中断;useRpc.ts的 Composable 封装负责事件监听的建立与清理(见其文档注释),组件卸载时应确保监听被移除,避免重复订阅与内存泄漏。 - 并发写一致性:多设备同时通过 LAN 访问时,
/rpc写设置与/sse事件推送构成"命令 + 广播"模型。设置变更后主题等 UI 状态依赖main.ts中的响应式 watch 同步;其他客户端需通过/sse收到变更事件才能刷新本地缓存视图(事件推送机制细节属 RPC/SSE 通信页面范围)。 - 端口占用 / 目标不可达:开发模式下若后端未启动,所有代理目标
localhost:51206不可达,页面表现为 RPC 失败。LAN 模式下该风险同样存在——后端是唯一资源与数据来源。
性能与运维注意事项(Performance & Operational Notes)
- 单一端口、无 CDN 依赖:前端资源与数据全部来自
:51206,LAN 内首次加载的资源体积受构建产物大小影响;/static前缀的缓存策略由后端决定(后端实现超出本页范围)。 - SSE 与代理超时:开发模式下
/sse经 Vite 代理转发,需确认代理对长连接不设置过短超时;LAN 直连则无此层代理,直接由浏览器与后端维持连接。 - 防火墙 / 端口放行:LAN 访问要求操作系统放行
51206端口入站。仓库中未发现自动配置防火墙的代码,属部署/运维事项。 - 构建工具链:项目使用 C++23(xmake.lua)与 pnpm workspace 管理前端依赖;前端构建产物需由后端托管后才可被 LAN 访问。
扩展点(Extension Points)
- 新增 LAN 可用能力:新增功能若要同时服务嵌入与 LAN 两种形态,应实现为
/rpc端点上的方法(前端通过相对路径调用),而不是只走 WebView 桥。features/settings/api中的xxApi模式(见使用示例三)是可复用的封装范式。 - 事件订阅扩展:新的运行时事件可通过
/sse通道广播,前端使用composables/useRpc.ts的事件监听 Composable 消费,保持与现有设置同步机制(main.tswatch)一致的架构风格。 - 多语言 UI:
useI18nComposable(t('...')调用,见示例三)承担文案本地化;新增 LAN 场景专属提示时遵循同一 key 规范。
相关链接(Related Links)
- AGENTS.md — 仓库级架构说明:web 代理与 docs 独立性
- web/vite.config.ts — 四类端点的代理规则
- web/src/types/webview.d.ts — WebView 嵌入桥类型声明
- web/src/composables/useRpc.ts — RPC 事件监听 Composable
- web/src/main.ts — 主题模式响应式同步
- web/src/components/AdbDeviceInput.vue — 设置 API 消费示例