Appearance
ADR-075: 模型广场 · 网页浏览(内嵌代理 + 外链闭环)
状态: 已采纳 · 已实施(Phase 1 基础代理 + 导航接入) 日期: 2026-07-09 关联: ADR-003(下载策略,方案 C 已实施)、ADR-011(Wails 版本策略) 增强: ADR-077(Cookie 中继·登录态)、ADR-078(下载拦截·一键入库) 来源: 用户需求 2026-07-09;参考
ysm-model-manager的「创意工坊」实现
背景
用户希望在 MikuMikuAR 内浏览模型资源站,复刻 ysm-model-manager 的「创意工坊」体验(内嵌 iframe + Go 本地反向代理突破 X-Frame-Options)。
ADR-003 方案 D(内嵌 WebView + 反向代理)曾因「SPA 全链路代理工程量大易失效」被否决,转用方案 C(系统浏览器 + fsnotify 落库)。但 ysm-model-manager 实际用双路规避了方案 D 的失效点:
- 免登录的展示型站点(Pixiv / Booth / 作者页)走内嵌代理;
- 需登录的强 SPA(模之屋)走系统浏览器外链,保留登录态与临时签名 URL。
本项目已有方案 C 的 fsnotify 落库闭环(internal/app/watch.go + DownloadWatchDir),可无损复用。两条路在「落库」处会师,无需重复造轮子。
决策
采用双路模型(联邦架构,见对话中的接入架构图):
| 模式 | 流量 | 站点类型 | 登录态 |
|---|---|---|---|
| A · 内嵌 | Go 反向代理 → iframe | 免登录展示站 | 不需要 |
| B · Wails 预热窗口 | NavigatePlazaWindow → 预热单实例 WebView2 窗口 | 需轻量隔离的站 | 共享 cookiejar |
| C · 外链 | Browser.OpenURL → 系统浏览器 | 需登录 SPA | 保留 |
B 模式优化(2026-07-11):原
OpenPlazaWindow每次调用NewWithOptions创建新 WebView2 renderer 进程,冷启动 1–3s。改为SetWailsApp时创建隐藏预热窗口(plaza:prewarmed),用户点击时SetURL + Show,打开延迟降至 ~200ms。RegisterHook(WindowClosing)拦截关闭事件 →Cancel + Hide,窗口复用而非销毁。
落库闭环:无论哪条路下载的 zip,落入用户 Downloads/ 目录(首启自动监听,详见 ADR-003 方案 C),由 fsnotify 监听 → Magic Number 校验 → ImportLocalFile 落库。
Wails v3 适配要点
- 绑定生成:
frontend/bindings/由npm run generate:bindings(wails3 generate bindings -ts -i -d frontend/bindings ./...)自动生成,含全部export function包装与 FNV-1a method ID(生成器自动算,无需手写)。新增/删除 Go 方法后重跑生成器即可,禁止手维护bindings/下 .ts。契约测试app.contract.test.ts动态校验导出函数存在性 + FNV-1a ID(仅一致性护栏,非手写依据)。 - 下载钩子:v3 WebView2 是否暴露下载事件未验证;内嵌模式内的下载先引导用户用「在浏览器打开」切到外链(模式 B),不依赖 webview 下载拦截。
- Android:本期不做(用户确认桌面优先)。
proxy.go的反向代理逻辑保持 cgo-free,未来 Android 端可直接复用。
技术实现
- Go 新增
internal/app/proxy.go:StartProxy(target string) (string, error):启动httputil.ReverseProxy,返回http://127.0.0.1:<port>/本地 URL;剥离响应头X-Frame-Options与 CSP 的frame-ancestors;改写页面内相对 URL / 重定向Location为代理绝对 URL。StopProxy():关闭并清理代理 server。- 复用
zipextract.go的a.httpSrvMu/httpServerInfo/shutdownServers框架挂载生命周期。
- 站点元数据:首版用前端常量
frontend/src/menus/plaza-sites.ts({ name, url, mode: 'embed'|'window'|'external' },三模式独立、无自动选路)。2026-08-01 演进(见 ADR-224):站点/创作者数据改为「GitHub 远程配置(creators.json+workshop_sites.json,三源 fetch)+ Go 用户目录缓存(plaza-cache/)+ 前端内置常量兜底」三层结构;内置三站 id 去plaza:前缀与远程站统一,经mergeSites按 id 融合;SavePlazaConfig/GetCachedPlazaConfig绑定负责持久化,不再经 CWD 相对路径。 - 前端新增
frontend/src/menus/plaza.ts:全屏#webviewLayer视图层(非 SlideMenu 弹窗),根级卡片网格列站点;内嵌站启动反向代理 + iframe,外链站直接Browser.OpenURL。 - 入口绑定:
frontend/src/core/main.ts用dom.btnPlaza.addEventListener('click', ...)+Ctrl+7快捷键接入。 - 事件:无新增前端事件;
StartProxy同步返回本地 URL。
涉及文件
| 文件 | 改动 |
|---|---|
internal/app/proxy.go | 新增 StartProxy / StopProxy |
frontend/src/menus/plaza.ts | 新增菜单 + iframe 面板 |
frontend/src/menus/plaza-sites.ts | 站点常量 |
frontend/src/core/main.ts | 绑定入口(toggleOverlay) |
frontend/bindings/mikumikuar/internal/app/app.ts + models.ts | 自动生成(npm run generate:bindings) |
frontend/src/core/wails-bindings.ts | 手维护(model 类型登记聚合层,生成器只写 bindings/、不碰 src/) |
frontend/src/__tests__/bindings/app.contract.test.ts | 动态计数(Object.keys),Go 方法增删后重跑生成器即自动同步,无需手改期望值 |
风险与限制
- 站点反爬 / 动态 API:内嵌代理仅用于相对静态的展示站;登录站一律外链,规避 cookie / 签名 URL 跨域代理失效。
- HTTPS 重定向改写:
ReverseProxy的ModifyResponse需改写Location与页面内相对路径,细节需实测。 - CSP 残留:部分站点 CSP 含
script-src限制,iframe 内 JS 可能仍受限;首版接受,必要时扩展头改写。
后续增强(已拆分 ADR)
| ADR | 内容 | 优先级 |
|---|---|---|
| ADR-077 | Cookie 中继 — 代理层维护 cookiejar,解锁登录态站点内嵌 | P0 |
| ADR-078 | 下载拦截 — 注入 JS 拦截下载链接,Go 端直接入库 | P0 |
| ADR-087 | 浏览器体验增强路线图 — window 模式下载拦截(代理桥接)/ 导航控制 / URL 追踪 / 下载进度 / 拖放导入 / 键盘快捷键 | 规划中 |