切片 3 — 由视图支撑的模块(只读报表)
切片 1 追踪的是 CRUD 模块:表单读写一张表。切片 3 前进一步:模块从数据库视图读取,连接多张基础表,并把连接结果呈现为可排序、可过滤的表格。没有保存,没有删除,只读。
这是 xly 中所有“列表视图”的基础:订单汇总、日报、库存快照、KPI 看板。框架通过与表支撑模块相同的元数据管线处理视图支撑模块,唯一差异是 gdsconfigformmaster.sType = 'view' 会抑制写操作。
记录对象
| 模块 |
工单工序明细(Work-Order Process Details) |
| URL 片段 |
/indexPage/commonList(通用“只读列表”页面模板) |
模块 sId |
19211681019715708462888040 |
| 支撑对象 |
viw_mftworkorderprocess,数据库视图,不是基础表 |
| 字段数 | 40 |
| 写过程 | 无,设计上只读 |
| 打印模板 | 无,纯表格 |
该视图把工单 master / slave 表族连接成扁平的每行表示。视图本身的存在就是 xly 报表渲染约定的一部分:与其在每个读取查询中重新写 join,xly 会编写数据库侧 CREATE VIEW,并让一行元数据指向它。
为什么选这个模块 {#why-this-module}
它是 xly 最常见运行模式之一的最简单成员:视图支撑的列表页。这个 dev DB 中有 405 个视图支撑表单(全部表单的 20%),仅次于表支撑表单。理解本切片后,其他运维报表都是变体。
它也反驳切片 1 可能造成的两个误解:
- 并非每个模块都从单表读取;视图支撑模块读取 join。
- 并非每个 URL 都是唯一拼音路径。
/indexPage/commonList是共享模板 URL,许多不同模块都使用它。URL 不标识模块;API 调用上的sModelsId参数才标识模块。
追踪
1. 页面 shell
与切片 1 相同:访问 /indexPage/commonList 进入 SPA shell;SPA 再通过侧边栏 / 菜单状态解析要加载的模块。URL 是显示状态,不是路由驱动。
2. 元数据获取
GET /xlyEntry/business/getModelBysId/19211681019715708462888040?sModelsId=19211681019715708462888040
同一个 handler,同一个五键复合响应:formData、gdsformconst、gdsjurisdiction、billnosetting、report。这里的 report 为空,因为该模块没有附加打印模板。
与切片 1 的关键差异在 formData:form-master 行包含:
sType = 'view'
sTbName = 'viw_mftworkorderprocess'
sSqlStr = SELECT ... FROM viw_mftworkorderprocess ... 骨架
sWhere/sOrder = 默认谓词
sType = 'view' 是框架判断只读的唯一信号。其余元数据结构与 CRUD 模块相同。
3. 表格数据获取
POST /xlyEntry/business/getBusinessDataByFormcustomId/{formId}?sModelsId={moduleId}
仍然是切片 1 的同一端点。handler 不关心支撑对象是表还是视图,只把 sTbName 代入由 sSqlStr + sWhere + sOrder 组合出的参数化 SQL。视图负责 join;框架负责租户过滤和分页。
为什么用视图而不是运行时 JOIN? xly 把 join 写进 SQL DDL(
CREATE VIEW),使运行时 SQL 保持扁平的SELECT ... FROM <one-name> WHERE ...。这样可以把 table、view、proc 等不同“形状”来源插入同一表单机制。代价是命名视图增多;此 dev DB 中有 311 个。
4. 视图本身
viw_mftworkorderprocess 把工单 master 与每工序 slave 明细连接起来。完整 CREATE VIEW 定义见该视图的自动目录页。
命名约定:xly 视图前缀是 viw_ 或 Viw_(schema 中大小写不一致)。join 视图常常很宽,40+ 列很常见。它们几乎总是从主基础表带出 sBrandsId / sSubsidiaryId,使框架的通用租户过滤仍然有效。
5. 保存:不存在
对 sType = 'view',框架不提供新增 / 更新 / 删除按钮。理论上,如果被要求,addUpdateDelBusinessData 的 MyBatis 侧会尝试写入视图;但框架不会为它渲染按钮。需要让某个视图可写时,维护人员通常应把表单指向基础表并使用自定义保存过程,或依赖 MySQL 自动可更新视图能力;本模块都没有这样做。
6. 打印报表(存在时)
一些视图支撑模块确实有打印模板:Excel via jxls、PDF via iText。机制与表格分离:
-
getModelBysId返回report数组,来自通过sFormId关联到表单的sysreport行。 - 前端“打印” / “导出”按钮调用
xlyEntry/src/main/java/com/xly/web/report/下的 controller;PrintReportController是活跃类。(同目录的PrintReportControllerOld.java文件仍存在,但类体已全部注释,是死代码。)controller 加载 jxls / iText 模板,使用同一视图查询的“取全部行”包装,并把二进制文件流回。 - 本模块没有模板,因此不覆盖打印路径。
后续工作。 如果后续修订选择一个确实带打印模板的模块,就能端到端追踪 jxls 导出。当前受 dev DB 状态阻塞(没有任何视图支撑表单挂接
sysreport行;见下面的待验证项)。
本切片引入或强化的概念
-
视图支撑模块:
gdsconfigformmaster.sType = 'view'让模块只读,其余元数据流相同。 -
共享模板 URL:
/indexPage/commonList、/indexPage/commonBill、/indexPage/commonClassify、/indexPage/commonNewBill被数百个模块复用。URL 选择页面形状;模块身份来自sModelsId。 -
报表模板(仅预览):
sysreport通过sFormId关联,jxls / iText 模板由PrintReportController提供。
本切片使用的参考
-
配置人员:如何定义虚拟表:对视图支撑模块来说,表单的
sType和sTbName就是声明。 -
维护人员:运行时:与切片 1 使用同一条
BusinessBaseController路径;唯一新增的是sType分支。 - 新页:视图与报表(维护人员下):记录
viw_/Viw_命名约定、视图必须带出租户列才能保持租户安全的规则,以及打印模板流程。
待验证项
Item 1 — 暂缓(需要填充过的 dev DB)。 截至最近审计,dev DB 中没有任何 view-backed form 挂接
sysreport行:SELECT … FROM gdsconfigformmaster m INNER JOIN sysreport r ON r.sFormId = m.sId WHERE m.sType='view'返回 0 行。该项仍是实际验证缺口,需要一个sysreport至少包含一个 view-backed form 的租户部署。
- 带打印模板的视图支撑模块:选择一个并端到端追踪 jxls 导出。
-
sType = 'proc'变体:209 个表单由存储过程支撑,应另开切片说明过程如何返回结果集和参数如何流动。 -
视图租户安全:审计哪些已关闭:305 个中有 19 个(约 6.2%)泄漏。 对实时 DB 执行:viw_*缺少sBrandsId。
SELECT v.TABLE_NAME
FROM information_schema.views v
WHERE v.TABLE_SCHEMA = DATABASE()
AND v.TABLE_NAME LIKE 'viw_%'
AND v.TABLE_NAME NOT IN (
SELECT TABLE_NAME FROM information_schema.columns
WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'sBrandsId'
);
此 dev DB 返回 19 行,包括 viw_purorder_slave_detail、viw_qlyprocesstest、viw_accproductstoreinvoice* 家族、viw_hmwxjy* 等。如果某个表单指向这些视图且 gdsconfigformmaster.sWhere 没有外层租户谓词,它们就是潜在跨租户泄漏点。下一步应审计这些具体视图对应的表单层谓词;裸视图审计已经变成一条一次性 SQL。