一、问题场景
大屏做出来之后,最常被问到的一个问题是:
"这个数字是写死的吗?"
C06 里 AI 生成的大屏,数据来自静态 Mock。演示时足够好看,但交给业务方使用,必须接入真实数据。这个"最后一公里"往往比生成大屏本身更棘手:
- 数据源种类多:MySQL、PostgreSQL、Oracle、HTTP API……
- 同一张报表,不同部门看到的数据范围不同(行级权限)
- 手机号、身份证号不能直接显示(字段脱敏)
- 大屏上的"部门"需要显示"研发部"而不是
dept_code=IT(维度翻译) - 查询参数要支持"最近 7 天"这种动态值,还要支持组件之间的联动传参
把这些需求逐一硬编码进每个图表组件,维护成本会迅速失控。需要一个统一的数据接入层,让大屏组件只关心"我要什么数据",而不关心"数据从哪来、怎么算出来的"。
二、解决方案:数据源 + 数据集双模块
Forge 的设计思路是把"数据从哪来"和"我要什么数据"拆成两个独立模块。
数据连接(Data Connection)
↓ 定义"能连到哪个数据库"
数据集(Data Dataset)
↓ 定义"我要哪些字段、用什么条件过滤"
大屏组件
↓ 绑定数据集 ID + 参数,运行时自动查询
2.1 数据连接:解决"能连到哪"
ai_report_data_connection 表存储数据库连接信息:
// 核心字段
connectionCode // 连接编码
connectionName // 连接名称
dbType // 数据库类型(MySQL/PostgreSQL/Oracle...)
driverClassName // JDBC 驱动类
jdbcUrl // 连接地址
username // 用户名
passwordCipher // 密码(加密存储,非明文)
schemaName // 默认 schema
testSql // 测试 SQL
poolConfigJson // 连接池配置(JSON)
status // 0=禁用, 1=启用
管理后台提供完整的 CRUD 和"测试连接"功能,密码编辑时留空表示沿用原密码——这个细节很实用,避免每次修改连接都要重新填密码。
为什么选择 JDBC 直连而不是加一层数据中台?
中小型项目里,JDBC 直连的延迟最低、部署最简单。引入中间件意味着多一个故障点,也多一份运维成本。Forge 的定位是"能跑起来的数据大屏工具",而不是企业级数据中台,这个取舍是故意的。
2.2 数据集:解决"我要什么数据"
数据集是连接大屏组件和数据库之间的语义层。它不关心连接细节,只关心"查什么、怎么过滤、返回什么格式"。
系统支持两种数据集类型(DatasetTypeEnum):
| 类型 | 说明 | 适用场景 |
|---|---|---|
TABLE | 选表 + 选字段,系统自动生成 SQL | 快速配置,不需要写 SQL |
SQL | 手写 SQL 查询语句 | 复杂关联、子查询、多表 JOIN |
ai_report_data_dataset 表核心字段:
datasetCode // 数据集编码(唯一标识,前端绑定用这个)
datasetName // 数据集名称
connectionId // 关联的数据连接 ID
datasetType // TABLE / SQL
tableName // 表名(TABLE 类型时使用)
sqlText // SQL 文本(SQL 类型时使用)
paramSchemaJson // 查询参数定义(JSON 数组,核心!)
defaultOrderJson // 默认排序(JSON)
maxRows // 最大返回行数(默认 1000,防止全表扫)
timeoutSeconds // 查询超时时间(秒)
cacheEnabled // 是否启用缓存
cacheTtl // 缓存 TTL(秒)
publishStatus // DRAFT / PUBLISHED / OFFLINE
三、数据结构:三张核心表
3.1 数据集字段定义表(ai_report_data_dataset_field)
数据集的字段不是随便 SELECT * 就完事,每个字段可以单独配置显示名、数据类型、角色(维度/指标)、聚合方式、脱敏规则等:
fieldName // 字段名(对应数据库列名)
fieldLabel // 字段标签(中文显示名,大屏上显示这个)
sourceColumn // 源列名
dataType // 前端数据类型(string/number/date/...)
fieldRole // DIMENSION(维度)/ MEASURE(指标)
defaultAgg // 默认聚合方式(SUM/AVG/COUNT/...)
queryEnabled // 是否允许作为查询条件
displayEnabled // 是否允许在结果中显示
sensitiveLevel // 敏感等级(HIDDEN / MASK / CLEAR)
maskRule // 脱敏规则(正则表达式)
dictType // 字典类型(用于下拉筛选器的选项)
dateFormat // 日期格式
dataUnit // 数据单位(万元/个/%)
dimensionId // 关联维度 ID(用于维度翻译)
fieldRole 的设计值得单独说一下:维度字段用于分组和筛选,指标字段用于聚合计算。这个区分在大屏前端的数据适配层会自动生效——维度字段默认出现在 X 轴或图例,指标字段默认出现在 Y 轴或数值显示。AI 生成大屏时也会参考这个角色信息来决策用哪种图表类型。
3.2 动态参数结构(paramSchemaJson)
这是整个动态数据接入里最灵活的部分。数据集可以声明一组参数,大屏组件在请求数据时传入实际值,后端自动绑定到 SQL 中。
[
{
"paramName": "startDate",
"label": "开始日期",
"fieldName": "order_date",
"operator": ">=",
"defaultValue": "T-7",
"required": true
},
{
"paramName": "deptCode",
"label": "部门",
"fieldName": "dept_code",
"operator": "=",
"defaultValue": "",
"required": false
}
]
defaultValue: "T-7" 表示默认取 7 天前,后端会自动计算实际日期。支持的运算符:=, !=, >, >=, <, <=, LIKE。
四、实现链路:一次运行时查询的完整流程
这是最核心的部分。当用户打开一个大屏页面,每个组件会发起一次数据请求,完整的调用链如下:
大屏组件(Vue 组件)
↓ useChartDataFetch hook
↓ requestDataType == DATASET(3) ?
↓ 是 → datasetRequest()
↓
customizeHttp() — 统一请求入口
↓
POST /data/dataset/runtime/query
↓
DataDatasetRuntimeController
↓
DataQueryExecutor.execute()
↓
1. 加载数据集定义(含 paramSchemaJson)
2. 绑定动态参数(请求参数 → SQL 命名参数)
3. 应用行级权限条件(自动注入)
4. 执行 JDBC 查询(PreparedStatement,防 SQL 注入)
5. 维度翻译(dimensionId 关联字典,code → name)
6. 字段脱敏(maskRule 正则替换)
↓
DataDatasetQueryResultVO
{ dimensions, source, total, fields }
↓
datasetAdapter.adaptDatasetForComponent()
↓ 根据组件类型自动选择适配模式:
↓ ECharts 组件 → echartsDataset 格式
↓ TableScrollBoard → arrayRows 格式
↓ KpiCard → singleValue 格式
↓
渲染图表/表格
4.1 运行时查询接口
前端调用后端的核心接口:
// POST /data/dataset/runtime/query
{
"datasetId": 123,
"params": {
"startDate": "2024-01-01",
"endDate": "2024-12-31",
"deptCode": "IT"
},
"fields": ["dept_name", "amount"],
"pageNum": 1,
"pageSize": 50,
"maxRows": 1000,
"outputMode": "ECHARTS_DATASET"
}
outputMode 控制返回格式,ECHARTS_DATASET 对应 ECharts 的 dataset 格式(dimensions + source),前端几乎可以原样传给 ECharts 实例。
4.2 SQL 安全:为什么不用字符串拼接
参数绑定通过 SqlParameterBinder 转换为 PreparedStatement 参数,而不是字符串拼接。这意味着:
-- 安全的做法(使用命名参数,PreparedStatement 预编译)
SELECT dept_name, SUM(amount)
FROM t_order
WHERE order_date >= :startDate
AND dept_code = :deptCode
GROUP BY dept_name
-- 而不是:
SELECT ... WHERE order_date >= '${startDate}' ← 这会被拦截
SqlSafetyValidator 会在执行前做 SQL 安全检查,防止明显的注入尝试。这不是银弹,但作为纵深防御的一层是有意义的。
4.3 前端动态参数:四种来源
大屏组件在请求数据时,参数值可以从四个来源自动获取(requestDynamicParams.ts):
| 来源 | 说明 | 示例 |
|---|---|---|
context | 用户上下文(userId、username、deptCode 等 18 个字段) | { source: 'context', sourceKey: 'userId' } |
pageContext | 页面上下文(区域、对象等信息) | { source: 'pageContext', sourceKey: 'regionCode' } |
component | 其他组件的当前值(组件联动) | { source: 'component', componentId: 'xxx', componentField: 'value' } |
preset | 预设值(T-N 日期偏移) | { source: 'preset', presetType: 'tn-day-start', offsetDays: 7 } |
preset 类型特别实用:大屏上"最近 7 天销售额"这种需求,不需要前端算日期,直接配 T-7 即可,后端自动算出 CURDATE() - INTERVAL 7 DAY。
4.4 维度翻译
数据库里存的是 dept_code = 'IT',但大屏上要显示"研发部"。这个过程叫维度翻译,由 dimensionId 字段驱动:
数据集字段 dept_code,dimensionId = 5
↓ 查询维度表
↓ code=IT → name=研发部
↓ code=MK → name=市场部
↓
返回给前端的是翻译后的值
这个翻译在后端查询结果出来之后、返回前端之前完成,前端不需要关心 code 和 name 的映射关系。
4.5 字段脱敏
sensitiveLevel + maskRule 实现字段级脱敏:
// 示例:手机号脱敏
// maskRule: (\d{3})\d{4}(\d{4})
// 输入:13812345678
// 输出:138****5678
HIDDEN 级别直接返回空,MASK 级别按正则规则脱敏,CLEAR 级别原样返回。这个逻辑在查询结果映射阶段执行,确保脱敏后的数据不会泄漏到前端。
五、设计取舍
5.1 为什么选择 JDBC 直连而非中间件
| 方案 | 优点 | 缺点 |
|---|---|---|
| JDBC 直连(当前方案) | 延迟低、部署简单、调试方便 | 数据库连接数受限、无统一数据治理 |
| 数据中台/API 网关 | 统一治理、权限集中管控 | 运维成本高、引入额外故障点 |
结论:Forge 的定位是"能快速跑起来的数据大屏工具",目标用户是中小团队。JDBC 直连在这个场景下是最务实的选择。如果未来有企业级需求,可以扩展 forge-plugin-external 插件,通过外部 API 代理方式接入数据中台。
5.2 缓存策略:缓存什么、不缓存什么
cacheEnabled + cacheTtl 字段已定义,但缓存的实现需要权衡:
- 可以缓存的:聚合结果、字典数据、维度翻译结果
- 不应该缓存的:实时交易数据、行级权限过滤后的结果(权限可能变化)
当前实现中,缓存层的具体方案(Caffeine 本地缓存 vs Redis 分布式缓存)取决于部署形态。单机部署用 Caffeine 足够,集群部署需要引入 Redis。
5.3 数据集发布流程
数据集有 publishStatus 字段(DRAFT / PUBLISHED / OFFLINE),这个设计参考了 BI 工具的"发布"概念:
DRAFT:编辑中,大屏无法引用PUBLISHED:已发布,大屏可以绑定,但仍可编辑(编辑后自动变回 DRAFT)OFFLINE:已下线,大屏引用了会报错
这个流程在多人协作时有用——数据集的修改不会影响正在运行的大屏,只有重新发布后新打开的大屏才会用到新版本。
六、二开指南
6.1 如何扩展新的数据源类型
当前支持 JDBC 兼容的数据库。DbTypeEnum 枚举定义了支持的数据库类型,扩展新数据库需要:
- 在
DbTypeEnum中新增枚举值 - 确认 JDBC 驱动是否在依赖中(Maven
pom.xml) - 在管理后台"新增连接"时,
driverClassName填对应数据库的驱动类全限定名
对于非 JDBC 数据源(如 Elasticsearch、InfluxDB),需要:
- 在
DataQueryExecutor中新增执行分支 - 实现对应的
DatasetQueryResultVO构建逻辑 - 在前端
datasetAdapter.ts中确认返回格式兼容
6.2 如何自定义参数绑定逻辑
DatasetParamSchemaParser 解析 paramSchemaJson,SqlParameterBinder 负责绑定。如果需要支持新的运算符(如 IN、BETWEEN),需要:
- 在
DatasetParamSchemaItem.operator中新增枚举值 - 在
SqlParameterBinder中增加对应的 SQL 片段生成逻辑 - 前端
requestDynamicParams.ts中增加对应的参数构造逻辑
6.3 如何优化查询性能
几个实用建议:
- 加索引:确保
paramSchemaJson中用作过滤条件的字段有索引 - 限制返回行数:
maxRows默认 1000,大屏不需要全量数据 - 使用缓存:对变化不频繁的数据集启用
cacheEnabled - 避免 SELECT ***** :用
fields参数指定需要的字段,减少数据传输 - 分页查询:大屏表格组件使用
pageNum + pageSize分页,不要一次拉全量
七、体验预告:C05 → C06 → C07 完整链路
这三篇博客覆盖了一条完整的产品链路:
C05 多 AI 供应商接入
↓ 配置好 AI 供应商和模型
C06 AI 生成数据大屏
↓ AI 根据自然语言描述生成大屏 JSON(含数据集绑定配置)
C07 大屏动态数据接入(本文)
↓ 数据集绑定真实数据库,大屏显示实时业务数据
从"能对话的 AI"到"能生成大屏的 AI"再到"大屏能显示真实数据",这条链路已经可以跑通。下一步的方向是:
- 大屏模板市场:预设行业模板,用户选模板 → AI 微调 → 绑定自己的数据集
- 数据源插件生态:让社区可以贡献新的数据源类型(Elasticsearch、Prometheus...)
- AI 辅助数据集配置:根据数据库表结构,AI 自动生成数据集的字段定义和参数配置
项目入口
- 后台管理:http://81.70.22.48:8084/forge/login
- 项目文档:http://81.70.22.48:8084/forge-docs/
- 大屏设计器:http://81.70.22.48:8084/forge-report/
- Gitee:gitee.com/ForgeLab/fo…
- GitHub:github.com/yaomindong1…
本文基于 forge-plugin-data 模块源码和 datasource-analysis.md 分析报告撰写,代码细节以实际仓库为准。