日期: 2026-02-27
定位: 基于现有微前端架构,面向海运业务后台系统的性能与稳定性系统化治理方案
关联文档: 架构质量评估报告 | Dev 性能优化 | 埋点方案
一、业务场景与挑战
1.1 海运业务特征
| 特征 | 技术挑战 |
|---|
| 集装箱订单量大 | 单表 10w+ 行,列表页滚动/筛选/导出性能敏感 |
| 多模块协作 | 7+ 子应用(单证/箱管/业财/营销/EDI/运营/IBS),频繁跨模块切换 |
| 表单复杂 | 提单、订舱单字段 100+,动态联动、校验规则复杂 |
| 全球化部署 | 多时区、多语言、多租户,CDN 分发与接口延迟敏感 |
| 操作人员日均高频 | 单人日操作 500+,页面切换秒级响应是基本要求 |
| 数据准确性要求极高 | 运费计算、关务合规零容错 |
1.2 当前架构现状
技术栈: Vue 3.5 + Vite 7 + TypeScript 5 + Wujie 微前端 + Pinia + Element Plus
Monorepo: pnpm workspace + Turborepo
共享包: 12 个 @cmclink/* packages
子应用: 7 个业务子应用(iframe 沙箱隔离)
已完成的治理(架构质量评估综合 4.5/5):
- 子应用接入成本极低(3 步 5 API)
- Dev 模式 327 请求/6s → ~150 请求/2-3s
- 主应用壳层精简(AuthenticatedLayout 400+ → ~200 行)
- 死代码清理(DICT_TYPE 100 → 5 个成员)
- 可插拔埋点适配层(TelemetryAdapter)
二、治理目标与核心指标
2.1 性能 SLO(Service Level Objectives)
| 指标 | 当前估值 | 目标值 | 衡量方式 |
|---|
| 主应用首屏 FCP | ~2s (dev) | ≤ 1.5s (prod) | Lighthouse / RUM |
| 子应用首次进入 TTI | ~3-4s | ≤ 2s | 埋点 child_before_load → child_after_mount |
| 子应用二次切换 | ~0.5-1s | ≤ 300ms | 保活模式下 wujie activated |
| 大表格渲染(1w 行) | 卡顿 | ≤ 500ms 可交互 | 虚拟滚动 + Performance API |
| 复杂表单首次渲染 | ~800ms | ≤ 400ms | 异步分片渲染 |
2.2 稳定性 SLO
| 指标 | 目标值 | 衡量方式 |
|---|
| JS 错误率 | ≤ 0.1% PV | 全局 onerror + Vue errorHandler |
| 接口成功率 | ≥ 99.5% | Axios 拦截器统计 |
| 子应用加载成功率 | ≥ 99.9% | 埋点 + 重试机制 |
| 白屏率 | ≤ 0.05% | MutationObserver + 心跳检测 |
| 页面崩溃率 | 0 | Service Worker 兜底 |
三、秒开性能治理体系
3.1 分层加载策略
┌──────────────────────────────────────────────────────┐
│ 用户首次访问 │
│ │
│ ① 主壳秒出(静态 HTML + 骨架屏) │
│ ② 认证 + 菜单数据并行拉取 │
│ ③ 当前子应用按需加载(exec: true 已预执行) │
│ ④ 非当前子应用空闲预加载 │
└──────────────────────────────────────────────────────┘
3.1.1 主壳秒出(已具备 + 待增强)
| 策略 | 状态 | 说明 |
|---|
| HTML 内联关键 CSS | ⏳ 待实施 | 首屏骨架屏样式内联到 index.html,无需等 CSS 文件加载 |
| Vite 预构建 | ✅ 已实施 | optimizeDeps.include 覆盖 20+ 核心依赖 |
| 资源预热 | ✅ 已实施 | server.warmup 预转换关键入口文件 |
| 路由懒加载 | ✅ 已实施 | views 按路由 () => import() 分割 |
| DNS 预解析 | ⏳ 待实施 | 子应用域名 <link rel="dns-prefetch"> |
3.1.2 子应用预加载(已具备,架构优势)
当前 wujie.ts 中的预加载机制是性能核心:
setupApp({ name, url, exec: true, fiber: true, props: getSharedProps() })
preloadApp({ name })
增强方向:
| 增强项 | 方案 | 预期收益 |
|---|
| 分级预加载 | 高频子应用(doc/ibs-manage)立即预加载;低频子应用(EDI/MKT)空闲时预加载 | 首屏加载减少 ~30% 并发资源 |
| 预加载调度 | 使用 requestIdleCallback 在主线程空闲时触发低优先级预加载 | 避免预加载阻塞用户交互 |
| 预加载监控 | 对每个子应用预加载耗时埋点,建立基线 | 发现预加载退化 |
3.1.3 资源加载优化(Prod 构建链路)
| 策略 | 实施方案 | 预期收益 |
|---|
| 代码分割 | Vite manualChunks 按路由 + 第三方库分割 | 首屏 JS 减少 40%+ |
| Tree-shaking | Element Plus 按需(已有)+ lodash-es(已有) | 减少未使用代码 |
| Gzip/Brotli | Nginx 开启 br 压缩(优先于 gzip) | 传输体积减少 70%+ |
| CDN 静态资源 | JS/CSS/字体/图片上 CDN,base 配置 CDN 域名 | 全球访问延迟 < 100ms |
| HTTP/2 Server Push | 关键资源(vendor.js/app.css)服务端推送 | 消除请求等待 |
| 资源版本化 | 文件名 hash + 强缓存 1 年 + index.html 不缓存 | 重复访问零下载 |
3.2 大数据量场景优化
这是海运业务系统的核心痛点 —— 集装箱订单列表动辄万级数据。
3.2.1 表格虚拟滚动
┌─────────────────────────────────────┐
│ 可视区域(~20 行) │ ← 只渲染这些 DOM
├─────────────────────────────────────┤
│ │
│ 缓冲区(上下各 5 行) │ ← 平滑滚动缓冲
│ │
├─────────────────────────────────────┤
│ │
│ 未渲染区域(占位高度) │ ← 仅 padding/transform
│ │
└─────────────────────────────────────┘
实施方案:
| 层级 | 方案 | 适用场景 |
|---|
| 组件级 | @cmclink/ui 的 CmcTable 集成 el-table-v2(Element Plus 虚拟表格) | 标准列表页 |
| 高级场景 | 结合 @tanstack/vue-virtual 实现自定义虚拟滚动 | 复杂自定义布局 |
| 性能基线 | 10,000 行 × 20 列,滚动帧率 ≥ 55fps | 验收标准 |
3.2.2 分页与无限加载
| 策略 | 说明 |
|---|
| 服务端分页 | 默认 pageSize=20,用户可调 50/100,禁止全量拉取 |
| 光标分页 | 订单流水等时序数据使用 cursor-based pagination,避免 offset 性能劣化 |
| 搜索降级 | 模糊搜索 > 500ms 无结果时,自动切换为精确搜索提示 |
3.2.3 大表单性能
| 策略 | 说明 |
|---|
| 分片渲染 | 100+ 字段表单分 Tab/Collapse 区域,首屏只渲染第一个区域 |
| v-memo | 静态区域(如收货人地址块)使用 v-memo 避免重复渲染 |
| computed 缓存 | 联动计算(运费 = 基价 × 数量 × 汇率)使用 computed 避免重复计算 |
| 防抖校验 | 输入框校验 debounce 300ms,避免每次击键触发远程校验 |
3.3 运行时性能守卫
3.3.1 内存管理
| 问题 | 方案 |
|---|
| 子应用内存泄漏 | wujie iframe 沙箱天然隔离;子应用 deactivate 时清理 EventListener/Timer |
| Pinia Store 膨胀 | 大列表数据不存 Store,仅组件级 ref 持有;Store 只存 session 级状态 |
| DOM 节点过多 | 虚拟滚动限制 DOM < 500 节点;关闭的 Tab 对应的子应用 deactivate |
| 长列表图片 | loading="lazy" + IntersectionObserver,可视区外不加载 |
3.3.2 网络请求优化
| 策略 | 实施位置 | 说明 |
|---|
| 请求去重 | @cmclink/api Axios 拦截器 | 相同 URL + 参数 500ms 内只发一次 |
| 请求取消 | 路由切换时 AbortController.abort() | 避免已离开页面的请求回调执行 |
| 数据缓存 | SWR 模式(stale-while-revalidate) | 字典/港口等低变频数据优先展示缓存 |
| 字典批量 | 主应用统一拉取 → shared-provider 广播 | N+1 → 1 次请求(已实施) |
| 接口合并 | 列表 + 统计 + 配置信息 → 一个 BFF 接口 | 减少首屏请求数 |
四、高可用稳定性治理体系
4.1 错误防御三层模型
┌─────────────────────────────────────────────────┐
│ Layer 1: 编译时防御(TypeScript strict + ESLint) │
│ → 类型错误、空安全、未处理 Promise 编译期拦截 │
├─────────────────────────────────────────────────┤
│ Layer 2: 运行时防御(全局拦截 + 边界隔离) │
│ → onerror / unhandledrejection / Vue errorHandler │
├─────────────────────────────────────────────────┤
│ Layer 3: 降级兜底(重试 + 缓存 + 骨架屏) │
│ → 接口重试 / 子应用重试 / 离线缓存 / 白屏恢复 │
└─────────────────────────────────────────────────┘
4.1.1 Layer 1 — 编译时防御(已具备)
| 措施 | 状态 | 说明 |
|---|
| TypeScript strict 模式 | ✅ | noImplicitAny / strictNullChecks / strictFunctionTypes |
| ESLint + @antfu/eslint-config | ✅ | 统一代码规范,@cmclink/eslint-config 工厂函数 |
| 类型化事件通信 | ✅ | MicroBridgeEventMap 编译时校验事件名和 payload |
| API 类型约束 | ✅ | @cmclink/api 请求/响应类型定义 |
4.1.2 Layer 2 — 运行时防御
| 机制 | 实施位置 | 说明 |
|---|
| Vue errorHandler | main.ts 全局注册 | 捕获组件渲染/生命周期错误,上报 + 展示友好提示 |
| window.onerror | wujie-compat-error.ts | 抑制 wujie 沙箱已知错误,转发真实错误到埋点 |
| unhandledrejection | main.ts 全局监听 | 捕获未处理的 Promise 异常 |
| Axios 拦截器 | @cmclink/api 统一错误处理 | 401 自动登出 / 403 权限提示 / 500 友好降级 / 网络超时重试 |
| 路由守卫 | router/permission.ts | 未登录跳转 / 无权限拦截 / 404 兜底 |
4.1.3 Layer 3 — 降级兜底
| 场景 | 降级方案 |
|---|
| 子应用不可达 | 超时提示 + 重试按钮(已实施:useChildAppLoader 可达性探测 + 重试) |
| 接口超时 | 自动重试 2 次(指数退避)→ 展示缓存数据 → 提示用户手动刷新 |
| 白屏检测 | 页面 mount 5s 后检测 document.body.children.length,为 0 则自动刷新 |
| 登录态过期 | Token 刷新机制 → 无感续期 → 刷新失败跳登录页(shared-provider 广播登出) |
| CDN 故障 | 静态资源加载失败时回退到源站(<script onerror> 备用地址) |
4.2 微前端隔离与容错
这是 wujie iframe 沙箱架构的天然优势,需进一步发挥:
| 能力 | 当前状态 | 增强方向 |
|---|
| JS 隔离 | ✅ iframe 天然隔离 | 子应用 JS 错误不影响主壳 |
| CSS 隔离 | ✅ iframe 天然隔离 | 子应用样式不污染主壳 |
| 子应用崩溃隔离 | ✅ iframe 崩溃不影响主壳 | 增加崩溃检测 → 自动重载 iframe |
| 子应用超时熔断 | ✅ 已实施 | 加载超时 → 展示降级 UI → 可重试 |
| 子应用独立部署 | ✅ 已具备 | 子应用独立构建/部署/回滚,不影响其他应用 |
| 灰度发布 | ⏳ 待实施 | 基于用户/租户/比例的子应用版本灰度 |
4.3 接口高可用策略
海运业务系统后端微服务众多,前端需具备接口层面的容错能力:
| 策略 | 实施层 | 说明 |
|---|
| 超时控制 | Axios timeout: 30000 | 防止无响应请求长时间挂起 |
| 自动重试 | Axios 拦截器 | GET 请求自动重试 2 次(指数退避 1s → 2s) |
| 请求队列 | 并发控制 | 同时最多 6 个请求,超出排队(避免浏览器限制导致阻塞) |
| Token 无感刷新 | 401 拦截 | Token 过期时暂存请求 → 刷新 Token → 重放暂存请求 |
| 降级缓存 | @vueuse/core useStorage | 字典/配置等数据接口失败时读 localStorage 缓存 |
| 健康检查 | 心跳 ping | 定期检测后端网关可达性,不可达时全局提示 |
五、可观测性体系
5.1 监控三板斧
┌──────────────────────────────────────────────┐
│ Metrics(指标) │
│ FCP / TTI / CLS / 接口耗时 / 错误率 │
├──────────────────────────────────────────────┤
│ Logging(日志) │
│ 结构化日志 / 操作审计 / 调试日志 │
├──────────────────────────────────────────────┤
│ Tracing(链路追踪) │
│ 全链路 TraceID:前端请求 → 网关 → 微服务 │
└──────────────────────────────────────────────┘
5.1.1 前端性能指标采集
| 指标 | 采集方式 | 用途 |
|---|
| FCP / LCP / CLS | PerformanceObserver | Core Web Vitals 监控 |
| 子应用加载耗时 | 埋点 child_before_load → child_after_mount | 子应用性能基线 |
| 接口耗时分布 | Axios 拦截器 + performance.now() | 慢接口定位 |
| 页面停留时长 | visibilitychange + beforeunload | 用户行为分析 |
| 内存使用 | performance.memory(Chrome) | 内存泄漏预警 |
| 长任务 | PerformanceObserver (longtask) | 卡顿定位 |
5.1.2 错误监控
| 类型 | 采集方式 | 信息 |
|---|
| JS 运行时错误 | window.onerror + Vue.config.errorHandler | 错误堆栈 + 组件名 + 用户操作路径 |
| Promise 未捕获 | unhandledrejection | 异步操作错误 |
| 资源加载失败 | window.addEventListener('error', ...) 捕获 <script> / <link> | CDN / 静态资源故障 |
| 接口异常 | Axios 拦截器 | 状态码 + 请求参数 + 响应体 |
| 子应用加载超时 | useChildAppLoader 埋点 | 超时应用 + 耗时 + 重试次数 |
5.1.3 全链路 TraceID(前后端协同)
前端请求 → Axios interceptor 注入 X-Trace-ID (uuid)
→ Nginx 转发保留 Header
→ 后端网关提取并传播到微服务链路
→ 日志平台关联 TraceID 查询全链路耗时
实施:在 @cmclink/api 的 Axios 请求拦截器中统一注入:
config.headers['X-Trace-ID'] = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
5.2 与已有埋点体系衔接
当前已有的可插拔埋点架构(TelemetryAdapter)完美适配此方案:
| 阶段 | Adapter 实现 | 输出 |
|---|
| Phase A(当前) | ConsoleReporter | 开发环境 console 日志 |
| Phase B | BufferedReporter | 批量上报 + 本地文件落盘 |
| Phase C(平台就绪) | HttpReporter | 对接公司监控平台 → 看板 + 告警 |
六、质量保障体系
6.1 测试策略金字塔
┌─────────┐
│ E2E │ ← Playwright:核心链路 6 Case
│ 测试 │ (子应用加载/切换/路由同步)
├─────────┤
┌─┤ 集成测试 ├─┐ ← Vitest + Vue Test Utils
│ │ │ │ 组件 + Store + Composable
│ └─────────┘ │
┌─┤ 单元测试 ├─┐ ← Vitest:工具函数/类型守卫
│ │ │ │ @cmclink/utils / micro-bridge
│ └─────────────┘ │
┌─┤ 静态分析 ├─┐ ← TypeScript strict + ESLint
│ │ │ │ 编译时类型检查
│ └─────────────────┘ │
└─────────────────────┘
6.1.1 核心链路 E2E 测试(已有清单,需自动化)
已有 6 个冒烟测试 Case(05-父应用核心链路冒烟测试清单.md),需升级为 Playwright 自动化:
| Case | 测试内容 | 自动化优先级 |
|---|
| Case 1 | 子应用首次进入(加载 + 埋点) | P0 |
| Case 2 | 根路径自动重放 | P0 |
| Case 3 | 深链刷新恢复 | P0 |
| Case 4 | 标签切换路由同步 | P1 |
| Case 5 | 页面标签关闭与缓存联动 | P1 |
| Case 6 | 超时与重试降级 | P0 |
6.1.2 性能回归测试
| 方案 | 工具 | 触发时机 |
|---|
| Lighthouse CI | @lhci/cli | 每次 PR 合并到 main |
| Bundle Size Check | bundlesize / turbo outputs | 每次构建检查产物体积 |
| Performance Budget | Vite build.chunkSizeWarningLimit | 构建时超限告警 |
6.1.3 构建质量门禁
steps:
- pnpm type-check
- pnpm lint
- pnpm test:unit
- turbo build --affected
- bundlesize check
- lighthouse-ci
6.2 发布安全策略
| 策略 | 说明 |
|---|
| 子应用独立发布 | 每个子应用独立构建 + 独立部署 + 独立回滚 |
| 灰度发布 | 基于租户 ID / 用户角色 / 百分比灰度新版本 |
| 版本锁定 | pnpm-lock.yaml + engines 锁定 Node/pnpm 版本 |
| 分支策略 | feature → develop → release → main,hotfix 走快速通道 |
| 回滚机制 | 静态资源回退到上一版本 hash;子应用 URL 版本化 |
6.3 持续质量度量
| 度量维度 | 指标 | 采集频率 |
|---|
| 构建健康 | 构建成功率、构建耗时、产物体积趋势 | 每次 CI |
| 代码健康 | TypeScript 覆盖率、ESLint 违规数、死代码比例 | 每周 |
| 性能健康 | Lighthouse 评分趋势、FCP/LCP P75 | 每次发布 |
| 稳定性健康 | JS 错误率、接口成功率、白屏率 | 实时(平台就绪后) |
| 依赖健康 | 过期依赖数、安全漏洞数 | 每月 |
七、分阶段实施路线图
Phase 1(1-2 周):性能基线建立
| 优先级 | 任务 | 产出 |
|---|
| P0 | 接入 Lighthouse CI | 每次 PR 性能评分 ≥ 90 |
| P0 | 配置 Prod 构建链路优化(manualChunks + Brotli) | 首屏 JS 减少 40% |
| P0 | 主壳骨架屏内联 | FCP ≤ 1s |
| P1 | DNS 预解析 + 子应用资源预连接 | 子应用首开减少 ~200ms |
| P1 | Bundle Size 检查加入 CI | 产物体积不退化 |
Phase 2(2-4 周):大数据量场景优化
| 优先级 | 任务 | 产出 |
|---|
| P0 | CmcTable 集成虚拟滚动 | 万级数据 55fps |
| P0 | 大表单分片渲染方案 | 100+ 字段表单 ≤ 400ms |
| P1 | 服务端分页规范化(游标分页) | 深翻页无性能劣化 |
| P1 | 请求去重 + SWR 缓存 | 重复请求减少 60% |
Phase 3(4-6 周):稳定性加固
| 优先级 | 任务 | 产出 |
|---|
| P0 | 核心链路 Playwright 自动化(6 Case) | CI 级别回归保障 |
| P0 | 全局错误监控 + 白屏检测 | 线上问题 < 5min 感知 |
| P1 | Token 无感刷新机制 | 用户无感知的登录态续期 |
| P1 | 接口自动重试 + 降级缓存 | 网络抖动无感知 |
| P2 | 全链路 TraceID 打通 | 前后端问题 1min 定位 |
Phase 4(6-8 周):可观测平台对接
| 优先级 | 任务 | 产出 |
|---|
| P1 | TelemetryAdapter 升级为 HttpReporter | 实时上报 |
| P1 | 性能看板搭建(FCP/TTI/CLS 趋势) | 性能退化实时告警 |
| P2 | 业务看板搭建(子应用加载成功率/耗时分布) | SLO 达标监控 |
| P2 | 灰度发布机制 | 基于租户/百分比的安全发布 |
八、架构决策记录(ADR)
ADR-001:为什么选择虚拟滚动而非分页加载
- 背景:海运订单列表日常操作需要对比多行数据
- 决策:采用虚拟滚动 + 服务端分页组合
- 理由:用户可"无限"滚动浏览同时保持 DOM < 500 节点
- 代价:虚拟滚动组件开发成本略高,但
el-table-v2 已提供成熟方案
ADR-002:为什么不引入 SSR
- 背景:B 端后台系统,登录后使用,无 SEO 需求
- 决策:保持 CSR(Client-Side Rendering)
- 理由:SSR 增加服务端复杂度,B 端场景 ROI 低;预加载 + 骨架屏可满足秒开需求
- 代价:首屏 FCP 略慢于 SSR,但 TTI 差异不大
ADR-003:为什么不用 Service Worker 缓存
- 背景:海运业务数据实时性要求高,缓存可能导致数据不一致
- 决策:暂不引入 SW 缓存策略,仅预留 SW 注册点
- 理由:B 端用户对数据新鲜度敏感,缓存策略复杂度高
- 代价:离线场景不可用,但 B 端后台系统离线场景极少
九、风险与缓解
| 风险 | 影响 | 缓解措施 |
|---|
| 虚拟滚动与业务逻辑冲突(如行合并/树形表格) | 部分复杂列表无法使用虚拟滚动 | 提供 virtual prop 开关,复杂场景回退标准表格 |
| 性能优化引入新 Bug | 回归范围大 | Playwright E2E + Lighthouse CI 双重守卫 |
| 埋点数据量过大 | 存储/传输成本 | 采样 + 聚合 + TTL 过期清理 |
| 子应用团队推广阻力 | 优化方案落地慢 | 渐进式推广,先在 doc/ibs-manage 验证 |
| 后端接口不配合优化 | 前端单方面无法解决慢查询 | 协同后端建立接口 SLA(P95 ≤ 500ms) |
十、总结
本方案从性能(秒开)、稳定性(高可用)、可观测性、质量保障四个维度,基于现有微前端架构提出系统化治理策略:
| 维度 | 核心策略 | 预期收益 |
|---|
| 秒开性能 | 预加载 + 骨架屏 + 虚拟滚动 + CDN + 分片渲染 | 子应用首开 ≤ 2s,二次切换 ≤ 300ms |
| 高可用稳定性 | 三层错误防御 + 子应用隔离 + 接口容错 + 降级兜底 | 白屏率 ≤ 0.05%,子应用加载成功率 ≥ 99.9% |
| 可观测性 | 性能指标 + 错误监控 + 全链路 TraceID | 线上问题 5min 内感知,1min 定位 |
| 质量保障 | 测试金字塔 + CI 门禁 + 灰度发布 + 持续度量 | 每次发布 0 回归,性能不退化 |
核心优势:方案充分利用了现有 Wujie + Monorepo + Turborepo 架构的天然能力(iframe 隔离、预加载 exec:true、增量构建),不需要大规模架构迁移,通过渐进式增强即可达成目标。