Web 跨端深挖:SSR、Coding、兼容与发布治理

这篇承接 PixUI、JSB 与构建发布,继续回答 SSR/BFF、匹配配置、Coding 三仓、WebView 兼容、多媒体和发布治理。面试表达要始终落在“嵌入式 Web 如何成为可靠客户端模块”,而不是罗列前端框架。

面试官:你这段 Web 工作的技术全景是什么?

30 秒回答

我做的是游戏内跨端 Web 工程,分三条线:PixUI 承接端内高频页面,Next.js SSR/BFF 承接配置与运营页面,Blockly 内核、积木工厂和 IDE 承接可视化编程。我的职责覆盖页面业务,但更核心的是原生宿主通信、登录与环境、移动端适配、弱网退出路径、音视频生命周期,以及多仓构建发布治理。

90 秒回答

这类系统的复杂度来自它同时处在浏览器、游戏宿主和后端之间。页面既要处理 React 状态和接口,又不能假设自己运行在标准 Chrome;进入、关闭、重连、安全区、前后台和输入焦点都受宿主管理。SSR 首屏失败时,用户仍然需要原生可控的退出路径;WebView 内核较旧时,要做能力检测和降级;视频关闭或页面复用时,要显式停止播放和释放监听。

工程侧还有多仓依赖。Coding 的内核、积木工厂和 IDE 不能各自随意发布,必须明确兼容版本和制品身份;多赛季分支、流水线变量和人工审核也要可追踪、可回滚。所以我会把这段经历定义成跨端客户端工程,而不是“写过一些 Web 页面”。

面试官:SSR/BFF 在游戏业务里解决什么?

直接回答

SSR 解决首屏内容、分享落地和服务端预取;BFF 负责面向页面聚合后端接口、转换协议、注入环境与登录上下文。它们减少页面直接理解多个游戏服务的成本,但不能替代原生宿主,也不能成为客户端核心退出能力的唯一承载者。

flowchart LR
    A["游戏客户端 / 浏览器"] --> B["Next.js 页面"]
    B --> C["SSR / API Routes / BFF"]
    C --> D["登录与角色服务"]
    C --> E["地图 / 配置 / 活动服务"]
    B <--> F["Native Bridge"]
    F --> G["关闭 / 安全区 / 进入房间"]

状态分层

状态权威位置原因
页面表单与临时筛选Web 页面生命周期短,可重新构建
登录 Token 与角色上下文认证链路/BFF,页面只持受限凭证避免各页面自行解释身份
匹配配置持久数据业务服务端需要权限、版本和并发校验
页面关闭与宿主会话原生客户端SSR 失败时仍必须可操作
环境和接口路由配置与构建产物不能散落硬编码

面试官:SSR 页面如何处理登录态和多端环境?

请求进入服务端时,从 Cookie、Header 或受控参数恢复身份,再把必要的用户上下文传给页面。服务端到业务接口的请求由统一请求层处理环境、超时、错误码和日志,不能让每个页面自己拼域名与 Token。

需要区分三种环境:

标准浏览器:OAuth / Cookie / URL 回跳
游戏 WebView:宿主注入上下文 + Native Bridge
桌面工具:OAuth 完成后通过受控 deep link 回传

敏感凭证不应进入可分享 URL;SSR 输出也不能把服务端秘密序列化给浏览器。页面水合后,要能识别登录失效并进入统一重登或关闭流程。

面试官:SSR 失败或弱网时怎么恢复?

核心结论

错误页也必须属于会话状态机。即使 HTML 没有成功加载,关闭、返回和重试仍要有宿主级兜底。

Loading
  -> Ready
  -> RecoverableError -> Retry
  -> AuthExpired      -> Reauth
  -> FatalError       -> NativeClose

我处理过断线进入配置页后出现 502,用户无法退出或重连的问题。直接证据能证明我修复了错误态下对话框和移动端布局/交互,使退出入口可用;不能夸成我独立重构了整个网络恢复框架。

如果从架构上补全,应该由原生宿主提供独立于页面内容的超时和关闭层;Web 页面在成功启动后上报 ready,宿主超时未收到就展示原生重试/退出;业务请求再按是否幂等决定自动重试,保存类请求遇到结果未知则先查询状态,不能盲目重复提交。

面试官:匹配配置页面有什么技术难点?

它不是普通表单,而是把复杂玩法配置转成受约束的服务端协议。客户端要处理动态阵营、人数、开关、范围、条目上限、编辑/新建模式和提交反馈。

推荐的数据流是:

Server DTO
  -> normalize + default values
  -> Form Model
  -> synchronous field validation
  -> cross-field validation
  -> submit immutable snapshot
  -> server validation + version check
  -> refresh authoritative data

页面侧负责可用性和尽早提示,服务端仍需做完整校验。提交时冻结表单快照并禁用重复操作;成功后以服务端返回重建,不把本地乐观值当最终权威。

移动端问题主要来自固定设计稿缩放、安全区和内容溢出。我实际处理过编辑 UI 超框、新建后无法退出、条目上限没有拦截和旧设备显示不全。适配时要同时看可用视口、安全边距、横竖屏变化、最长文本和软键盘,而不只是写几个媒体查询。

面试官:Coding 为什么分成三个仓?

直接回答

三仓对应不同演进节奏:Blockly 内核提供工作区、事件、序列化和生成器;积木工厂把领域积木生产成定义、工具箱和生成规则;IDE 负责模板、项目、编辑体验、预览及客户端接入。

flowchart LR
    A["Blockly 内核<br/>workspace / event / serialization"]
    B["积木工厂<br/>block schema / generator / assets"]
    C["Coding IDE<br/>project / template / preview / bridge"]
    D["Lua / 中间产物"]
    E["游戏脚本运行时"]

    A --> B
    A --> C
    B --> C
    C --> D
    D --> E

拆仓带来独立演进,也带来版本组合风险。因此发布不能只写“取最新版”:IDE 应锁定内核和积木包版本,制品带 commit、branch、build ID 和协议版本;积木 Schema 改动要有迁移或兼容策略。

面试官:Blockly 生成 Lua 就结束了吗?

没有。生成代码只是中间一步,完整链路还包括:

  1. 积木连接的类型与作用域校验。
  2. 对象引用从编辑器身份映射到稳定运行时身份。
  3. 生成结果的格式化、错误定位和 source map/积木定位。
  4. 脚本 API 的版本、权限与沙箱。
  5. 运行时异常、超时、日志和网络权威校验。

我的直接工作可以讲三仓构建、模板和兼容接入;完整 Lua VM、安全沙箱及所有领域 API 不能说成我个人实现。

面试官:如何治理低版本 WebView 兼容?

一句话结论

按能力检测决定是否使用某个 API,系统版本只能作为辅助信息。

const canUseFeature = typeof globalThis.SomeApi === 'function';

if (canUseFeature) {
  useFastPath();
} else {
  useCompatiblePath();
}

兼容治理分四层:

层级做法
构建语法明确浏览器目标,转译语法并按需补 polyfill
运行时 APIfeature detection,提供降级或禁用路径
布局与输入真机覆盖安全区、键盘、触摸、焦点和横竖屏
宿主协议协议版本与能力握手,旧客户端不调用新能力

我处理过低版本内核不支持接口的问题,也处理过旋转后视口尺寸滞后和 position: fixed 错位:先响应 resize,再延迟重算根字号与安全边距,并在卸载时恢复全局样式。这个案例说明 WebView 适配既有浏览器兼容,也有客户端生命周期问题。

面试官:PC 和移动端响应式怎么做?

不要只按 UA 判断设备。页面先读取可用视口与宿主安全区,再决定宽、高或 fit 缩放基准;固定比例画布和信息流页面采用不同策略。

宽基准:列表、表单,横向铺满,纵向滚动
高基准:固定比例交互画布
fit:内容必须完整可见,允许一侧留白

全局根字号、CSS 变量和事件监听都要在页面卸载时还原,否则 WebView 复用后会污染下一个页面。软键盘弹出时还要区分真实旋转和 visual viewport 变化,避免输入框被错误缩放。

面试官:音视频为什么容易出线上问题?

视频不仅是一个组件,还涉及资源策略、播放器内核、前后台、音频焦点和页面复用。

Created -> Loading -> Playing -> Paused -> Ended
                     |             |
                     +-> Background+
                     +-> Unmount -> Released

关闭弹窗、切页签、路由卸载和应用切后台都应触发明确的暂停或释放;恢复前台时只有原先处于播放态的实例才能自动恢复。事件监听、定时器和播放器实例必须成对清理,否则会出现关窗后仍有声音、再次轮播杂音或多个页面抢音频焦点。

资源侧要明确网络流、预下载和缓存的选择。实际项目里针对视频改为默认网络流播放;面试时只能说做过该策略调整和具体故障治理,不能在没有指标时宣称节省多少磁盘或首播耗时。

面试官:Web 发布治理怎么设计?

直接回答

发布对象不是“某个分支当前内容”,而是可验证、不可变、可追踪的制品。

source commit
  -> dependency lock
  -> build + tests
  -> artifact + manifest + hash
  -> environment approval
  -> publish pointer
  -> monitor / rollback pointer

多赛季并行时,分支名、目标环境、应用 ID 和版本都应是不同字段。流水线变量必须语义明确,不能复用一个 version 同时表示赛季、包版本和制品版本。我实际处理过监听目标分支、分支可选、人工审核、流水线链接与变量歧义等问题。

回滚应该切换到历史不可变制品,而不是临时重新构建旧分支。重新构建可能拿到不同依赖、环境变量或工具版本,产物不再等价。

面试官:怎么证明你不是只会写页面?

背诵版

我当然做业务页面,但我解决的问题跨过了页面边界。向下要和 C++/Lua 宿主处理 JSB、生命周期、输入、安全区和制品;横向要治理多应用共享 SDK、Coding 三仓和兼容版本;向上要理解 SSR/BFF、登录态、错误恢复与发布。像 502 页面无法退出、低版本 WebView API 不兼容、旋转后 fixed 错位、关窗后仍播放音频,本质都是跨运行时生命周期问题。这些工作仍然是客户端工程,只是 UI 技术栈换成了 Web。

追问边界

可以直接说应限定后再说当前不能说
负责过 SSR 页面、匹配配置、WebView 适配和发布流水线治理参与 BFF/API 聚合与登录接入独立负责所有 Web 后端服务
做过 Coding 三仓的构建、模板、兼容和发布工作理解 Blockly 到 Lua 运行链独立设计全部积木和脚本 VM
修复过弱网错误态下无法退出的问题架构上主张宿主提供原生兜底已经完成全链路断线恢复重构
做过音视频生命周期和流播放策略调整可以说明应采哪些指标没有数据时声称具体性能收益

一分钟收口

这段工作的核心是把 Web 作为游戏客户端中的一个受管理运行时。页面业务只是表层,底下还要有宿主协议、身份与环境、失败恢复、兼容矩阵、音视频生命周期和可追踪制品。SSR、PixUI 和 Coding 的技术形态不同,但我用同一套原则处理:明确状态权威,协议版本化,页面销毁时回收资源,失败时保留原生逃生通道,发布时以不可变制品而不是分支名称为准。