# 75 · 插件评估新增「托管友好性」与「资源成本」两个维度(2026-09-12) - 日期:2026-09-12 - 状态:📋 **标准已立(本档只立标准,未改预检脚本)** - 触发:`dsh-univer-office` 静态预检判 `ok` 却在平台上完全不可用,且加载成本 **+65.1 MiB** → 用户提出「把这个作为插件评估和改造的一个点」 - 范围:**评估维度**(候选池导入预检)+ **改造规范**(自研 / 改造插件) - 关系:**接续档案 71**(插件兼容性预检:依赖范围 + 导出符号),本档是**新增维度**,不改变其机制与分级语义 --- ## 一、为什么需要(案例暴露的两个盲区) `dsh-univer-office` 在**档案 71 的静态兼容性预检里判 `ok`**(依赖与导出符号全满足)——**但它在平台上完全不可用**: | 预检现状 | 案例暴露的盲区 | |---|---| | 查 semver 依赖范围 | ✅ 通过(依赖没问题) | | 查运行时导出符号 | ✅ 通过(符号都在) | | ❌ **不查「它假设的部署形态」** | 把 `http://127.0.0.1:${port}` 交给浏览器 → **托管平台必然失效,且平台侧无法补救** | | ❌ **不查资源成本** | 加载 `rss` **+65.1 MiB**(实测)= 实例配额的 1/6 | **⇒ 兼容 ≠ 可用。** 需要补两个维度。 --- ## 二、新维度 A:托管友好性(Hostability) ### 判据(一句话) > **插件的一切对外交互,是否都能走「平台已有的那一条入口」?** > 即:**浏览器侧只用相对路径;实例侧不依赖本机 loopback 上的网络服务。** ### 检查项 | ID | 检查 | 检测方法(复用档案 71 的 `walkJs` 框架) | 等级 | |---|---|---|---| | **H1** | bundle 里出现硬编码 loopback 服务地址 | 文本扫描 `127.0.0.1:` / `localhost:` | 🟡 出现即警告 | | **H2** | **client bundle 里出现绝对 URL**(`http://` 字面量) | 只扫 client 侧产物;命中即「会交给浏览器」 | 🔴 **阻断** | | **H3** | 自建网络监听且非 unix socket | 扫描 `listen(` / `createServer` 参数形态 | 🟡 需人工确认是否 loopback-only | | **H4** | 交付浏览器的 URL 由 Host 生成,还是客户端自行拼接 | 人工研判(架构文档 + client 代码) | 🔴 若客户端自拼绝对地址 | **为什么 H2 是阻断级**:绝对地址一旦交给浏览器,在托管平台上**必失效**——跨机器(`127.0.0.1` 指向用户本机)+ HTTPS 混合内容会被浏览器硬拦,**且平台侧无补救手段**(不能靠配置、不能靠反代)。这正是本案例的死因。 ### 反面样板(本案例实测) ```js viewerUrl = `${gateway}/?file=...` // ← 交给浏览器的绝对 URL gateway = `http://127.0.0.1:${port}` // ← 硬编码 loopback,且被浏览器 iframe 使用 ``` **两条独立阻碍,缺一都打不开**: 1. **Host → Gateway**:实例内进程 uid 100002 → `127.0.0.0/8` 被 nft `reject with tcp reset` 2. **Browser → Gateway**:绝对 loopback URL,浏览器在用户电脑上 → **即使解封第 1 条也打不开** ### 正面样板(同一插件内) - **Client**(浏览器模块):只走 `/univer-api/*` 同域相对路径 → ✅ 平台可反代 - **平台自研 ×3**:加载成本 **0.0 MiB**、无自建监听 → ✅ --- ## 三、新维度 B:资源成本(Resource Cost) | 项 | 判据 | 方法 | |---|---|---| | **加载内存** | 插件 `import` 后的 `rss` 增量 | 隔离 cgroup 内逐个 `import` 实测(`node --expose-gc`,`MemoryMax` 兜底) | | **标注阈值** | **> 30 MiB → 标注**;**> 60 MiB → 强提示** | 同上 | ### 实测基线(guest 实例,2026-09-12) | 插件 | rss 增量 | |---|---| | `dsh-univer-office` | **+65.1 MiB** | | `libsql`(间接依赖) | +7.8 MiB | | 平台自研 ×3(portal-entry / workspace-scoped-picker / business-plugins) | **0.0 MiB** | | `puppeteer-core`(仅 import 主入口、未启动浏览器) | **0.0 MiB** | **⇒ 成本由「装了什么」决定,不是「装了几个」** —— 一个重型插件 ≈ 无穷多个轻量插件。 **⇒ 平台自研插件的 0.0 MiB 是正面基线**,应作为自研插件的验收参考。 --- ## 四、改造规范(自研 / 改造插件必须满足) | # | 规范 | 理由 | |---|---|---| | **R-a** | **对外入口唯一**:浏览器可达面全部挂在 Host 的 webServer(同域相对路径) | 复用平台反代,零新增暴露面 | | **R-b** | **内部通信走 IPC**:unix domain socket 或 stdio,**不用 TCP loopback** | 不经 IP 层 ⇒ nft 管不到;天然无端口冲突 | | **R-c** | **不新增监听端口** | 多租户同主机下,端口 = 跨租户风险 + 端口漂移 | | **R-d** | **重型依赖懒加载**:原生绑定、引擎等改 `await import()` | 实测有效:`puppeteer-core` 仅 import 主入口 = **0.0 MiB** | | **R-e** | 交付浏览器的地址**一律由 Host 生成且为相对路径** | 绝对地址在托管平台必失效 | --- ## 五、落地方式(**未实施,待排期**) | 步 | 动作 | 落点 | |---|---|---| | 1 | 兼容性预检加 **H1/H2** 静态扫描(复用 `walkJs`) | `dsh-server-docs/scripts/plugin-compat-check.mjs` | | 2 | H2 命中 → 判 `blocked`,理由「向浏览器交付绝对地址,托管平台不可用」 | 同上(沿用档案 71 的分级语义) | | 3 | 资源成本:预检阶段先做**静态体积提示**(解包后 `artifacts/` 体积),精确值留隔离实测 | 同上 | | 4 | 自研插件按 §四 规范执行(**T03 的 MCN suite 起适用**) | `交接单/T03` | > ⚠️ 本档**只立标准**。改预检脚本属**生产代码变更**,须另立交接单执行(**规划与执行分离**)。 --- ## 六、与既有档案的关系 | 档案 | 关系 | |---|---| | 71(兼容性预检:导入/上传即判定) | **本档是其新增维度**,机制与分级语义沿用,不替换 | | 66(业务插件 P0 误报 → admin 显式信任) | 本档 🟡 级沿用「放行 + 标注 + admin 可见」口径 | | 70(anysearch 不兼容致崩溃循环) | 同属「预检盲区导致线上事故」,本档是**系统性补盲** | | 74(实例内存治理:V8 堆限 256→160) | §三 资源成本维度与 74 的配额治理同源 | --- ## 七、决策记录 - **2026-09-12** · 用户提出「把这个作为插件评估和改造的一个点」→ 本档立标准 - **待定**:是否立即实现 §五 步骤 1–3(需另立交接单)