携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
设计思路:
阐述系统应用加载器之前,我们需要说明一点:
对于系统业务服务的部件,各部件的能力
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-library的 Config对象中
Config.registerBSConfig(BSConfig.data)
解释一下@basic-library的 Config对象中的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