• 乾坤微服务的使用


    前言:

            在这里整理下用乾坤来开发微服务的一些资料。

    使用好处:

            使用乾坤可以实现什么效果呢?众所周知,前端的框架五花八门,react/vue/angular等各领风骚,那么如果我们有需要把不同技术栈的项目整合起来,应该怎么去做呢?如果统一技术栈进行开发,工作量太大,成本也高,那还能怎么做呢?没错,这就是我们乾坤技术出来的由来,可以通过他把不同的项目进行一个融会贯通,让他们可以实现亲密的联系,又能各自发展。

    乾坤的官网地址:点我

    乾坤的逻辑流程:

    如何去使用:

    1、安装
    1. yarn add qiankun
    2. npm i qiankun -S
    2、主应用的main.js
    1. // (1)导入乾坤框架
    2. import { registerMicroApps, start } from "qiankun";
    3. // (2)注册乾坤子应用
    4. registerMicroApps([
    5. {
    6. name:"sub-application01", //子应用名称
    7. entry:"//localhost:8001", //子应用入库地址
    8. container:"#container", //主应用容器
    9. activeRule:"/sub-application01", //主应用路由匹配规则
    10. props:{
    11. token:"sub-application-001"
    12. } //主应用传递参数
    13. },
    14. // {
    15. // name:"sub-application02",
    16. // entry:"//localhost:8002",
    17. // container:"#container",
    18. // activeRule:"/sub-application02",
    19. // props:{
    20. // token:"sub-application-002"
    21. // }
    22. // }
    23. ]);
    24. //(3)开启乾坤
    25. start();
    3、主应用的配置  initGlobalState(state)
    • 参数
    • state - Record - 必选
    • 用法定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法
    1. import { initGlobalState } from 'qiankun';
    2. // 跨应用共享状态
    3. const initialState = {
    4. hasCallPhone: false, // 是否拨打电话
    5. outsidePhone: '', // 外部电话号码
    6. isLocal: true, // 是否是本地号码
    7. channelId: '', // 渠道
    8. leadsId: '',
    9. hasSendMsg: false, // 是否发送短信
    10. maSend: {}, // MA的leadsId,channelId
    11. hasSendEmail: false, // 是否发送邮件
    12. contactHistory: false, // 是否展示联系历史
    13. customerId: '', // 联系历史需要的id,
    14. newDict: false, // 是否新增字典
    15. addDictId: '', // 传入字典id
    16. callDetails: false, // 是否展示通话详情
    17. channelSessionId: '', // 通话详情需要的id
    18. urgentObj: null, // 获取紧急程度
    19. socketCallback: null,
    20. taskList: [],
    21. isCustomerEdit: false, // 是否可以编辑客户
    22. trendsLayout: [], // 客户表单
    23. dynamicFields: [], // 动态字段
    24. fixedFieldsComponent: [], // 固定字段
    25. operateType: '', // 操作方式,是新增还是编辑
    26. callerName: '', // 主叫号人员名称
    27. calledName: '', // 被叫号人员名称
    28. roomNumber: '', // csp呼叫房间
    29. softPhone: {
    30. curOperate: '', // 呼叫状态
    31. hasSipConnected: false, // 电话连接状态
    32. mediaAvailabled: false, // 音频媒体
    33. webrtcConfig: {}, // 初始化连接webrtc配置
    34. },
    35. imPageNoticeInfo: {}, // 内部聊天页面通知相关数据
    36. iqcPageNoticeInfo: {}, // 内部支持页面通知相关数据
    37. reconnectCallback: null, // 内部支持断网重连回调
    38. reconnectImCallback: null, // IM
    39. callVoiceCallback: null,
    40. callVoiceInfo: {},
    41. goConversation: false, // 通讯录跳转
    42. };
    43. const actions = initGlobalState(initialState);
    44. export default actions;
    4、主应用中手动加载微应用的方式:
    1. import { loadMicroApp } from 'qiankun';
    2. let leftMicroApp = null;
    3. 方法内部:
    4. leftMicroApp = loadMicroApp({
    5. name: 'crm_core',
    6. entry: '//localhost:9011',
    7. container: '#wrapper__right',
    8. props: {
    9. path: 'customerTabs',
    10. },
    11. });
    12. //组件销毁,调用子应用的 unmount方法
    13. destroyed() {
    14. leftMicroApp.unmount()
    15. },
    5、子应用中
    1、新建文件:public-path.ts/ public-path. js
    1. /* eslint-disable camelcase */
    2. if (window.__POWERED_BY_QIANKUN__) {
    3. __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
    4. }
    2、main.ts/main.js
    1. import './core/public-path'
    2. // vue3中写法
    3. const __qiankun__ = window.__POWERED_BY_QIANKUN__
    4. __qiankun__ || render()
    5. // vue2中写法
    6. //创建子应用渲染函数
    7. function render(props = {}) {
    8. const { container } = props;
    9. router = new VueRouter({
    10. base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
    11. mode: 'history',
    12. routes,
    13. });
    14. instance = new Vue({
    15. router,
    16. render: (h) => h(App),
    17. }).$mount(container ? container.querySelector('#app') : '#app');
    18. };
    19. // 独立运行时
    20. if (!window.__POWERED_BY_QIANKUN__) {
    21. render();
    22. };
    3、打包配置,vue.config.js
    1. configureWebpack: {
    2. output: {
    3. library: `${name}-[name]`,
    4. libraryTarget: 'umd', // 把微应用打包成 umd 库格式
    5. jsonpFunction: `webpackJsonp_${name}`,
    6. filename: 'static/js/[hash:8].bundle.js'
    7. },
    8. },
    6、微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

    微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。

    生命周期钩子封装

    1. /**
    2. * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
    3. * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
    4. */
    5. export async function bootstrap() {
    6. console.log('react app bootstraped');
    7. }
    8. /**
    9. * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
    10. */
    11. export async function mount(props) {
    12. }
    13. /**
    14. * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
    15. */
    16. export async function unmount(props) {
    17. }
    18. /**
    19. * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
    20. */
    21. export async function update(props) {
    22. console.log('update props', props);
    23. }

    个人项目中用法:

    main.ts

    1. import './core/public-path'
    2. import { lifeCycle, render } from './core/life-cycle'
    3. const { bootstrap, mount, unmount } = lifeCycle()
    4. export { bootstrap, mount, unmount }
    5. const __qiankun__ = window.__POWERED_BY_QIANKUN__
    6. __qiankun__ || render()

    life-cycle.ts

    1. ...
    2. /**
    3. * 微应用生命周期
    4. */
    5. const lifeCycle = (): { [key: string]: (props?: qkProps) => Promise<void> } => {
    6. return {
    7. async bootstrap(props) {
    8. console.log(`bootstrap props: ${props}`);
    9. },
    10. async mount(props) {
    11. console.log(`mount props: ${props}`);
    12. if (props) {
    13. // 生成动态路由
    14. const availRoutes = assyAvailRoutes(props.menuLists, 1, "", APP_NAME);
    15. // 扁平化菜单树
    16. const flatMenus = flatMenuTree(props.menuLists);
    17. // 将菜单树、动态路由、扁平菜单树存入全局状态中
    18. store.commit("user/SET_MENUS", { menuLists: props.menuLists, availRoutes, flatMenus });
    19. // 将角色列表存入全局状态中
    20. store.commit("user/SET_ROLES", props.roles);
    21. store.commit("user/SET_USERINFO", props.userInfo);
    22. const routes = selfRoutes.concat(availRoutes);
    23. props.routes = routes;
    24. store.commit("chat/SET_SINGLE_CONFIG_EVO", []);
    25. // 如果开启内部聊天语音通话时获取有没有语音聊天权限
    26. if (matchFuncConfig("INTERNALCHAT_SOFTPHONE_ACTIVE") && store.state.chat.singleConfigEvo.length === 0) {
    27. getSingleMyConfigs();
    28. }
    29. // props.functions.sendOrder({
    30. // message: {
    31. // type: 'typing',
    32. // sendUserId: '',
    33. // groupType: ''
    34. // }
    35. // });
    36. actions.setActions(props);
    37. actions.setGlobalState({
    38. socketCallback: (data: any, extraParams: any) => {
    39. store.commit("chat/SET_SOCKET_MAINAPP_PARAMS", extraParams);
    40. const { namespace } = extraParams;
    41. // 接收到父应用分发的消息,进行处理
    42. if (namespace === "im") {
    43. if (data.type === spm.ON_PING) {
    44. imDispatchMessage({ messageType: cmd.SOCKET_PING });
    45. } else {
    46. imDispatchMessage({
    47. messageType: enumMsg[data.messageType],
    48. message: data.message,
    49. });
    50. }
    51. }
    52. if (namespace === "iqc") {
    53. if (data.type === spm.ON_PING) {
    54. iqcDispatchMessage({ messageType: cmd.SOCKET_PING });
    55. } else {
    56. iqcDispatchMessage({
    57. messageType: enumMsg[data.messageType],
    58. message: data.message,
    59. });
    60. }
    61. }
    62. },
    63. // 断网重连回调
    64. reconnectCallback: () => {
    65. store.commit("internal/SET_RECONNECTED_COUNT");
    66. },
    67. // 断网重连回调
    68. reconnectImCallback: (networkStatus:string) => {
    69. utilHelper.handleDisconnectOrOnreconnected(networkStatus)
    70. console.log('##################执行reconnectImCallback',networkStatus);
    71. },
    72. });
    73. }
    74. await render(props);
    75. },
    76. async unmount() {
    77. // 关闭所有的页面通知实例
    78. const { pageNoticeInstances = {} } = store.state.chat;
    79. const instanceKeys = Object.keys(pageNoticeInstances);
    80. forEach(instanceKeys, (key) => {
    81. const notifyInstance = pageNoticeInstances[key];
    82. notifyInstance.close();
    83. });
    84. console.log("unmount props");
    85. instance.unmount();
    86. instance = null;
    87. router = null;
    88. },
    89. async update(props) {
    90. console.log(`update props: ${props}`);
    91. },
    92. };
    93. };
    94. async function render(props?: qkProps): Promise<void> {
    95. let basePath = "";
    96. // 如果是生产环境
    97. if (process.env.NODE_DEV === "production") {
    98. // 如果是子应用,使用二级域名前缀,反之使用带internalPortal三级域名
    99. basePath = __qiankun__ ? `/${APP_NAME}` : `/internalPortal/${APP_KEY}/`;
    100. } else {
    101. // 如果非生产环境,并且不是子应用,
    102. basePath = __qiankun__ ? `/${APP_NAME}` : "/";
    103. }
    104. // 初始化固定路由
    105. let routes = selfRoutes;
    106. if (__qiankun__) {
    107. // 如果是微应用,则使用主应用传递的路由集合
    108. if (props?.routes) routes = props?.routes;
    109. } else if (store.state.user.accessToken) {
    110. // 如果没有授权令牌
    111. // 请求菜单树,非子应用时不控制权限
    112. const response: AxiosResponse = await axiosSingle(getCompleteTree(), false);
    113. if (response.data.length > 0) {
    114. // 获取当前子应用相关的菜单
    115. let menuLists = response.data[0].children.filter((item: IMenu) =>
    116. includes(["conversation", "organization"], item.i18n)
    117. );
    118. // 递归生成菜单
    119. menuLists = recurseTree(menuLists, "");
    120. if (menuLists.length) {
    121. // 生成动态路由
    122. const availRoutes = assyAvailRoutes(menuLists, 1, "", APP_NAME);
    123. // 扁平化菜单树
    124. const flatMenus = flatMenuTree(menuLists);
    125. // 将菜单树、动态路由、扁平菜单树存入全局状态中
    126. store.commit("user/SET_MENUS", { menuLists, availRoutes, flatMenus });
    127. // 叠加固定路由和动态路由
    128. // routes = selfRoutes.concat(availRoutes)
    129. selfRoutes[0].children = availRoutes;
    130. routes = selfRoutes;
    131. }
    132. }
    133. }
    134. router = createRouter({
    135. history: createMemoryHistory(basePath),
    136. routes,
    137. });
    138. instance = createApp(App).use(router).use(store).use(i18n).use(plugin, { imports: [] });
    139. // 全局注册El组件
    140. components.forEach((item) => {
    141. if (item) instance.use(item);
    142. });
    143. // 全量导入El图标
    144. for (const key in Icons) {
    145. if (Reflect.has(Icons, key)) {
    146. instance.component(key, Icons[key]);
    147. }
    148. }
    149. // 注册按钮授权指令
    150. instance.use(authDirective);
    151. // 注册按钮授权全局方法
    152. instance.config.globalProperties.$vAuth = function (key: any) {
    153. return directiveAuth(this, key);
    154. };
    155. instance.use(draggable);
    156. instance.mount(props?.container ? props.container.querySelector("#appInternalChat") : "#appInternalChat");
    157. // instance.use(VueVirtualScroller);
    158. // instance.component('DynamicScroller', VueVirtualScroller.DynamicScroller)
    159. // 前置路由守卫
    160. router.beforeEach(async (to: any, from: any) => {
    161. if (!__qiankun__) {
    162. // 1 如果不是子应用
    163. if (store.state.user.accessToken) {
    164. if (!store.state.user.userInfo) {
    165. const infoConfig = configRequest(`${GlobalConfig.API_HRMS_URL}/employee/current`, httpMethod.GET);
    166. const response1 = await axiosSingle(infoConfig);
    167. const userInfo = response1.data;
    168. store.commit("user/SET_USERINFO", userInfo);
    169. // 1.1 如果有授权令牌
    170. if (to.path === "/login") {
    171. // 1.1.1 如果访问页面为登录页,则跳转到首页
    172. return "/";
    173. } else if (to.matched.length) {
    174. // 1.1.2 如果有匹配的路由,则进行跳转
    175. return true;
    176. } else {
    177. // 1.1.3 如果找不到匹配路由,则跳转到未授权报错页面
    178. // next({ path: '/403', replace: true })
    179. return false;
    180. }
    181. }
    182. } else if (to.path === "/login" && to.query.code) {
    183. // 1.2 如果没有令牌并跳转到登录页,并有授权码
    184. return true;
    185. } else {
    186. // 如果没有令牌并且没有授权码,则跳转到sso进行登录
    187. signIn();
    188. }
    189. } else if (to.matched.length) {
    190. // 2 如果是子应用,并且有匹配的路由,则进行跳转
    191. return true;
    192. } else {
    193. // 3 如果没有匹配路由,则跳转到未授权报错页面
    194. // next({ path: '/403', replace: true })
    195. return false;
    196. }
    197. });
    198. }
    199. export { lifeCycle, render };

  • 相关阅读:
    通过电脑查看Wi-Fi密码的方法,提供三种方式
    【解决】mysqladmin flush-hosts
    单线双线多线服务器有哪些区别
    05-prometheus的联邦模式-分布式监控
    2022年双十一买哪款蓝牙耳机?学生党值得买的蓝牙耳机推荐
    C/C++ 动态规划 算法
    GUI-Guider软件使用
    计算机专业毕业论文java毕业设计开题报告SSM项目源码实现的在线音乐歌曲网站[包运行成功]
    01_SpringSecurity学习之配置HttpSecurity
    Linux进程控制【概念 + 代码演示】
  • 原文地址:https://blog.csdn.net/qq_41619796/article/details/139805601