前言
这一集非常简单,思路简单,代码也少。模块配置设计这个词多少有点太专业,我们来具体阐述下,这个配置相关的设计的来源和目的
大家在日常开发中,肯定遇到过这种场景:
功能开发中,某个功能场景需要用到配置信息,要判断业务场景的走向,一般的做法
1、进行前端独立配置,也就是放到某个文件中,代码编译打包后,携带到源代码中,线上环境需要,直接进文件目录进行更新---看着没毛病,但是业务耦合性较强
2、服务端开个接口,搞个配置表,在不同的业务情况下,业务模块调用这个接口,取到数据,在做逻辑判断走向。---看着也没毛病,但是业务耦合性较强
上面是针对业务场景,但是实际中,大部分遇到的
- 系统名称
- 系统Logo
- 登录背景图
- ICP配置
- 业务类配置
上述场景中,一般平躺的思维,就是模块种直接引用,可以快速达到目的。
但对于这种在业务代码直接硬编码从本地存储里面获取,当然也有可能被直接篡改数据的可能,问题还是比较多的。
如果那一天,领导说,我们要把这些零散的配置提到公共层面进行统一管理,这个时候,你会怎么办?
思路上大同小异,上面的解决方案,规整下即可
1、提取配置,放到一个文件中,统一使用接口请求方式,可以方便后期切换到服务接口进行管理维护 2、对数据进行存储,使用方式统一起来,简单
设计思路
配置数据-systemConfig.json
{
"systemName": "系统名称",
"systemLogo": "resource/image/home/Logo2.png",
"loginBackground": "resource/image/login/login4.png",
"userDocPath": "resource/notice/用户使用手册.pdf",
"userDocName": "用户使用手册.pdf",
"HeartTime":30000,
"FOOTER_ICP":"京ICP备xxxx",
"IS_AUTH_INTERFACE": false,
"MQTT_USER_CONFIG":{
"openIM": true,
"protocol":"ws",
"hostname": "",
"port":"",
"proxy": "",
"path":"/ws",
"options":{
"username": "guest",
"password": "guest"
}
}
}
业务应用:
import { Config } from '@basic-library'
const {systemName} = Config.BSConfig
实践代码:
应用实践
// 配置初始化加载
await InitialConfig({ mode: 'local' })
InitialConfig
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: ffzheng
*/
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/systemConfig?${Date.now()}`, requestId: 'systemConfig' });
}
请求和逻辑进行了隔离,使用到了Promise.all
,方便后期进行扩展
配置管理器
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;
核心源码解读:
这个还是一个理念,使用registerBSConfig注册后,对传入的数据options,赋值给BSConfig,其中用到了immer.js
库的produce
immer produce原理解析
produce函数的逻辑很简单,不考虑柯里化可以看作三个步骤:生成draft对象,对draft对象进行修改操作,生成新的结果。
function produce(baseState, recipe) {
const draft = createProxy(baseState);
recipe(draft);
const result = finalize(draft);
}
其中关键在于生成的draft对象,draft通过拦截取值操作和赋值操作将变化前后状态都记录下来。
在这个函数内部对原来的数据进行任何操作,都不会对原对象产生任何影响。
完毕~~~
老铁们,我们一起关注系列设计: