# 运行时:BusinessBaseController 及相关组件 `xlyEntry/src/main/java/com/xly/web/businessweb/` 是元数据驱动运行时所在位置。本页是维护人员理解大部分通用表单运行时 controller 和 service 的地图。 ## 承重 controller | 类 | 包 | 角色 | 最常引用端点 | |---|---|---|---| | `BusinessBaseController` | `web/businessweb/` | 元数据驱动模块的通用 CRUD。每个表单的默认 API surface。 | `/business/getModelBysId/{moduleId}`、`/business/getBusinessDataByFormcustomId/{formId}`、`/business/addUpdateDelBusinessData`、`/business/getSelectDataBysControlId/{controlId}` | | `BusinessConfigformController` | `web/businessweb/` | 已有表单的每用户 / 每组显示定制,不是基础表单定义 CRUD。 | `/configform/getConfigformData/{moduleId}`、`/configform/sHandleConfigform`、`/configform/sCopyConfigform` | | `GdsmoduleController` | `web/systemweb/` | builder 侧使用的模块树和模块定义 CRUD。 | `/gdsmodule/getModuleTreePro`、`/gdsmodule/addGdsmodule`、`/gdsmodule/updateGdsmodule` | | `GdsconfigformController` | `web/systemweb/` | form-master 和 form-slave 元数据 CRUD。 | `/gdsconfigform/*` 下端点 | | `GdsconfigtbController` | `web/systemweb/` | 虚拟表 master / slave 元数据 CRUD。 | `/gdsconfigtb/*` 下端点 | | `BusinessTreeGridController` | `web/businessweb/` | 树表格端点。当前分支实现了 proc-backed 路径,普通 `getTreeGrid` service 方法仍是 stub。 | `/treegrid/getTreeGridByPro/{formId}` | | `GenericProcedureCallController` | `web/businessweb/` | 按名称 + 参数通用调用存储过程。 | `/procedureCall/doGenericProcedureCall` | | `ConfigformPanelController` | `web/businessweb/` | `gdsconfigformpanel` 中的面板布局持久化。 | `/panel/get/{sFormId}`、`/panel/save/{sFormId}` | | `CheckFlowController` | `web/businessweb/` | Activiti 工作流 surface(审批 / 驳回 / 查看),仅在部署工作流时有意义。 | `/checkFlow/*` 下端点 | 注意 controller 分布在**两个包**中:`businessweb/` 承载运行时端点,`systemweb/` 承载 builder 侧元数据 CRUD 端点。两者都编译进同一个 `xlyEntry` WAR。 ## 四表读取 对任何元数据驱动模块,请求生命周期(见[概念 → 请求生命周期](../../concepts/request-lifecycle.md))可归结为: ```java public Map getModelBysId(Map map) { List> formList = this.getModelConfigByModleId(map); // 1. join gdsmodule⋈form-master⋈form-slave List> fList = businessGdsconfigformsService.getFormconstData(qMap); // 2. gdsformconst List> jList = businessGdsconfigformsService.getJurisdictionData(qMap); // 3. gdsjurisdiction(ADMIN 跳过) Map billnosettingMap = businessGdsconfigformsService.getBillnosettingData(param); // 4. sysbillnosettings List> reportList = printReportService.getReportData(qMap); // 5. sysreport return composite(formList, fList, jList, billnosettingMap, reportList); } ``` 先读 `BusinessBaseServiceImpl.java` 中这个方法;运行时其余部分都是它的变体。 ## 返回的五键复合体 | Key | 来源 | 前端用途 | |---|---|---| | `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave`(+ 覆盖) | 表单布局 | | `gdsformconst` | `gdsformconst` | 表单级常量、下拉标签 | | `gdsjurisdiction` | `gdsjurisdiction` | 按钮 / 数据权限 | | `billnosetting` | `sysbillnosettings` | 单据编号规则 | | `report` | `sysreport` | 打印模板 | ## 保存端点 `POST /business/addUpdateDelBusinessData` 把新增 + 更新 + 删除打包为一个事务批次。前端为每行提供 `sTable` 和 `column` map: ```json { "addData": [{"sTable": "", "column": {"sId": "", "...": "..."}}], "updateData": [{"sTable": "
", "column": {"sId": "", "...": "..."}}], "delData": [{"sTable": "
", "column": {"sId": "", "...": "..."}}] } ``` 当 `gdsmodule.sSaveProName` 为空时,框架默认 Add/Update 路径运行,即 `AddDelUpdCommonServiceImpl.java`。非空时,调用指定存储过程。 ## 多租户边界 每个 controller 方法的第一条非平凡语句都是: ```java RequestAddParamUtil.me().addParams(params, userInfo); ``` 这是把用户 session 身份(`sBrandsId`、`sSubsidiaryId`、`sBrId`、`sSuId`、`sLoginId`、`sLanguage`、`sTeamId`、`sMachineId` 及其他 key)注入下游 `params` map 的单点。任何引用 `#{sBrandsId}` / `#{sSubsidiaryId}` 的 MyBatis 查询或存储过程调用都会从这里自动获得作用域。 新的 controller 方法如果不调用 `RequestAddParamUtil`,就是**多租户 bug**。 ## 需要审计的安全关注点 1. **`addUpdateDelBusinessData` 中的 `sTable` 校验。** 前端直接命名目标表。运行时必须交叉检查传入表是否属于该表单授权的支撑表,否则是权限提升面。若不存在检查,应作为安全 ticket 提出。见[切片 1 v2 follow-up](../../slices/01-hello-world.md#open-verification-items)。 2. **ADMIN 绕过权限。** `BusinessBaseServiceImpl` 对 `UserType.ADMIN` 完全跳过 `gdsjurisdiction` 加载。ADMIN 账号治理必须来自应用外部。 ## 缓存失效 **后台**保存元数据变更时会触发 JMS 消息,`xlyErpJmsConsumer` 中的 `ConsumerChangeGdsModuleThread` 会清除每个运行节点上的元数据缓存。见[元数据变更后的缓存失效](cache-invalidation.md)。