业务侧-系统应用加载器-AppLoader

175 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

设计思路:

阐述系统应用加载器之前,我们需要说明一点:

对于系统业务服务的部件,各部件的能力

image.png

1、向外提供给业务功能

2、服务于系统基础运行

对于系统应用加载器-AppLoader,它的定位是为业务基础运行服务

标题功能说明
配置初始化加载应用系统相关配置-系统配置、业务功能配置等
系统API接口注册http请求接口服务注册
系统异常注册请求异常拦截、埋点等
用户中心注册用户信息、权限等
字典注册系统字典表加载
路由注册动态路由注册等
IM通信注册消息通信-交互实时性较高场景

庞杂的功能,都是根据情况调度各加载器的注册功能

以上的功能调度,都是系统初始化加载所需:

  • 登录进入系统
  • 页面刷新后

系统入口 main.js

const run = async()=>{
   // 加载业务元素--字典注册/接口注册等
   await AppLoader.install()
   app.use(router).use(eduComponent).mount('#app')
}

run()

App.js

/**
 *  应用管理器安装
 * @param {*} No
 * 场景: 系统加载
 */
async function install() {

    // 配置初始化加载
    await InitialConfig({ mode: 'local' })

    // 接口注册
    registerInterFace()
  
    // 异常拦截注册
    ErrorLoader.registerError()

    // 用户相关--菜单/用户信息
    await registerUser()
    // 字典注册
    await registerDict()
    // 路由注册
    RouterLoader.loader(router)
    // IM通信注册/连接
    IMLoader.register()
}

我们来分析一下,上面应用管理器安装部分的核心代码以及功能:

1、配置初始化 InitialConfig

属于系统应用侧,对于应用系统配置以及业务配置,系统登录后或者刷新后,会发起http请求

mode: 'local'

请求本地的静态public/config/systemConfig.json文件

{
  "systemName": "XXX教育云",
  "subSystemName":"XXX教育云",
  "systemLogo": "resource/image/home/Logo2.png",
  "loginBackground": "resource/image/login/login4.png",
  "userDocPath": "resource/notice/用户使用手册.pdf",
  "userDocName": "用户使用手册.pdf",
  "homePath":"/coursePage",
  "HeartTime":30000,
  "FilePreview": "http://10.40.128.174:8080",
  "FOOTER_ICP":"xxx",
  "IS_AUTH_INTERFACE": false,
  "MQTT_USER_CONFIG":{
    "openIM": true,
    "protocol":"ws",
    "hostname": "",
    "port":"",
    "proxy": "",
    "path":"/ws",
    "options":{
      "username": "guest",
      "password": "guest"
    }
  }
}

其他情况,会发送后端请求进行获取,目前服务端具体的还未提供,待提供后在进行迁移

这样会有一个好处,就是系统的一些配置的东西,放在了静态的文件中

上线后可进行配置修改,避免重新打包

具体的请求,目前设计在业务组件中,做为数据加载组件存在

import { InitialConfig, InitialRequest } from '@businessComponents'


import { Config } from '@basic-library'
import { querySystemConfig } from './utils'

async function InitialConfig({ mode = 'server' }) {
  return Promise.all([
    querySystemConfig(mode),
  ]).then((res) => {
    const [BSConfig] = res
    BSConfig.data && Config.registerBSConfig(BSConfig.data);
  });
}
export default InitialConfig


/*
 * @Description: 系统配置-工具--请求
 * @Author: xxx
 */
import { Request } from '@basic-library'

export async function querySystemConfig(mode) {
  const BaseUrl = process.env.BASE_URL
  if (mode === 'local') {
    return Request.$http({ url: `${BaseUrl}config/systemConfig.json?${Date.now()}`, requestId: 'systemConfig' });
  }
  return Request.$http({ url: `${BaseUrl}api/configSerive/systemConfig?${Date.now()}`, requestId: 'systemConfig' });
}

