1. 方案背景宗旨
尽可能减少更改代码量,对各个项目进行整合,各项目间仍然能保证代码相互独立,减少依赖交互。无论怎么合并,我们最终的需求始终是希望通过一个入口来完成所有项目应用之间的交互工作。
2. 技术方案
方案一 SSO单点登录授权
2.1.1 大致实现逻辑
a)首先需要进行用户数据同步清洗,对所有用户进行整合,统一主入口登录页面(其余各类框架项目均不保留登录页),登录如下图所示,sso服务返回对应的token信息,把token存储在sso前端授权页面(授权即登录页)。
b)不同的应用进入页面时应该检查本地是否有对应的token令牌,否则执行以下步骤,拿上自身的clientId加回调网址,前往sso授权页,此时sso如果在登录期存在cookie,则直接分配一个code并按照回调地址返回。不同应用拿上code向自身服务端发起请求获取令牌和用户信息。
- 简单点,如果不需要区分应用来源的话,可直接忽略code,前往授权页可直接返回sso存储的公有token,看具体需求
各应用间的请求拦截,UI也是相互独立的,比如当全局的token过期,则不是退出到自身的登录路由,而是我们多应用公用的sso前端授权登录页
做完以上步骤的改动,一个项目的整合基本初步完成,各应用之间发布互不影响,部署也是毫无影响
2.1.2 需要改动点
- 新增角色、用户体系产品需要考虑如何调整?是各项目维护同步至sso,还是统一系统页面进行维护
- 登录界面删除,所有登录类的跳转需要按规则跳转到sso统一的授权登录页
- 各应用需要预留应用切换的统一入口
2.1.3 方案优缺点
- 改动点非常小,便于维护
- 切换应用是各域名间的切换,借助了cookie 的存储和授权完成,因此切换时会有页面刷新的效果
- 由于是各域名的切换,也就直接肯定了各应用之间是完全独立的,后期不易让各应用进行整合(比如一个应用菜单下含有其他应用的菜单)
- 不能统一不同框架的统一风格(如,全局message、全局loading)
方案二 微前端应用整合
2.2.1 大致实现逻辑
a)用户数据进行整合,接口统一,开发一套基座主应用,用来承载不同应用(不同框架),主应用的功能主要包含登录、顶部header等公共区域。
b)基座应用的作用就是处理各系统通用事件,如定义好公共的交互方式,让其他应用来配合接入(如:获取token、退出、loading、提示等),再如新增角色、授权,菜单分配等
c)在部署时,主项目应用通过路由的匹配规则加载了各个其他应用(框架)的js代码片段,渲染到主应用预留的dom节点。
暂时无法在飞书文档外展示此内容
2.2.2 需要改动点
- 添加base路由前缀,防止各应用之间路由冲突
- 更改项目自身的登录token和用户存储机制,放置主应用基座中开发
- 制定通用的交互方式,一些特殊时机需要主应用来执行(如:获取token、退出、loading、提示等)
- 不同框架接入的方式有所不同,需要注意不同框架不同版本间的兼容
- 不同应用的头部均要进行调整,统一切成主应用的header
2.2.3 方案优缺点
- 通过主应用对多个单页面应用的融合,切换应用没有任何刷新的感知,用户体验上更像是一个项目
- 可以通过主应用的路由匹配规则,可以将各应用放置系统的任意位置,你可以理解为iframe,但比它更友好
- 可以结合主应用运行,也可以单独运行,可跨框架(单独运行时需要在本地预留一套登录体系,多地需要判断是在主应用内执行的还是单应用执行的)
- 可以将所有系统的menu进行整合,在menu菜单内集成多系统的不同界面,用户无感知,但项目是隔离的
- 开发难度稍微会复杂一些,最新框架的版本暂时没有适配
如:qiankunjs为一种技术方案:qiankunjs
micro-app:micro-app
如果使用的是乾坤,代码注意点:
我们的react项目中,webpck版本默认使用的是5.x,案例为react17,与常规官网使用有所不同(官网为react16,webpack4.0以下),直接引入会有出现失败
具体见github:https://github.com/umijs/qiankun/issues/1092
方案:
function render(props: any) {
const { container } = props
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter basename="/dvp">
<ConfigProvider virtual={true} locale={zhCN}>
<App />
</ConfigProvider>
</BrowserRouter>
</PersistGate>
</Provider>
</React.StrictMode>,
container ? container.querySelector('#root') : document.getElementById('root')
)
}
declare global {
interface Window {
__POWERED_BY_QIANKUN__: boolean
__webpack_public_path__: string
__INJECTED_PUBLIC_PATH_BY_QIANKUN__: string
qiankunLifecycle: any
}
}
declare var __webpack_public_path__: any
if (!window.__POWERED_BY_QIANKUN__) {
render({})
}
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
export async function bootstrap() {
console.log(' react app bootstraped')
}
export async function mount(props: any) {
console.log('props from main framework', props)
render(props)
}
export async function unmount(props: any) {
const { container } = props
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('root'))
}
if (process.env.NODE_ENV === 'development') {
window.qiankunLifecycle = {
bootstrap,
mount,
unmount
}
然后在html中添加以下代码在底部
<% if(process.env.NODE_ENV==='development' ) { %>
<script entry>
if (window.__POWERED_BY_QIANKUN__) {
window.purehtml = window.qiankunLifecycle;
}
</script>
<% } %>
2.3 方案对比
sso单点登录 | 微前端应用整合 | ||
---|---|---|---|
难度/复杂度 | ⭐️ | ⭐️⭐️⭐️ | |
改动点 | ⭐️ | ⭐️⭐️⭐️⭐️ | |
易维护 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | |
代码隔离程度 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️⭐️ | |
用户体验 | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | 前者切换应用、授权会刷新,应用内部切换菜单不会刷新,后者则都不刷新;不同框架之间的系统消息提示、loading前者会有所差距 |
跨应用灵活度 | ⭐️ | ⭐️⭐️⭐️⭐️ | 前者要求应用相对独立,后者可以将不同应用的菜单无感知整合到一起;如果迭代需要整合各系统的菜单,则微前端会更适合。 |