深入挖掘前端基础服务&中间件设计-配置设计

1,089 阅读3分钟

前言

这一集非常简单,思路简单,代码也少。模块配置设计这个词多少有点太专业,我们来具体阐述下,这个配置相关的设计的来源和目的

大家在日常开发中,肯定遇到过这种场景:

功能开发中,某个功能场景需要用到配置信息,要判断业务场景的走向,一般的做法

1、进行前端独立配置,也就是放到某个文件中,代码编译打包后,携带到源代码中,线上环境需要,直接进文件目录进行更新---看着没毛病,但是业务耦合性较强

2、服务端开个接口,搞个配置表,在不同的业务情况下,业务模块调用这个接口,取到数据,在做逻辑判断走向。---看着也没毛病,但是业务耦合性较强

上面是针对业务场景,但是实际中,大部分遇到的

  • 系统名称
  • 系统Logo
  • 登录背景图
  • ICP配置
  • 业务类配置

上述场景中,一般平躺的思维,就是模块种直接引用,可以快速达到目的。

但对于这种在业务代码直接硬编码从本地存储里面获取,当然也有可能被直接篡改数据的可能,问题还是比较多的。

如果那一天,领导说,我们要把这些零散的配置提到公共层面进行统一管理,这个时候,你会怎么办?

思路上大同小异,上面的解决方案,规整下即可

1、提取配置,放到一个文件中,统一使用接口请求方式,可以方便后期切换到服务接口进行管理维护 2、对数据进行存储,使用方式统一起来,简单

设计思路

image.png

配置数据-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通过拦截取值操作和赋值操作将变化前后状态都记录下来。


在这个函数内部对原来的数据进行任何操作,都不会对原对象产生任何影响。

完毕~~~

老铁们,我们一起关注系列设计:

深入挖掘前端基础服务&中间件设计-basic-library
深入挖掘前端基础服务&中间件设计-字典设计