细心的同学,可能会发现,配置加载完成后,会加载到@basic-libraryConfig对象中

Config.registerBSConfig(BSConfig.data)

解释一下@basic-libraryConfig对象中的registerBSConfig方法是对配置进行注册,注册完成后, 获取时

import { Config } from '@basic-library

// 系统名称
const {systemName} =  Config.BSConfig

就是一个向外提供注册,注册后,可进行自我管理,然后向外提供获取

Config

关于 import { Config } from '@basic-library的实现,因为篇幅关系,只贴代码,后期有专门章节进行讲解:

import produce from 'immer';

const config = {
  //模块配置
  AppConfig: produce({}, () => {}),
  registerAppConfig(options) {
    this.AppConfig = produce(this.AppConfig, (draft) => {
      Object.keys(options).forEach((key) => {
        draft[key] = options[key];
      });
    });
  },
  //应用配置
  BSConfig: produce({}, () => {}),
  registerBSConfig(options) {
    this.BSConfig = produce(this.BSConfig, (draft) => {
      Object.keys(options).forEach((key) => {
        draft[key] = options[key];
      });
    });
  },
};

const EDUConfig = (function () {
  if (window._EDU_CONFIG_) {
    return window._EDU_CONFIG_;
  } else {
    window._EDU_CONFIG_ = config;
    return config;
  }
})();

export default EDUConfig;

2、接口注册 registerInterFace

/**
*  接口注册
* @param {*} No
* 场景: 系统加载
*/
function registerInterFace() {
    // 接口注册--白名单接口、代理PROXY设置
    Service.register(globalApi.API_STORE, {
        proxy: CONFIG.INTERFACE_PROXY,
        unique: true,
        errorListen: true,
    })
}

从参数可以简单看出来

  • 全局的 globalApi.API_STORE,就是接口的名称和地址
  • proxy 设置全局请求代理
  • 请求是否添加唯一后缀 unique,目前使用的是时间戳,避免请求304缓存
  • errorListen 是否开启异常拦截,异常拦截使用了订阅发布模式

3、异常拦截注册

围绕上面的接口注册,对请求进行拦截处理,http请求码等,具体实现待专门章节讲解

4、用户中心 registerUser

/**
*  用户相关--菜单/用户信息
* @param {*} No 
*/
const registerUser = async () => {
    // 用户相关--菜单/用户信息
    const { loginStatus } = BaseStore.app
    if (loginStatus) {
        await InitialRequest()
    }
}

用到了,InitialRequest,和上述的InitialConfig的来源有点相似,就是系统业务请求注册,比如权限等

5、字典注册 registerDict

/**
*  注册字典
* @param {*} No 
* 场景: 系统加载
*/
const registerDict = async () => {
    Dict.clearAll()
    // 字典注册 -- 静态
    Dict.register(typeCodes)
    // 字典注册 -- 远程
    const { loginStatus } = BaseStore.app
    if (CONFIG?.DICT_REMOTES && loginStatus) {
        const { returnObj = [] } = await Service.useHttp('queryAllDictList');
        returnObj?.length && Dict.register(returnObj, { name: 'name', code: 'code', type: 'dictType' })
    }
}

框架层,提供@basic-library的字典管理器Dict,提供注册、删除等基本能力,请求字典接口后,返回的数据进行注册处理,供后续业务功能使用。

6、路由管理器 RouterLoader

直接调用 路由管理器的 RouterLoader.loader,实现动态路由的注册

从上述用户中心的数据,注入到路由管理器中

待专门章节讲解

7、IM通信注册

直接调用 IM管理器的 IMLoader.register,实现系统级别MQTT的注册,实现用户会话级别的订阅。

待专门章节讲解

业务侧-Loader设计
业务侧-系统应用加载器-AppLoader
业务侧-登录管理器-LoginLoader
业务侧-异常拦截管理器-ErrorLoader
业务侧-路由管理器-RouterLoader