从“用框架”到“控系统”——数据流、事件流、接口边界是如何形成的;
1. 数据流(Data Flow)
什么是数据流?
数据流是指数据在系统中的流动和处理过程,从输入到输出,经过一系列的转换、存储或计算。数据流关注的是数据如何从一个组件传输到另一个组件,以及在传输过程中如何被加工或使用。
数据流如何形成?
数据流通常由以下因素决定:
- 输入源:数据来源,如用户输入、数据库查询、外部 API 调用。
- 处理逻辑:数据在系统中经过的处理步骤,如过滤、聚合、转换。
- 输出目标:数据最终的去向,如显示到前端、存储到数据库、发送到其他系统。
- 组件交互:系统中组件(如前端组件、后端服务、数据库)之间的数据传递。
例子:MES 系统中的数据流
在 formStorageQuantity.vue
中,数据流可以这样描述:
- 输入源:
- 用户通过
<el-form>
输入过滤条件(formFilter.locationFilter
)。 - 前端调用后端 API
BizInventoryEquipmentController.listWithGroup
获取数据。
- 用户通过
- 处理逻辑:
- 后端执行 SQL 查询(
getGroupedBizInventoryEquipmentList
),按location
分组,计算SUM(total_quantity)
。 - 返回数据(
[{ location: 'xxx', totalQuantitySum: 123 }, ...]
)到前端。 - 前端在
loadChartData
方法中将location
转换为locationName
(通过DictionaryController
获取字典名称)。
- 后端执行 SQL 查询(
- 输出目标:
- 数据渲染到柱状图(
bar-chart
)和表格(table-box
)。
- 数据渲染到柱状图(
形成过程:
- 数据流由用户交互(输入过滤条件)触发,经过前端-后端-数据库的传递和处理,最终呈现为可视化结果。
- 每个组件(前端 Vue、后端 Controller、数据库)负责数据的一个处理阶段,形成一个清晰的流向:
用户输入 → API 请求 → 数据库查询 → 数据处理 → 界面渲染
。
代码示例(前端 loadChartData
):
async loadChartData() {let params = {groupParam: [{ fieldName: 'location' }],aggregateParam: [{ fieldName: 'totalQuantity', aggregateType: 'sum', alias: 'totalQuantitySum' }]};if (this.formFilter.locationFilter) {params.bizInventoryEquipmentDtoFilter = { location: this.formFilter.locationFilter };}const res = await BizInventoryEquipmentController.listWithGroup(this, params);const data = res.data.dataList || [];const locationList = await this.formStorageQuantity.locationFilterWidget.onVisibleChange(true);const locationMap = {};locationList.forEach(item => locationMap[item.id] = item.name);this.chartData = data.map(item => ({location: item.location,totalQuantitySum: item.totalQuantitySum || 0,locationName: locationMap[item.location] || item.location}));
}
数据流路径:
- 输入:
formFilter.locationFilter
。 - 处理:API 调用 → 数据库查询 → 数据转换(
location
转locationName
)。 - 输出:
chartData
渲染到柱状图。
2. 事件流(Event Flow)
什么是事件流?
事件流是指系统中事件(用户操作、系统触发、异步通知等)的触发、传播和处理过程。事件流关注的是事件如何在组件间传递,以及如何触发相应的响应逻辑。
事件流如何形成?
事件流由以下因素决定:
- 事件触发:用户操作(点击、输入)、系统事件(定时任务、状态变化)或外部信号(如消息队列)。
- 事件传播:事件通过组件间的监听机制(如事件监听器、回调函数)传递。
- 事件处理:触发特定逻辑,如更新 UI、调用 API、记录日志。
- 事件链:一个事件可能触发后续事件,形成链式反应。
例子:MES 系统中的事件流
在 formStorageQuantity.vue
中,事件流可以这样描述:
- 事件触发:
- 用户点击“搜索”按钮,触发
filter-box
的@search="refreshFormStorageQuantity(true)"
。
- 用户点击“搜索”按钮,触发
- 事件传播:
refreshFormStorageQuantity
方法调用bizInventoryEquipmentWidget.refreshTable
和loadChartData
。loadChartData
发起 API 请求,触发后端BizInventoryEquipmentController.listWithGroup
。
- 事件处理:
- 后端执行数据库查询,返回数据。
- 前端更新
chartData
和表格数据,触发 Vue 的响应式渲染。
- 事件链:
- 用户输入 → 搜索点击 → API 调用 → 数据更新 → UI 渲染。
形成过程:
- 事件流由用户交互触发,通过事件监听(如
@search
)在前端组件间传播,调用后端 API,最终完成数据更新和渲染。 - 每个组件(前端按钮、Vue 方法、后端 Controller)响应特定事件,形成链式流程。
代码示例(前端 filter-box
和 refreshFormStorageQuantity
):
<filter-box :item-width="350" @search="refreshFormStorageQuantity(true)" @reset="resetFormStorageQuantity"><el-form-item label="存放位置"><el-select v-model="formFilter.locationFilter" @visible-change="formStorageQuantity.locationFilterWidget.onVisibleChange"><!-- 选项 --></el-select></el-form-item>
</filter-box>methods: {refreshFormStorageQuantity(reloadData = false) {this.formStorageQuantity.bizInventoryEquipmentWidget.refreshTable(reloadData, reloadData ? 1 : undefined);this.loadChartData();}
}
事件流路径:
- 触发:用户点击“搜索”按钮。
- 传播:触发
refreshFormStorageQuantity
→ 调用refreshTable
和loadChartData
。 - 处理:更新表格和柱状图。
3. 接口边界(Interface Boundary)
什么是接口边界?
接口边界是指系统组件之间交互的明确定义点,通常通过 API、接口或协议划定。接口边界定义了组件的责任范围和交互方式,确保模块间低耦合、高内聚。
接口边界如何形成?
接口边界由以下因素决定:
- 模块划分:系统按功能划分为模块(如前端、后端、数据库),每个模块有明确职责。
- 接口定义:通过 API(如 RESTful API)、接口类(如 Java 接口)或协议(如消息队列)定义交互规则。
- 数据契约:明确输入输出格式(如 JSON 结构、参数类型)。
- 封装性:隐藏内部实现,只暴露必要接口。
例子:MES 系统中的接口边界
在项目中,接口边界体现在前端和后端的交互:
- 模块划分:
- 前端:
formStorageQuantity.vue
负责 UI 渲染和用户交互。 - 后端:
BizInventoryEquipmentController
负责数据查询和处理。 - 数据库:
biz_inventory_equipment
表存储数据。
- 前端:
- 接口定义:
- RESTful API:
/bizInventoryEquipment/listWithGroup
定义了分组查询的入口。 - 输入:
{ groupParam, aggregateParam, bizInventoryEquipmentDtoFilter }
。 - 输出:
ResponseResult<MyPageData<BizInventoryEquipmentVo>>
。
- RESTful API:
- 数据契约:
- 前端期望后端返回
[{ location: 'xxx', totalQuantitySum: 123 }, ...]
。 - 后端保证返回符合约定的 JSON 结构。
- 前端期望后端返回
- 封装性:
- 前端无需关心后端如何查询数据库(SQL 细节)。
- 后端无需关心前端如何渲染(柱状图或表格)。
形成过程:
- 接口边界通过明确的功能划分(前端 UI、后端逻辑、数据库存储)和API 契约(输入输出格式)形成。
- 每个模块只暴露必要的接口(如
/listWithGroup
),隐藏内部实现(如 SQL 查询逻辑),确保模块间独立。
代码示例(后端 API 定义):
@SaCheckPermission("bizInventoryEquipment.view")
@PostMapping("/listWithGroup")
public ResponseResult<MyPageData<BizInventoryEquipmentVo>> listWithGroup(@MyRequestBody BizInventoryEquipmentDto bizInventoryEquipmentDtoFilter,@MyRequestBody(required = true) MyGroupParam groupParam,@MyRequestBody MyOrderParam orderParam,@MyRequestBody MyPageParam pageParam) {String orderBy = MyOrderParam.buildOrderBy(orderParam, BizInventoryEquipment.class, false);groupParam = MyGroupParam.buildGroupBy(groupParam, BizInventoryEquipment.class);BizInventoryEquipment filter = MyModelUtil.copyTo(bizInventoryEquipmentDtoFilter, BizInventoryEquipment.class);MyGroupCriteria criteria = groupParam.getGroupCriteria();List<BizInventoryEquipment> resultList = bizInventoryEquipmentService.getGroupedBizInventoryEquipmentListWithRelation(filter, criteria.getGroupSelect(), criteria.getGroupBy(), orderBy);return ResponseResult.success(MyPageUtil.makeResponseData(resultList, BizInventoryEquipmentVo.class));
}
接口边界:
- 前端调用:
BizInventoryEquipmentController.listWithGroup(this, params)
。 - 契约:输入 JSON(如
{ groupParam: [{ fieldName: 'location' }], aggregateParam: [...] }
),输出 JSON(如{ data: { dataList: [{ location: 'xxx', totalQuantitySum: 123 }, ...] } }
)。 - 封装:前端无需知道后端如何查询数据库,后端无需关心前端如何渲染。
如何结合形成?
数据流、事件流和接口边界在系统中是相互关联的,共同构成了系统的运行机制:
- 数据流驱动数据从输入到输出的流动。
- 例:用户输入
locationFilter
→ API 查询 → 渲染柱状图。
- 例:用户输入
- 事件流触发和协调数据流的执行。
- 例:点击“搜索”按钮触发
refreshFormStorageQuantity
→ 调用loadChartData
。
- 例:点击“搜索”按钮触发
- 接口边界定义数据流和事件流的交互规则。
- 例:前端通过
/listWithGroup
API 与后端交互,明确输入输出格式。
- 例:前端通过
形成过程总结:
- 需求驱动:用户需求(如查看库存统计)定义了数据流(查询
totalQuantity
)和事件流(点击搜索)。 - 模块划分:系统按功能分为前端、后端、数据库,形成接口边界(如 RESTful API)。
- 交互设计:通过事件监听(
@search
)和 API 调用(listWithGroup
)连接模块,形成数据流和事件流。 - 契约约束:接口边界通过明确的输入输出格式(如 JSON)确保模块间正确交互。
优化建议(结合MES 项目)
-
数据流优化:
- 在
formStorageQuantity.vue
中,缓存DictionaryController.dictGlobalDict
的结果,减少重复 API 调用:data() {return {locationCache: null,// ...}; }, async loadChartData() {if (!this.locationCache) {this.locationCache = await this.formStorageQuantity.locationFilterWidget.onVisibleChange(true);}// 使用缓存的 locationCache }
- 后端批量查询,减少数据库访问:
List<BizInventoryEquipment> resultList = bizInventoryEquipmentMapper.getGroupedBizInventoryEquipmentList(filter, groupSelect, groupBy, orderBy);
- 在
-
事件流优化:
- 使用防抖(debounce)或节流(throttle)减少频繁触发事件:
import { debounce } from 'lodash'; methods: {refreshFormStorageQuantity: debounce(function (reloadData = false) {this.formStorageQuantity.bizInventoryEquipmentWidget.refreshTable(reloadData, reloadData ? 1 : undefined);this.loadChartData();}, 300) }
- 避免重复触发 API 调用,提高前端性能。
- 使用防抖(debounce)或节流(throttle)减少频繁触发事件:
-
接口边界优化:
- 确保 API 契约清晰,添加 Swagger 文档:
@ApiOperation("分组查询库存设备") @PostMapping("/listWithGroup") public ResponseResult<MyPageData<BizInventoryEquipmentVo>> listWithGroup(@RequestBody RequestBodyDto dto) {// ... }
- 使用 DTO 校验输入:
public class RequestBodyDto {@NotNullprivate MyGroupParam groupParam;private BizInventoryEquipmentDto bizInventoryEquipmentDtoFilter;// ... }
- 确保 API 契约清晰,添加 Swagger 文档:
练习:设计数据流、事件流和接口边界
任务:为 formNewManufacture.vue
的新制件统计功能设计数据流、事件流和接口边界。
- 数据流:
- 描述从用户输入
useLifeFilter
到渲染柱状图的数据流动路径。
- 描述从用户输入
- 事件流:
- 描述点击“搜索”按钮触发的事件链。
- 接口边界:
- 定义
/bizProjectEquipment/listWithGroup
和/bizInventoryEquipment/listWithGroup
的输入输出契约。
- 定义
参考答案:
-
数据流:
- 输入:用户在
input-number-range
输入useLifeFilter
(如[1, 5]
)。 - 处理:
- 前端调用
BizProjectEquipmentController.listWithGroup
(source: 'SGRP'
, 按useLife
分组,聚合sgrpReceiveQuantity
)。 - 前端调用
BizInventoryEquipmentController.listWithGroup
(equipmentClass: 20
, 按useLife
分组,聚合totalQuantity
)。 - 前端合并数据,生成
chartData
([{ useLife: "1 年", newManufactureQuantity: 123 }, ...]
)。
- 前端调用
- 输出:渲染到柱状图(
bar-chart
)。
- 输入:用户在
-
事件流:
- 触发:用户点击
filter-box
的“搜索”按钮,触发@search="refreshFormNewManufacture(true)"
。 - 传播:调用
bizProjectEquipmentWidget.refreshTable
和loadChartData
。 - 处理:
loadChartData
发起两个 API 请求,更新表格和图表。
- 触发:用户点击
-
接口边界:
/bizProjectEquipment/listWithGroup
:- 输入:
{ groupParam: [{ fieldName: 'bizInventoryEquipment.useLife' }], aggregateParam: [{ fieldName: 'sgrpReceiveQuantity', aggregateType: 'sum', alias: 'sgrpReceiveQuantitySum' }], bizProjectEquipmentDtoFilter: { source: 'SGRP' } }
- 输出:
{ data: { dataList: [{ useLife: 1, sgrpReceiveQuantitySum: 50 }, ...] } }
- 输入:
/bizInventoryEquipment/listWithGroup
:- 输入:
{ groupParam: [{ fieldName: 'useLife' }], aggregateParam: [{ fieldName: 'totalQuantity', aggregateType: 'sum', alias: 'totalQuantitySum' }], bizInventoryEquipmentDtoFilter: { equipmentClass: 20 } }
- 输出:
{ data: { dataList: [{ useLife: 1, totalQuantitySum: 70 }, ...] } }
- 输入:
总结
- 数据流:数据从输入(用户、数据库)到输出(UI、存储)的流动,由组件交互和处理逻辑形成。
- 事件流:事件从触发(用户操作、系统信号)到处理(更新 UI、调用 API)的传播,由监听机制和调用链形成。
- 接口边界:模块间的交互规则,通过 API、接口类和数据契约定义,确保低耦合。
- 结合 MES 项目:
- 数据流:
locationFilter
→ API →chartData
。 - 事件流:点击“搜索” →
refreshFormStorageQuantity
→ API 调用。 - 接口边界:
/listWithGroup
API 定义输入输出契约。
- 数据流: