proc-dispatch.md 3.24 KB

通用存储过程分发

gdsmodule.sSaveProName(或 sDeleteProNamesCalcProNamesProcNamesSaveProNameBefore非空时,框架会调用指定存储过程,而不是落入默认 Add/Update 路径。同一套机制也处理按钮计算和按需自定义逻辑。

处理器是 xlyEntry/com/xly/web/businessweb/ 中的 GenericProcedureCallController

端点形状

前端向 /business/genericProcedureCall* 下的 URL POST:

  • 存储过程名(通常从 gdsmodulegdsconfigformslave.sButtonParam 解析)。
  • 参数值(前端提供,框架注入租户身份)。

handler 会:

  1. information_schema.PARAMETERS 加载过程签名(参数名和类型)。
  2. 拼出 CALL <proc>(?, ?, ...),并按位置绑定参数。
  3. RequestAddParamUtil 增补 sBrandsIdsSubsidiaryIdsLoginIdsLanguage
  4. 通过 MyBatis 执行。
  5. 返回过程结果集(过程内 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.sql
  • sDeleteProName.sql
  • sProcName.sql
  • sSaveProNameBefore.sql
  • sButtonParam.sql
  • sProTempleByDb.sql
  • sp_btn_calc.sql
  • sSqlStr.sql

这些是工程师编写新过程时填充的模板;运行时不会用它们动态生成过程。loader 和占位符语法见 SQL 模板

需要关注的失效模式

  1. 参数顺序不匹配。 通用分发按位置绑定;IN 参数重排的过程会在运行时炸掉。
  2. 缺少租户过滤。 某过程在内部谓词中忘记 sBrandsId / sSubsidiaryId,就是多租户泄漏。过程不会被框架自动过滤,必须自己处理。
  3. 长运行过程阻塞请求线程。 长计算应放到独立 worker(见 xlyErpTask,尽管它当前在 settings.gradle 中禁用)。