# BI / KPI / 图表引擎 xly **没有**内置通用 OLAP / cube 引擎(没有 Mondrian、Saiku、MDX;随包里的 `olap4j-1.2.0.jar` 没有任何 Java import,属于 classpath 上的历史包袱,见[技术栈](tech-stack.md)里的 OLAP4J 说明)。 xly 真正提供的是一套**自研的元数据驱动看板 + KPI 层**。它和框架其他部分使用同一组原语:`gds*` 元数据行指向 `Sp_*` 存储过程,再由通用 Java service 渲染。本页按端到端路径说明它。 ## 三个部分 | 部分 | 入口 | 支撑表 | Service | |---|---|---|---| | **图表**(卡片、柱线饼仪表盘组件、看板) | FROUNT 的 `/indexPage/commonChar` 模块;BACK 的图表配置管理页 | `gdsconfigcharmaster`(3,006 行)、`gdsconfigcharslave`(1,951 行) | `CharServiceImpl`(2,219 行,是 xlyBusinessService 里最重的类之一) | | **KPI**(按员工 / 模块做绩效评分) | BACK / FROUNT 的 KPI 页面,例如 `行为KPI项目设置`、`5.经营KPI分析`、`异常清除KPI任务表` | `kpimaster`(124,524 行)、`kpidetail`(1,308)、`kpimodule`(44)、`kpimoduleuser`、`kpimoduleuserday`、`kpislavel` | `KpiServiceImpl`(833 行,位于 `xlyBusinessService/.../KPIService/`)+ `BusinessModelKpiServiceImpl`(901 行)+ `FlushModleKpiThread`(后台刷新) | | **预置聚合过程** | 由图表元数据行调用 | n/a | 20 个 `Sp_chart_*` 过程 + 2 个 `Sp_KPI_*` 过程 + `spKPImodule` | ## 图表:看板如何渲染 xly 中的一个图表就是 `gdsconfigcharmaster` 中的一行,关键列如下: | 列 | 作用 | |---|---| | `sId` | 图表 ID | | `sParentId` | 所属模块(`gdsmodule.sId`) | | `sChinese` / `sEnglish` / `sBig5` | 图表标题 | | `sCharType` | 组件类型,见下方分布 | | `sProcedureName` | 产出图表数据的存储过程 | | `sProcedureParam` | 存储过程参数 JSON 规格 | | `iWidth` | 布局跨度(24 列栅格) | 实时 dev DB 中的 `sCharType` 分布: | `sCharType` | 数量 | 渲染内容 | |---|---:|---| | `Div` | 1558 | 容器 / 纯布局块 | | `sLabel` | 1143 | 单值文本卡片,例如“今日销售额:¥X” | | `Progress` | 137 | 进度条 | | `sPie` | 52 | 饼图 | | `commonList` | 45 | 内嵌数据表格(复用通用 grid) | | `sColumnarGroup` | 30 | 分组柱状图 | | `sColumnar` | 28 | 单系列柱状图 | | `sBrokenLine` | 5 | 折线图 | | `sBar` | 3 | 横向条形图 | | `ColorBlock` | 3 | 类热力块的彩色块 | | `sGauge` | 2 | 仪表盘组件 | 从表行(`gdsconfigcharslave`)保存多系列 / 多列图表所需的系列或列拆分。 运行时路径: ```text SPA 打开 /indexPage/commonChar?sModelsId=<看板模块 id> │ ▼ GET /xlyEntry/business/getModelBysId/ → 返回看板的元数据复合结果 (formData 中包含来自 gdsconfigcharmaster + slave 行的图表布局) │ ▼ 对每个图表: POST /xlyEntry/business/getXxx(CharServiceImpl 方法) 携带图表的 sProcedureName + sProcedureParam → CharServiceImpl 通过通用存储过程分发调用对应的 Sp_chart_* 过程 │ ▼ SPA 用前端 ECharts 渲染每张卡片;一行图表元数据对应一张卡片 ``` ## 20 个 `Sp_chart_*` 过程 | 过程 | 计算内容 | |---|---| | `Sp_chart_home_11`、`Sp_chart_home_13` | 首页看板卡片 | | `Sp_chart_TodayOrder`、`Sp_chart_TodayOrder_hm`、`Sp_chart_ThisMonthQty`、`Sp_chart_MonthOrder`、`Sp_chart_MonthTeamQty` | 当天 / 当月订单数、班组产量 | | `Sp_chart_TodayProfit`、`Sp_chart_MonthProfit`、`Sp_chart_TodayReceivables`、`Sp_chart_TodayReceive`、`Sp_chart_expenses` | 财务:利润、应收、收款、费用汇总 | | `Sp_chart_EquipmentLoad`、`Sp_chart_EquipmentLoad1`、`Sp_chart_EquipmentLod1`、`Sp_chart_EquipmentLast`、`Sp_chart_sMachine_speed`、`Sp_chart_Bottleneck` | 车间:设备负载、最后运行状态、当前瓶颈 | | `Sp_chart_OrderProcess`、`Sp_chart_WorkOrderProcess` | 订单 / 工单进度时间线 | 每个过程都遵循标准的 `(IN sLoginId, IN sBrId, IN sSuId, ...) → result-set` 形状,因此通用分发器可以直接调用。[多租户作用域](../../concepts/multi-tenancy.md)也会自然传入:每张图表都会自动按租户过滤。 ## dev DB 中的预置看板模块 `/indexPage/commonChar` 是共享路由。dev DB 中有 6 个模块映射到它: | Module sId | 中文名称 | |---|---| | `19211681019715464089035510` | 销售图表分析 | | `19211681019715481435115760` | 财务图表分析 | | `19211681019715481435298200` | 生产图表分析 | | `19211681019715708435449190` | 销售大数据分析 | | `19211681019715708471874620` | 采购大数据分析 | | `101251240115015889205266000` | 采购价格分析查询 | 六个模块全部由 `gdsconfigcharmaster` 行驱动;新增或修改它们不需要改 Java 代码。 ## KPI 子系统 > **先消歧。** FROUNT 首页也有一个标题为“**KPI监控**”的卡片,但它**不是**本页记录的 KPI。首页卡片是 `BusinessModelCenterController.getModelCenter` 提供的未清任务计数器,读取 `gdsmodule.bUnTask` / `sUnType`,没有目标值、评分和图表,只是名字容易误导。见运行时页的 [KPI 工作中心](runtime.md#kpi-工作中心front-端首页-dashboard)。下面的 `kpi*` 表族才是真正的员工绩效评分层。 `kpi*` 是独立于图表渲染的**按员工绩效评分**层。形状如下: | 表 | 作用 | 实时行数 | |---|---|---:| | `kpimaster` | 按员工、按期间的 KPI 汇总。它是容量最大的表:每个员工的每次评分事件一行。 | 124,524 | | `kpidetail` | 支撑每条 `kpimaster` 汇总的明细行。 | 1,308 | | `kpimodule` | KPI 定义:哪些模块 / 指标参与评分。 | 44 | | `kpimoduleuser` | 每用户 KPI 分配。 | 0(dev DB 未分配) | | `kpimoduleuserday` | 每用户每日 KPI 桶。 | 1 | | `kpislavel` | KPI 等级 / 档位定义。 | 0 | Java 侧: - `KpiServiceImpl.java`(833 行,位于 `xlyBusinessService/.../KPIService/`):KPI 事件的读写 API。 - `BusinessModelKpiServiceImpl.java`(901 行):把业务事件数据计算成 KPI 行的计算层。 - `FlushModleKpiThread.java`:后台重算线程。 - `KpimasterCloum.java` enum(xlyPersist):列名常量。 存储过程: - `Sp_KPI_DetailByEmployee`:按员工的明细报表。 - `Sp_KPI_SumByEmployee`:按员工的汇总报表。 - `spKPImodule`:按模块重算 KPI。 `script/客户/` 下也有较大的客户覆盖,例如 `script/标版/30100101/spKPImodule.sql` 以及多个 `Sp_SalesOrder_Kpi*` 过程。这与[每客户 SQL 覆盖通道](../../slices/05-customer-sql-override.md)一致:需要不同 KPI 规则的客户会交付自己的过程。 ## 自研方案的代价 元数据 + 每图表一个过程的设计与 xly 的数据驱动论点一致,也避免了携带重型 OLAP 引擎。但代价很明确: 1. **每张新图表都需要 SQL 作者。** “PM 添加一行元数据”只在工程师已经写好配套 `Sp_chart_*` 过程之后才成立。这里没有聚合构建器、字段选择器或自动生成查询;每个指标都是工程团队手写、评审和维护的存储过程。20 个过程和 11 种图表类型就是当前系统能渲染的全部形状。 2. **图表在 OLTP DB 上跑重 SQL。** 没有数仓、没有预聚合、没有增量汇总。“今日利润”图表就是在实时交易 schema 上做 SELECT。大客户加载图表时会和录单负载竞争同一个 MySQL 实例。缓存有帮助,但只对命中有效;元数据变更后的第一次加载仍要付完整成本。 3. **图表之间没有语义一致性保证。** 每个 `Sp_chart_*` 过程自行决定如何计算“月利润”“今日销售额”等指标。两个看似展示同一指标的图表可能因为过程体不同而悄悄不一致。真正的语义层可以避免这个问题,自研模型不能。 4. **不能钻取,也不能自由切片分析。** 每张图表都是固定查询形状。用户不能自由切换维度,也不能从汇总卡片钻取到底层交易,除非工程师为每条路径再写一个过程。 5. **KPI 逻辑会按客户分歧。** `script/客户/` 下的客户会交付自己的 `spKPImodule` 和 `Sp_SalesOrder_Kpi*` 覆盖;不同客户的 KPI 算法不同,而且代码只存在于该客户 DB 中。这会让“这个 KPI 到底是什么意思”取决于当前连接的是哪个 schema。 这种简单设计足够支撑“展示 xly 一直展示的那 20 张卡片”。如果目标是即席分析或自助报表,它就不够了;那需要一套 xly 当前没有的独立语义层 / 数仓层。 ## 它不是什么 - **不是自助 BI 工具。** 客户不能随便指向一张表并拖拽生成图表;新图表需要一个 SQL 存储过程,以及懂得注册元数据行的管理员。 - **不是实时分析基础设施。** 图表在缓存 miss 时会直接在 OLTP MySQL schema 上运行过程。没有独立数仓、没有增量聚合管道、没有流式层。大客户的大图表会在实时 DB 上执行重 SQL。 - **不是列存 / OLAP 引擎支撑的分析。** `xlyPersist/build.gradle` 中的 `olap4j` jar 没有任何 Java import,只是 classpath 上的历史包袱。xly 通过 MyBatis 和通用存储过程分发使用 MySQL 的普通行存。