前言
在做后台业务开发的时候,你是否也跟我一样遇到这样的痛点:
- 每个页面都要重复的编写表格,搜索表单,按钮事件啊这一系列的工作。
- 相同的功能可能就因为表格哪些东西显示的不一样,或者新增表单一些不一样又重新开发。
- 一但产品有需求变化的时候就可能好多个页面的组件都要改。
- 新来的同事接到项目的时候需要耗费很多时间理解大量看似页面展示的内容不同,而代码一大堆的重复。
今天我来分享一套基于JSON Schema规范配置驱动的架构,通过一处配置,多处生效的设计理念,将减轻许多重复的业务开发,让你的业务开发效率得到一大部分提升。
一、设计理念:schema 驱动配置模式
传统的开发模式 VS schema 驱动配置模式。
传统模式:每个页面都要写组件、样式、逻辑
vue
<template>
<div>
<el-table :data="tableData">
<el-table-column prop="product_id" label="商品ID" width="300" />
<el-table-column prop="product_name" label="商品名称" width="200" />
</el-table>
<el-form>
<el-form-item label="商品名称">
<el-select v-model="searchForm.product_name">
</el-select>
</el-form-item>
</el-form>
</div>
</template>
schema 去驱动配置模式:只需要遵从项目中docs目录下dashboard-model.js文件的规范结构
注:dashboard-model.js 文件是自己自定义的一套数据,并且遵循 JSON Schema 规范。
dashboardModel.js
module.exports = {
menu: [{
key: "product",
name: "商品管理",
moduleType: "schema",
schemaConfig: {
api: "/api/proj/product",
schema: {
type: "object",
properties: {
product_id: {
type: "string",
label: "商品ID",
tableOption: { width: 300, "show-overflow-tooltip": true },
searchOption: { comType: "input", placeholder: "请输入商品ID" }
},
product_name: {
type: "string",
label: "商品名称",
tableOption: { width: 200 },
searchOption: {
comType: "dynamicSelect",
api: "/api/proj/product_enum/list"
}
}
}
}
}
}]
}
二、架构设计
架构图

