[study] 基于DSL 方案快速搭建不同领域后台

144 阅读3分钟

注:此文章仅是个人学习记录,如有错误,欢迎指出。

背景

在前后端开发中管理后台存在高度相似性,开发者每天进行大量重复性工作,如重复编写和使用类似的组件。DSL(Domain-Specific Language 领域特定语言)配置方案为了减少这种重复性劳动,提高开发效率,将公共模块进行抽离和沉淀。通过这种方式,可以实现对特定领域后台的快速搭建和拓展。

DSL 配置方面:

例如:DSL 用于描述指示板 dashboard 的模板页。通过 json-schema + js 对象 来进行配置。

头部菜单配置:最外层 menu 字段描述头部菜单,通过 menuType 区分是有子菜单的 group 还是菜单项的 module 。

菜单项配置:当 menuType 为 module 时,可展示侧边栏 sider 、数据页面 schema-view 或者定制化页面 custom-view或内嵌页面 iframe-view ,并分别有相应的具体配置。

整体的 DSL 配置总结:包括模板类型、名称、描述、图标、首页等信息,以及详细的菜单配置。

例如电商系统基类的配置:

module.exports = { 
  model: 'dashboard', 
  name: '电商系统', 
  menu: [ ... // 具体的菜单配置项 ] 
};

而延伸的电商系统模型配置:

module.exports = { 
  name: '某宝', 
  desc: '某宝电商系统', 
  homePage: '/schema?proj_key=taobao&key=product', 
  menu: [ ... // 具体的菜单配置项 ] 
};

moduleTypemodule 时,延展的自定义配置:

// 当 moduleType == sider 时
siderConfig: {
  menu: [{
    // 可递归 menuItem (除 moduleType === sider)
  }, ...],
},
// 当 moduleType == iframe 时
iframeConfig: {
  path: "", // iframe 路径
},
// 当 moduleType == custom 时
customConfig: {
  path: "", // 自定义路由路径
,

schema-view,也可自定义配置表格或者搜索框等等,提高可扩展性

// 当 moduleType == schema 时
schemaConfig: {
  api: "", // 数据源 API (遵循 RESTFUL 规范)
  schema: { // 板块数据结构
    type: "object",
      properties: {
        key: {
          ...schema, // 标准 schema 配置
          type: "", // 字段类型
          label: "", // 字段中文名
          // 字段在 table 中的相关配置
          tableOption: {
            ...elTableColumnConfig, // 标准 el-table-column 配置 
            toFixed: 0, // 保留小数点后几位
            visiable: true,  // 默认为 true (false 时,表示不在表单中显示)
          },
          // 字段在 search-bar 中的相关配置
          searchOption: {
            ...eleComponentConfig, // 标准 el-component-columu 配置
            comType: "", // 配置组件类型 input/select/...
            default: "", // 默认值

            // 当 comType === "select"
            enumList: [], // 下拉框可选项

            // comType === "dynamicSelect"
            api: ""
          }
        },
        ...
      }
    },
    // table 相关配置
    tableConfig: {
      headerButtons: [{
        label: "", // 按钮中文名
        eventKey: "", // 按钮事件名
        eventOption: {}, // 按钮事件具体配置
        ..elButtonConfig // 标准 el-button 配置
       , ...],
       rowButtons: [{
         label: "", // 按钮中文名
           eventKey: "", // 按钮事件名
           eventOption: {
           // 当 eventKey === "remove"
             params: {
             // paramKey = 参数的键值
             // rowValueKey = 参数值(当格式为 schema:: 且是tableKey 的时候,到 table 中找相应的字段)
               paramKey: rowValueKey
             }
            }, // 按钮事件具体配置
            ...elButtonConfig // 标准 el-button 配置
        }, ...]
       }, 
       searchConfig: {}, // search-bar 相关配置
       components: {}, // 模块组件
   },
 }

DSL 解析方面

通过继承基类配置,获取整个要渲染的数据结构,方便后续页面搭建使用

module.exports = (app) => {
  const modelList = [];

  // 遍历当前文件夹,构造模型数据结构,挂在到 modelList 上
  const modelPath = path.resolve(app.baseDir, `.${sep}model`);
  const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
  fileList.forEach((file) => {
    if (file.indexOf("index.js") > -1) return;
    // 区分配置类型(model / project)
    const type = file.indexOf(`${sep}project${sep}`) > -1 ? "project" : "model";

    if (type === "project") {
      const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
      const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
      let modelItem = modelList.find((item) => item.model?.key === modelKey);
      if (!modelItem) {
        // 初始化 model 数据结构
        modelItem = {};
        modelList.push(modelItem);
      }
      if (!modelItem.project) {
        // 初始化 project 数据结构
        modelItem.project = {};
      }
      modelItem.project[projKey] = require(path.resolve(file));
      modelItem.project[projKey].key = projKey; // 注入projKey
      modelItem.project[projKey].modelKey = modelKey; // 注入modelKey
    }
    if (type === "model") {
      const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
      let modelItem = modelList.find((item) => item.model?.key === modelKey);
      if (!modelItem) {
        // 初始化 model 数据结构
        modelItem = {};
        modelList.push(modelItem);
      }
      modelItem.model = require(path.resolve(file));
      modelItem.model.key = modelKey; // 注入 modelKey
    }
  });

  // 数据进一步整理: project => 继承 model
  modelList.forEach((item) => {
    const { model, project } = item;
    for (const key in project) {
      project[key] = projectExtendModel(model, project[key]);
    }
  });

  return modelList;
};

总结

DSL 配置具有一定的复杂性,是需要一些约定的。DSL 配置需要对各种数据结构和配置项有清晰的理解,例如 json-schema 规范、菜单配置、模块类型配置、表格配置等。

对于需要频繁对接产品、后端、UI、测试,且存在大量 CRUD 工作的系统。例如电商系统等,通过配置 DSL 描述的模板页和基于领域模型,可以快速搭建大部分页面,提高开发效率。