简述
elpis 是一个沉淀了 80% 的标准性、重复性的 CRUD 工作,又提供了 20% 的个性化扩展的前端框架,能基于一份 json 配置快速搭建标准的中后台管理系统页面,同时保障稳定性与可靠性。

elpis 的整体架构如上。它是一个 BFF 层的架构,是前后端的桥梁。
- 从一份领域模型 model 的 json 配置(如商品管理),可以拓宽到各个业务系统(如淘宝商品管理、京东商品管理)。
- json 满足 json-shema 规范,定义前后端统一校验与数据库表设计,同时扩展出 UI 组件的配置
- 经过 elpis BFF 的处理,可 SSR 渲染各个业务系统的首页,提升首页的渲染速度。首页内菜单系统的切换是采用的 vue-router 的 SPA,保证其流畅性。
- 在 elpis 中,提供了常用的如 input、select等基础 UI 组件,同时支持业务方自定义组件与页面,满足业务特性需求。
- 接口遵循 restful 规范,交互同样经过 elpis。借助洋葱圈模型思想,支持了全局错误捕获、签名校验、参数校验、日志记录等多个中间件
- 实现不同环境的 webpack 差异化配置,满足开发与生产的不同需求
基于 koa 设计服务端引擎
为了统一代码规范,提升开发效率,借鉴 egg 的思路,设计了 elpis-core 服务端引擎 
loader 自动加载
为了减少在代码中各种 require 导致的依赖不清晰,elpis 设计了约定式的目录结构,在对应目录下的文件导出的实例都会被挂在 app 对象上。具体说来,eipis 借用 glob 和 path 两个工具,将相应文件夹下的 js 文件的文件名统一改为驼峰式命名,同时实例化对象并挂载到 app 下。如 app/middleware/api-params-verify.js 中的实例最终会处理成app.middlewares.apiParamsVerify。这样可以通过 app 对象访问到任何模块,而不需要require。
rust
app/controller/**/**.js --> app.controller.xxx
app/extend/**/**.js --> app.extend.xxx
app/middleware/**/**.js --> app.middleware.xxx
app/router/**/**.js --> app.router.xxx
app/router-schema/**/**.js --> app.routerSchema.xxx
app/service/**/**.js --> app.service.xxx
app/config/**/**.js --> app.config.xxx
中间件设计
利用洋葱圈模型,请求(包含业务请求和 API 请求)经过一层层的中间件过滤,再进行业务逻辑的处理,将处理后的结果再一层层返回。
elpis 中定义了三个基础中间件:
- 全局错误捕获。在最外层,最先使用,catch 住后续所有的错误并记录日志。再进行错误分析,如果是请求不存在的页面,则重定向到主页,不会导致页面白屏;如果是 API 请求报错,则统一返回
code: 50000, message: "网络异常 请稍后重试",不会导致接口无响应。 - 签名校验。对接口请求,将 key 和时间戳进行 md5 加密。防止恶意请求,减少服务压力。
- 参数校验。从 ctx 上下文中获取接口的 body、query、header等参数并记录日志,结合 ajv + json-schema,看是否满足
model/**.json中的字段定义的规则。如不满足,直接拦截。
此外,业务方可根据自己的需求定义额外的中间件。
基于 webpack 搭建前端工程化
开发环境热更新
由于首页采用 SSR 渲染,需要将模板 .tpl 文件输出到磁盘,方便后端能拿到最新的模板渲染页面。而现有的 hmr 服务如 webpack-dev-server 将所有文件保存到内存中,无法满足需求,故采用 webpack-dev-middleware 和 webpack-hot-middleware 实现定制的 hmr 服务(webpack-dev-server 就是封对二者进行了封装)。
如图,实现 hmr 服务需要两个基础能力:
- 监控能力。使用 webpack-dev-middleware 监控到业务文件的改变,触发解析引擎工作,将编译后的新的 js/css 文件替换掉内存中旧的。
- 通讯能力。使用 webpack-hot-middleware 进行通讯,通过 webSocket与浏览器保持长连接,推送文件变动事件(如 hash-changed )和新模块的加载指令(如 js/css 文件路径)。浏览器接收到通知后,从内存中动态请求新的产物文件,执行模块替换。
此外,还需进行两处配置:
- 由于 hmr 服务是通过 express 启动的,与后端 elpis-core 服务在不同的端口上,需要配置跨域请求允许。
- 客户端也需要被注入 hmr 的代码。所以需要在每个每个入口文件都加上 webpack-hot-middleware 的客户端代码,指定 HMR 的通信路径和端口。
构建与打包
不同的运行环境需要不同的配置,elpis 中一共使用了三份 webpack 文件。在 webpack.base.js 中定义了通用需配置,然后在开发环境和生产环境中通过 webpack-merge 插件的 merge.smart 引入通用配置并拓展当前环境所需的配置。
- 通用配置。
vue-loader,babel-loader,style-loader等各种 loader 解析器- 别名配置(模块解析的具体行为)
CleanWebpackPlugin,每次build前,清空 public/dist 目录- 打包输出优化(代码分割,模块合并,缓存 chunkhash,压缩优化等策略)
- 开发环境配置。主要是 hmr。
- 生产环境配置。
- 使用
thread-loader利用多核 CPU 加快打包速度 - 使用
mini-css-extract-plugin抽取 css 文件,不打包到 js 中,否则会阻塞渲染。使用css-minimizer-webpack-plugin压缩 css - 使用
terser-webpack-plugin压缩 js
- 使用
总之,开发环境注重的是构建速度,生产环境注重的是文件体积大小与安全稳定性。
业务拓展
以上都是 elpis 提供的基础服务,此外 elpis 支持业务自定义工程化配置,只需要在业务对应的目录(/app/webpack.config.js)下编写 webpack 文件即可,elpis会通过 merge.smart 合并到一起。
但 elpis 不止于此,elpis 将约定优于配置发挥的淋漓尽致。除了支持业务自定义工程化配置,还支持自定义页面,自定义组件,只需在约定目录下新建文件即可。这些目录下的文件都会被合并到 elpis 中,使用 elpis 的能力。
基于 json-schema 设计动态组件体系
上文说到,elpis 支持配置化开发,用户只需要配置一份 json,即可生成一个完整的页面。如下图,这份 json,不仅仅满足 json-schema 规范,可进行数据校验,elpis 对其功能进行了扩展,可以动态设计 UI 组件。甚至,还能用于辅助数据库建表。 
json-schema 规范
采用 json-schema 有以下几个原因
- 规范完整,完全满足校验需求,且业界流行度高,有完整的校验库(ajv)支持。
- json 只是一份数据,前端、后端、数据库可共用,避免了前后端校验不一致的情况(element-plus 中的校验只能前端使用,没法和后端保持统一)。由于定义了数据类型、是否必填等关键信息,后期数据库建表亦可参照此 json。
- 可拓展性强。elpis 在满足 json-schema 规范的基础上拓展出了 UI 组件的描述。
css
...
product_name: {
type: "string",
label: "商品名称",
maxLength: 10,
minLength: 2,
tableOption: {
width: 200,
},
searchOption: {
comType: "dynamicSelect",
api: "/api/proj/product_name/list",
},
createFormOption: {
comType: "input",
default: "iPhone 17 pro",
},
editFormOption: {
comType: "input",
},
detailPanelOption: {},
},
...
tableConfig: {
headerButtons: [
{
label: "新增商品",
eventKey: "showComponent",
eventOption: {
comName: "createForm",
},
type: "primary",
},
],
rowButtons: [
{
label: "详情",
eventKey: "showComponent",
eventOption: {
comName: "detailPanel",
},
type: "primary",
text: true,
},
],
},
componentConfig: {
createForm: {
title: "新增商品",
saveBtnText: "保存",
},
},
...
示例如上,对于"商品名称"这个字段,4-7 行是 json-schema 规范约束,往后都是 UI 组件描述。如在 tableOption 定义 width: 200, 则商品名称在表格中占据的宽度为200,tableOption 中的所有属性描述,可参照 eloment-plus 的 table-column,elpis 中已经做了绑定。如在 searchOption 中定义的 comType: "dynamicSelect",标识商品名称在搜索表单中的组件类型是一个动态下拉框,下拉框中的值通过 "/api/proj/product_name/list" 这个接口去获取。
动态组件设计
用户在 json 中说明字段需要在哪些地方展示(核心组件和动态组件),使用哪种组件类型(基础控件),elpis 用 Vue 的 component 标签,动态绑定 is 属性,同时给子组件传入对应的 json-schema 约束。通过 ref 收集所有子组件的引用,外层统一处理子组件的相关逻辑(如 validate 和 getValue 方法)。
-
基础配置
elpis 中提供了 schema-table、schema-search-bar 两个核心组件和 createForm、editForm、detailPanel 三个动态组件 和 input、inputNumber、select、dynamicSelect、dateRange 六个基础控件。用户在 json 中可自由搭配组合,只需满足对应规范,即可配置出一个完整的 CRUD 页面。
-
业务拓展
当基础配置满足不了业务需求时,elpis 支持业务拓展自己的组件。只需在约定目录下新建文件即可。具体说来:
- 编写业务自定义组件
- 在约定目录下创建组件映射,如
userPanel: { component: UserPanel } - elpis 使用 process.cwd 识别到业务约定目录下的映射文件,与自己的基础组件合并,同名会覆盖基础组件
- Vue 的 component 标签动态渲染时,从合并后的配置对象里查找组件。无论是 elpis 内置的还是业务扩展的,都能找到
基于 docker 改造项目部署
docker
- 编写 DokcerFile 构建 docker 镜像,保证本地、测试、生产使用的是同一镜像,不会导致环境差异问题
- 镜像有tag(branch+commit),可以做版本管理,方便出现问题及时回滚
流水线
基于腾讯 DevOps 2.0-Clound Native Build(cnb)构建 CI/CD 流水线,解决以往手动拉代码、打包、上传、部署的低效问题,只需要配置一次,后续无限复用。
npm run lint检查格式化与 js 报错npm install + npm run build打包docker build + docker push构建 docker 镜像并推送到镜像仓库- 借用 cnbcool/ssh 工具在服务器上
docker pull + docker run启动最新镜像
后续改进
- 支持 typescript。现在没有类型提示,编写代码和配置容易出错;
- 组件联动与通信。现有组件状态无法根据其他组件状态进行动态调整,可参照 formilyjs解决该问题
- 可视化配置。现有配置依赖研发编写 json,对非技术人员不友好。设计一个可视化页面,支持对组件的拖拉拽和属性的更改
- 加上监控看板,实时查看数据。