EMI协同管控平台 — 速记版
项目时间:2023年6月 - 2023年12月 | 企业级集成中台 技术栈:Vue3 + TypeScript + Wujie + Vite + Pinia + Element Plus + TailwindCSS + ECharts 一句话定位:把 6+ 个不通的子系统整合进一个"统一登录 + 统一权限 + 统一主题 + 跨系统联动"的中台门户。
📋 速记表(面试前快速扫)
| 题 | 一句话结论 | 关键词 |
|---|---|---|
| Q1 | Wujie 双层隔离:shadow 样式 + iframe+Proxy JS 沙箱 | 双层 技术栈无关 |
| Q2 | WujieSubApp 一个标签接一个子应用,入口路由三路 | entryRoute 优雅降级 |
| Q3 | props 管持续状态主题,bus 管路由跳转事件 | props透传 bus路由 |
| Q4 | 菜单/路由/按钮三级权限,v-auth 指令删按钮 | 三级权限 v-auth |
| Q5 | Token 无感刷新:isRefreshing 锁 + 请求队列 | isRefreshing 队列 |
| Q6 | CSS 变量驱动主题,startViewTransition 扩散动画 | CSS变量 ViewTransition |
| Q7 | 标签页 Pinia 管理,右键8项,keepAlive 二级拍平 | 标签页 keepAlive |
| Q8 | v-show 替代 v-if 保活 iframe,关闭=移除 DOM | v-show 保活 回收 |
| Q9 | refreshApp 清 Wujie 单沙箱缓存 | refreshApp 沙箱缓存 |
| Q10 | URL 参数 SSO,三参校验 + location.replace | 三参 清参数 |
| Q11 | Fork 源码修:instanceof/样式/Vite4/wangEditor | Fork 修bug 插件 |
Q1: 为什么选 Wujie + 沙箱隔离原理
面试官可能问:为什么选 Wujie 不选 qiankun?隔离原理是什么?
🗣️ 口述稿:
核心是技术栈无关、改造成本低。qiankun 要求子应用导出 mount/unmount 生命周期,老项目得改;Wujie 给个 URL 就能接 Vue2/Vue3/React/jQuery。隔离是双层:WebComponent(shadow DOM) 管样式、iframe+Proxy 管 JS 沙箱。
💡 关键词提示:技术栈无关 shadow样式 iframe+Proxy沙箱
⚠️ 备注:
- 我 Fork 了 wujie-core 源码进项目,因为早期 bug 多、等官方发版慢
- 只靠 shadow 管不住 JS 全局污染;只靠 iframe 拿不到浏览器原生组件(弹窗/innerWidth),所以叠起来
- 降级判断可直接背出:
localStorage.getItem('degrade')==='true' || !window.Proxy || !window.CustomElementRegistry
追问①:无界降级怎么判断?
三路检测:本地开关 degrade 或没 Proxy 或没 CustomElementRegistry →
degrade:true降成真 iframe 沙箱。
追问②:降级后 polyfill 插件还要吗?
部分不要。InstanceofPlugin 等 7 个 iframe 模式本就不需要,用
!degrade && !defaultDegrade构建时跳过,降级不只切模式还切插件。
Q2: WujieSubApp 通用组件封装
面试官可能问:这个通用组件封装了什么能力?
🗣️ 口述稿:
<WujieSubApp>一个标签挂一个子应用。支持入口路由动态指定(entryRoute / query.to / 兜底/)、多环境 HOST 自动拼环境后缀、优雅降级、注入用户信息window.$wujie.props、jump 桥接让子应用能跳主应用路由。
💡 关键词提示:entryRoute 环境后缀 jump桥接
⚠️ 备注:
- 入口路由三选一是"主应用从哪进来推子应用到哪";环境后缀解决"子应用部署不同路径"
追问①:jump 桥接是什么?
主应用把 router.push 封成 jump 塞进 props,子应用调 jump('/xxx') 就能跨到主应用路由。
追问②:子应用没加载完就发跳转会怎样?
会打"事件订阅数量为空"警告、事件丢失。我的处理是主应用 store 存 flag、激活后补发——承认"半解决"比吹强。
Q3: Wujie 主子应用通信(真实实现)
面试官可能问:主题/权限怎么同步?走 bus 吗?
🗣️ 口述稿:
两套并用:持续状态走 props 透传(主题/角色/Token 塞进
$wujie.props,子应用订阅),一次性跳转走 bus 事件(bus.$emit('${name}RouteChange'))。给每个子应用独立事件名隔离命名空间。
💡 关键词提示:props管状态 bus管路由 独立事件名
⚠️ 备注:
- [重要] 别笼统说"主题用 bus 同步"——代码里主题走 props、bus 管路由,说错和代码对不上
- bus 广播时子应用可能没激活 → 事件丢失,只做了 TODO 补发,承认半解决
追问①:为什么不用原生 postMessage?
Wujie 的 bus 是 postMessage 上封装的一层事件 API($emit/$on/$off),像 Vue 事件总线,还封装了 contentWindow。非 Wujie 的独立 iframe 才要用原生 postMessage。
追问②:props 是每次 render 都重建吗?
不是。主题/角色变化才更新注入 props 派发。那是老版本问题,别答成无脑更新。
[需自行确认]:60%、70% 两个数代码无量化口径,被追问承认是粗估没埋点。
Q4: RBAC 动态权限
面试官可能问:菜单/路由/按钮三级权限怎么做的?
🗣️ 口述稿:
三级:菜单级后端按角色返回路由树、前端 addAsyncRoutes 动态注入+更新菜单;路由级 filterNoPermissionTree 用 meta.roles 过滤 + beforeEach 守卫拦截,输 URL 也进不去;按钮级 v-auth 指令 mounted 时无权限就 removeChild 删掉按钮。
💡 关键词提示:addAsyncRoutes 路由守卫 v-auth指令
⚠️ 备注:
- 双重校验:菜单过滤防看不到 + 路由守卫防改 URL,两者不可互相替代
追问①:切角色怎么让权限即时生效?
switchRole → 后端返回新路由树 → resetRouter 清旧 + initRouter 重注入,X-User-Info 头同步新 currentRole。清+重注入+头同步的闭环。
追问②:角色被管理员撤销了怎么办?
登录拿所有角色,切换时发现当前角色已不在最新列表就自动回退第一个可用角色并提示。这个兜底细节有产品思维。
Q5: Token 无感刷新
面试官可能问:Token 过期怎么无感刷新?
🗣️ 口述稿:
Axios 拦截器里做。accessToken+refreshToken 双 token,请求先本地判 expires-Date.now()<=0,过期就刷新。isRefreshing 锁保证并发请求只触发一次刷新,其它请求进 retryOriginalRequest 队列,刷新完统一重发。响应 401 兜底再刷一次,双失败登出。
💡 关键词提示:双token isRefreshing锁 重发队列
⚠️ 备注:
- 并发去重是灵魂:N 个请求同时 401,只刷一次而不是每个都刷互相踩覆盖
- 面试追问再答存储取舍:accessToken 放 Cookie 自动带、refreshToken 放 sessionStorage 更安全
追问①:为什么放 Cookie + sessionStorage?
按流量和敏感度分储:accessToken 随请求自动带但 4KB 限制;refreshToken 只在当前标签页、更安全。
追问②:X-User-Info 头干嘛?
每个请求带 currentRole.key 告诉后端哪个角色在调;SSO 进来默认 unknown,拦截器下次请求自动从 sessionStorage 补全。
Q6: 主题系统与 ViewTransition
面试官可能问:主题/暗黑怎么做的?切换动画呢?
🗣️ 口述稿:
10 套主题色 + 暗黑开关 + 3 种布局 + 灰/色弱模式。Element Plus 主题用 CSS 变量动态注入(--el-color-primary-light-1..9),暗黑用 darken、日间用 lighten 生成变体。暗黑切换用 startViewTransition 做从点击处扩散的圆形裁剪动画。
💡 关键词提示:CSS变量 darken/lighten startViewTransition
⚠️ 备注:
- 要点是"CSS 变量驱动"而非写死两套 css,才能运行时切换、跟子应用联动
- 同步给子应用走 props(呼应 Q3 修正,别答 bus)
追问①:主题怎么同步给子应用?
走 props 透传 dataTheme 注入 $wujie.props。两次都答 props 就和代码一致了。
追问②:为什么亮白主题不能配暗黑?
亮白主题在暗黑下文字背景都白不可用,所以 showThemeColors 直接过滤 themeColor==='light' && isDark,从源头禁掉错误组合。
Q7: 标签页管理引擎
面试官可能问:标签页怎么管理?右键菜单?
🗣️ 口述稿:
标签存 useMultiTagsStore(Pinia),路由 beforeEach+handleTags('push') 自动加、开关用 splice 删。右键 8 项:重载/关当前/关左/关右/关其它/关全部/整屏全屏/内容区全屏。keepAlive 只支持二级,formatTwoStageRoutes 把三级以上拍平,cachePageList 管缓存。
💡 关键词提示:Pinia标签 右键8项 二级keepAlive
⚠️ 备注:
- 两亮点:dynamicLevel 限制同一路径最多开几个标签防订单详情开 N 个;标签持久化存 localstorage 但默认关,因为权限变恢复会 403
追问①:重新加载怎么实现?
先从 keepAlive 缓存 delete 删掉、间隔 100ms 重建本质是先移出缓存再重放。缓存想重放要先清旧缓存。
追问②:标签和 iframe 保活怎么相处?
iframe/Wujie 子应用已打开就保活,关闭时 clearIframeKeepAliveCache / refreshApp(name) 清沙箱缓存释放内存。
Q8: iframe 保活与性能(真实实现)
面试官可能问:iframe 标签怎么避免重复加载?
🗣️ 口述稿:
核心是 v-show 替代 v-if:v-if 每次切回重建 iframe 重新加载重执行,v-show 让 DOM 一直存在 display:none 隐藏、切回瞬间显示。用 activatedIframeList 维护已打开的 iframe,v-show 控制显隐。
💡 关键词提示:v-show替代v-if activatedIframeList
⚠️ 备注:
- 内存管理是另一半:v-show 保活意味着已打开 iframe 都占 DOM,用户关闭标签必须 emit clearIframeKeepAliveCache 移除 DOM。保活和可回收配套,光讲保活漏一半
- [需自行确认] "70%"代码无测量,别报没来源的数
追问①:v-show 保活会不会无限涨?
会。所以用户关闭标签就真正移除 DOM,只有"已打开未关"的才保持。点出"关闭=移除"对冲内存。
Q9: 沙箱缓存管理(refreshApp)
面试官可能问:怎么清 Wujie 子应用缓存?
🗣️ 口述稿:
Wujie 用 idToSandboxMap 存已激活子应用沙箱。我封装 refreshApp(name):iframe[name] 拿 contentWindow →
__WUJIE.inject.idToSandboxMap.clear()清单个;refreshAllApp() 遍历清全部。
💡 关键词提示:refreshApp idToSandboxMap clear
⚠️ 备注:
- Wujie 官方没暴露清单个沙箱 API,要么全杀要么不管,自己补这一层"填生态空白"是亮点
- 场景:切降级自动 refreshAllApp、子应用白屏手动 refreshApp、登出整页 reload
追问①:清沙箱缓存等于清什么?
让子应用下次激活重新初始化沙箱环境而不是用旧的,对内存回收 + 修复状态错乱都有用。
Q10: 单点登录 SSO
面试官可能问:单点登录怎么实现?
🗣️ 口述稿:
URL 参数方案。外部系统跳进来带 username/roles/accessToken 三参,sso.ts 校验齐就调 /getUserInfo 补全详情,setToken 存 Cookie+sessionStorage,最后 location.replace 把带 token 的 URL 历史替换掉防泄漏。
💡 关键词提示:三参校验 location.replace 防泄漏
⚠️ 备注:
- 重点在"用完要清参数":不 replace 的话点后退又回到带 token 的 URL。最易被忽略的安全细节
- 缺 currentRole 时默认 roles[0],X-User-Info 暂时 unknown,下次请求拦截器自动补全(自愈)
追问①:怎么防误触发 SSO?
严格 must=['username','roles','accessToken'] 三参都才判定,缺一不认,防普通业务 URL 被误判成 SSO 死循环。
追问②:SSO 进来没 currentRole 怎么办?
setToken 只存基本,currentRole 默认 roles[0],X-User-Info 暂时 unknown,下次请求检测 unknown 自动从 sessionStorage 补全。连 SSO 缺字段都想到了。
Q11: Wujie 插件与兼容性(真实代码)
面试官可能问:Fork 源码修复了哪些 Wujie bug?
🗣️ 口述稿:
把 wujie-core Fork 进项目所以能深度改。修的关键:Element Plus 的 instanceof 失效 → InstanceofPlugin;Vite4 子应用样式丢失 → patchElementHook 重定向 style 到 iframe head;innerWidth 不随主窗口变 → jsBeforeLoaders 重写 getter 返回 parent;wangEditor 的 Selection/DataTransfer 判断失败 → jsLoader 按行替换 instanceof。
💡 关键词提示:InstanceofPlugin patchElementHook jsBeforeLoaders
⚠️ 备注:
- 是 iframe 的 Window 和主应用 Window 不是同一份导致的 instanceof 失效
- 承认"Fork 自用为主、没都提 PR,维护债是手动 merge 易冲突"——诚实加分
- [需自行确认] "16 个插件"按 plugins.ts 实际数算,别背固定数字
追问①:这些会提 PR 回上游吗?
会,但我当时 Fork 自用为主没都提。官方更新要手动 merge、易冲突——这是承认的维护债。
🎙️ 行为面试
为什么做这个项目?
公司业务系统越来越多(供应链/化料/数据中心/称料大屏…),每套独立登录、独立权限、独立主题,用户要开多个标签来回登录。老板要统一门户。我从 0 搭 Wujie 微前端中台,把 6+ 子系统收进统一认证/权限/主题/联动。
💡 关键词:多系统 统一门户 Wujie中台
最大难点?
- Wujie 早期 bug 多只能 Fork 源码修——多子应用路由事件串扰(两天排查才发现事件名命名空间)、innerWidth 不更新、Vite4 样式丢
- Token 并发刷新去重——加 isRefreshing 锁 + 请求队列才稳定
- 十几个插件在"正常/降级"两态下的兼容矩阵调试繁琐
💡 关键词:Fork修bug 并发刷新 兼容矩阵
遗憾/弯路?
- Fork 维护成本高,该尽早提 PR 上游
- 标签持久化和权限撤销冲突,恢复可能 403,没彻底解决
- 子应用打包/部署没统一 CI/CD,出错率高;主题同步文案和真实 props/bus 混着,文档没跟上代码
💡 关键词:维护债 持久化冲突 无CI/CD
多少人参与?
前端我一个架构+核心开发;后端 2 个 Java 配合接口;各子应用由业务团队开发,我负责接入规范、WujieSubApp 组件和开发文档。主导选型到联调部署。
💡 关键词:前端独档 后端2人 主导接入
🃏 极简速记卡(面试前5分钟看)
- Q1 选型:技术栈无关;双层隔离shadow+iframe沙箱
- Q2 封装:一个标签一子应用,entryRoute三选
- Q3 通信:props管状态 bus管路由,独立事件名
- Q4 权限:三级双校验,v-auth删按钮
- Q5 刷新:isRefreshing锁+重发队列
- Q6 主题:CSS变量+ViewTransition扩散
- Q7 标签:Pinia+右键8项,二级keepAlive
- Q8 保活:v-show替代v-if,关闭=移除
- Q9 缓存:refreshApp清单沙箱
- Q10 SSO:三参校验+location.replace清参数
- Q11 Fork:instanceof/样式/Vite4修复
- 行为 动机:多系统统一门户;难点:Fork修bug+并发;遗憾:维护债+无CI
上一篇:06-EMI数字孪生平台 下一篇:08-EMI云数据平台(数据大屏)