三、实现细节:关键代码解析
一、DSL 语法定义阶段
1. 完整 DSL 语法规范(docs/dashboard-model.js)
javascript
module.exports = {
mode: "dashbord", // DSL 根节点类型
name: "", // 业务名称
menu: [ // 菜单 DSL 语法
{
key: "", // 唯一标识符
name: "", // 显示名称
menuType: "", // 枚举:group/module
moduleType: "", // 枚举:sider/iframe/custom/schema
// 当 moduleType === sider 时
siderConfig: {
menu: [/* 递归 menuItem */]
},
// 当 moduleType === iframe 时
iframeConfig: {
path: "" // iframe 路径
},
// 当 moduleType === custom 时
customConfig: {
path: "" // 自定义路由路径
},
// 当 moduleType === schema 时
schemaConfig: {
api: "", // 数据源 API
schema: { // JSON Schema 扩展语法
type: "object",
properties: {
field: {
type: "", // 标准 JSON Schema
label: "", // DSL 扩展:显示标签
tableOption: {}, // DSL 扩展:表格配置
searchOption: {} // DSL 扩展:搜索配置
}
}
},
tableConfig: {}, // 表格配置
searchConfig: {} // 搜索配置
}
}
]
};
二、DSL 实例编写阶段
1. 基础模型 DSL 实例
javascript
// model/buiness/model.js
module.exports = {
model: "dashbord",
name: "电商系统",
menu: [
{
key: "product",
name: "商品管理",
menuType: "module",
moduleType: "schema", // Schema DSL
schemaConfig: { /* ... */ }
},
{
key: "order",
name: "订单管理",
menuType: "module",
moduleType: "custom", // Custom DSL
customConfig: {
path: "/todo"
}
},
{
key: "data",
name: "数据分析",
menuType: "module",
moduleType: "sider", // Sider DSL
siderConfig: {
menu: [
{
key: "analysis",
name: "电商罗盘",
menuType: "module",
moduleType: "custom",
customConfig: { path: "/todo" }
},
{
key: "search",
name: "信息查询",
menuType: "module",
moduleType: "iframe", // Iframe DSL
iframeConfig: {
path: "https://www.pinduoduo.com/"
}
}
]
}
}
]
};
三、DSL 编译阶段
1. 词法分析(文件扫描与类型识别)
javascript
// model/index.js - DSL 词法分析器
const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
fileList.forEach((file) => {
// 识别 DSL 文件类型
const type = filePath.indexOf(`${sep}project${sep}`) > -1 ? "project" : "model";
if (type === "model") {
// 解析基础模型 DSL
const modelKey = extractModelKey(filePath);
modelItem.model = require(filePath);
}
if (type === "project") {
// 解析项目 DSL
const { modelKey, projectKey } = extractKeys(filePath);
modelItem.project[projectKey] = require(filePath);
}
});
2. 语法分析(继承合并)
javascript
// DSL 语法分析器 - 处理所有模块类型的继承
const projectExtendModel = (model, project) => {
return _.mergeWith({}, model, project, (modelValue, projectValue) => {
if (Array.isArray(modelValue) && Array.isArray(projectValue)) {
// 处理菜单数组的合并
const results = [];
// 按 key 匹配进行语法分析
for (let i = 0; i < modelValue.length; ++i) {
const modelItem = modelValue[i];
const projectItem = projectValue.find(
item => item.key === modelItem.key
);
// 递归解析所有模块类型的 DSL 结构
results.push(
projectItem ? projectExtendModel(modelItem, projectItem) : modelItem
);
}
return results;
}
});
};
四、DSL 执行阶段
1. 路由分发器(entry.dashboard.js)
javascript
// 根据 moduleType 分发到不同的渲染器
const routes = [
// Schema DSL 渲染器
{
path: "/view/dashboard/schema",
component: () => import("./complex-view/schema-view/schema-view.vue"),
},
// Iframe DSL 渲染器
{
path: "/view/dashboard/iframe",
component: () => import("./complex-view/iframe-view/iframe-view.vue"),
},
// Custom DSL 渲染器
{
path: "/view/dashboard/todo",
component: () => import("./todo/todo.vue"),
},
// Sider DSL 渲染器(支持嵌套)
{
path: "/view/dashboard/sider",
component: () => import("./complex-view/sider-view/sider-view.vue"),
children: [
{ path: "iframe", component: () => import("./complex-view/iframe-view/iframe-view.vue") },
{ path: "schema", component: () => import("./complex-view/schema-view/schema-view.vue") },
{ path: "todo", component: () => import("./todo/todo.vue") },
],
},
];
2. 主控制器(dashboard.vue)
javascript
// DSL 执行主控制器
const onMenuSelect = (menuItem) => {
const { moduleType, key, customConfig } = menuItem;
// 根据 moduleType 分发到不同的执行器
const pathMap = {
sider: '/sider', // Sider DSL 执行器
iframe: '/iframe', // Iframe DSL 执行器
custom: customConfig?.path, // Custom DSL 执行器
schema: '/schema', // Schema DSL 执行器
};
router.push({
path: `/view/dashboard${pathMap[moduleType]}`,
query: { key, proj_key: route.query.proj_key }
});
};
五、DSL 渲染阶段
1. Schema DSL 渲染器
javascript
// complex-view/schema-view/hook/schema.js
export const useSchema = () => {
// Schema DSL 执行逻辑
const buildDtoSchema = (_schema, comName) => {
// 将 Schema DSL 转换为组件可用的格式
};
return { api, tableSchema, tableConfig, searchSchema, searchConfig };
};
2. Iframe DSL 渲染器
javascript
// complex-view/iframe-view/iframe-view.vue
const setPath = () => {
const { key, sider_key: siderKey } = route.query;
const menuItem = menuStore.findMenuItem({
key: 'key',
value: siderKey ?? key,
});
// 执行 Iframe DSL,获取 iframe 路径
path.value = menuItem?.iframeConfig?.path ?? '';
};
3. Custom DSL 渲染器
javascript
// 直接路由到自定义组件
// customConfig.path 指向具体的 Vue 组件
4. Sider DSL 渲染器
javascript
// complex-view/sider-view/sider-view.vue
const setMenuList = () => {
const menuItem = menuStore.findMenuItem({
key: 'key',
value: route.query.key,
});
// 执行 Sider DSL,获取子菜单列表
if (menuItem && menuItem.siderConfig && menuItem.siderConfig.menu) {
menuList.value = menuItem.siderConfig.menu;
}
};
const handleMenuSelect = (menuKey) => {
const menuItem = menuStore.findMenuItem({
key: 'key',
value: menuKey,
});
const { moduleType, key, customConfig } = menuItem;
// 递归执行子菜单的 DSL
const pathMap = {
iframe: '/iframe',
schema: '/schema',
custom: customConfig?.path,
};
router.push({
path: `/view/dashboard/sider${pathMap[moduleType]}`,
query: {
key: route.query.key,
sider_key: key,
proj_key: route.query.proj_key,
}
});
};
六、完整 DSL 执行流程图
graph TD
A[DSL 语法定义] --> B[DSL 实例编写]
B --> C[词法分析 - 文件扫描]
C --> D[语法分析 - 继承合并]
D --> E[语义分析 - 模块类型验证]
E --> F[路由分发器]
F --> G{模块类型判断}
G -->|schema| H[Schema DSL 执行器]
G -->|iframe| I[Iframe DSL 执行器]
G -->|custom| J[Custom DSL 执行器]
G -->|sider| K[Sider DSL 执行器]
H --> L[Schema 渲染器]
I --> M[Iframe 渲染器]
J --> N[Custom 渲染器]
K --> O[Sider 渲染器]
O --> P{子菜单类型}
P -->|schema| H
P -->|iframe| I
P -->|custom| J
P -->|sider| K
七、DSL 目录结构
1. DSL 语法定义层
bash
docs/
├── dashboard-model.js # DSL 语法规范文档
├── api-schema.js # API 参数 DSL 规范
└── component-schema.js # 组件配置 DSL 规范
2. DSL 实例编写层
bash
model/
├── index.js # DSL 解析器入口
├── buiness/ # 电商业务域
│ ├── model.js # 基础 DSL 实例
│ └── project/ # 项目 DSL 实例
│ ├── jd.js # 京东 DSL 配置
│ ├── pdd.js # 拼多多 DSL 配置
│ └── taobao.js # 淘宝 DSL 配置
├── course/ # 课程业务域
├── model.js # 基础 DSL 实例
└── project/
├── bilibili.js # B站 DSL 配置
└── dy.js # 抖音 DSL 配置
3. DSL 编译器层
ruby
elpis-core/
├── index.js # DSL 编译器入口
├── loader/ # DSL 加载器
│ ├── config.js # 配置加载器
│ ├── controller.js # 控制器加载器
│ ├── middleware.js # 中间件加载器
│ ├── router.js # 路由加载器
│ ├── router-schema.js # 路由 Schema 加载器
│ ├── service.js # 服务加载器
│ └── extend.js # 扩展加载器
└── env.js # 环境配置
4. DSL 运行时层
csharp
app/
├── controller/ # DSL 执行控制器
│ ├── base.js
│ ├── business.js # 业务 DSL 执行器
│ ├── project.js # 项目 DSL 执行器
│ └── view.js # 视图 DSL 执行器
├── middleware/ # DSL 中间件
│ ├── api-params-verify.js # API 参数验证中间件
│ ├── api-sign-verify.js # API 签名验证中间件
│ ├── error-handler.js # 错误处理中间件
│ └── project-handler.js # 项目处理中间件
├── router-schema/ # API Schema DSL
│ ├── business.js # 业务 API Schema
│ └── project.js # 项目 API Schema
├── service/ # DSL 服务层
│ ├── base.js
│ └── project.js
└── pages/ # DSL 渲染层
├── dashboard/ # 仪表板 DSL 渲染
│ ├── complex-view/ # 视图 DSL 渲染组件目录
│ │ ├── schema-view/ # Schema DSL 渲染组件
│ │ │ ├── hook/
│ │ │ │ └── schema.js # Schema DSL 执行钩子
│ │ │ └── schema-view.vue
│ │ ├── iframe-view/ # Iframe DSL 渲染组件
│ │ ├── sider-view/ # Sider DSL 渲染组件
│ │ └── header-view/ # Header DSL 渲染组件
│ └── dashboard.vue
├── project-list/ # 项目列表 DSL 渲染
├── store/ # DSL 状态管理
│ ├── index.js
│ ├── menu.js # 菜单 DSL 状态
│ └── project.js # 项目 DSL 状态
└── widgets/ # DSL 组件库
├── schema-search-bar/ # 搜索栏 DSL 组件
├── schema-table/ # 表格 DSL 组件
├── header-container/ # 头部 DSL 组件
└── sider-container/ # 侧边栏 DSL 组件
四、未来规划与总结
一、未来规划
- TypeScript支持:提供类型安全的配置
- 可视化配置:提供配置管理界面
二、总结
这套 DSL 的架构设计,通过一套配置,实现了
- 开发效率的替身
- 维护成本的降低
- 减少了重复代码的编写
- 支持项目的定制