proc-dispatch.md
3.24 KB
通用存储过程分发
当 gdsmodule.sSaveProName(或 sDeleteProName、sCalcProName、sProcName、sSaveProNameBefore)非空时,框架会调用指定存储过程,而不是落入默认 Add/Update 路径。同一套机制也处理按钮计算和按需自定义逻辑。
处理器是 xlyEntry/com/xly/web/businessweb/ 中的 GenericProcedureCallController。
端点形状
前端向 /business/genericProcedureCall* 下的 URL POST:
- 存储过程名(通常从
gdsmodule或gdsconfigformslave.sButtonParam解析)。 - 参数值(前端提供,框架注入租户身份)。
handler 会:
- 从
information_schema.PARAMETERS加载过程签名(参数名和类型)。 - 拼出
CALL <proc>(?, ?, ...),并按位置绑定参数。 - 用
RequestAddParamUtil增补sBrandsId、sSubsidiaryId、sLoginId、sLanguage。 - 通过 MyBatis 执行。
- 返回过程结果集(过程内 SELECT)或 OUT 参数(保存 / 计算过程)。
为什么动态过程分发重要
schema 中有 1,687 个存储过程和约 177 个函数,把每一个硬编码进 Java 不可承受。按名称分发让框架能调用元数据指向的任意过程,而无需代码变更。框架把过程当作黑盒:输入名称和参数,得到结果。
缺点是:运行时无法静态知道哪些过程存在或它们有什么效果。gdsmodule.sSaveProName 中的拼写错误会变成运行时“proc not found”,不是编译错误。
过程遵循的约定
xly 过程共享一套调用约定,以便通用分发可行:
-
固定顺序的公共参数: 通常是
(IN sLoginId, IN sCustomerId, IN sBrId, IN sSuId, ...),跨切面的身份参数总是在前。 -
租户感知: 每个过程接受
sBrId(=sBrandsId)和sSuId(=sSubsidiaryId),并在内部每个谓词中使用它们保证租户安全。 -
通过 OUT 参数输出: 保存 / 计算过程通过
OUT sCode INT, OUT sReturn LONGTEXT返回成功 / 错误;规范脚手架见xlyEntry/src/main/resources/templates/templesql/sSaveProName.sql。 -
内部动态 SQL: 许多过程用
CONCAT+PREPARE+EXECUTE构造 SQL 字符串。这是 xly 对运行时形状变化的逃生口,也是依赖树中存在jSqlParser的原因(框架偶尔会检查这些字符串)。
配套文件:templesql/
xlyEntry/src/main/resources/templates/templesql/ 保存标准过程类型的脚手架,当前树中有 8 个文件:
sSaveProName.sqlsDeleteProName.sqlsProcName.sqlsSaveProNameBefore.sqlsButtonParam.sqlsProTempleByDb.sqlsp_btn_calc.sqlsSqlStr.sql
这些是工程师编写新过程时填充的模板;运行时不会用它们动态生成过程。loader 和占位符语法见 SQL 模板。
需要关注的失效模式
- 参数顺序不匹配。 通用分发按位置绑定;IN 参数重排的过程会在运行时炸掉。
-
缺少租户过滤。 某过程在内部谓词中忘记
sBrandsId/sSubsidiaryId,就是多租户泄漏。过程不会被框架自动过滤,必须自己处理。 -
长运行过程阻塞请求线程。 长计算应放到独立 worker(见
xlyErpTask,尽管它当前在settings.gradle中禁用)。