# 切片 5 — 扩展:每客户 SQL 覆盖 元数据驱动框架能表达的内容是有限的。 客户可能需要销售对账报表使用不同聚合规则,需要一个独特的自定义视图支撑成本看板,或需要一个行为从根本上偏离标准的存储过程。覆盖表([切片 4](04-custom-field.md))可以加字段和覆盖 SQL 片段,但不能替换存储过程的*逻辑*;过程是代码,不是数据。 到达这个边界时,xly 的逃生口是:**把手写 SQL 文件提交到 `script/客户//`,并直接应用到该客户 schema**。这完全绕过元数据层。它是生产中真实使用的通道,适用于若干客户;Wiki 必须诚实记录其范围、成本、适用时机和运维纪律。 ## 记录对象 | | | |---|---| | **客户** | 重庆展印 | | **覆盖文件** | `script/客户/重庆展印/Sp_SalSalesCheck.sql`(723 行) | | **替换内容** | 标准 `Sp_SalSalesCheck` 存储过程(销售对账列表) | | **配套文件** | `script/客户/重庆展印/viw_salsaleschecking_pro.sql`(该过程读取的自定义视图) | | **部署** | **手工**,由工程师 / DBA 对该客户 schema 应用 | 文件开头两行说明了这个通道的形状: ```sql DROP PROCEDURE IF EXISTS `Sp_SalSalesCheck`; delimiter ;; CREATE PROCEDURE `Sp_SalSalesCheck`(IN sLoginId varchar(100), ...) ``` 标准过程被 drop,自定义过程取而代之。`gdsmodule` 中的元数据仍按名称引用该过程(`sProcName = 'Sp_SalSalesCheck'`);运行时照常调用。差异完全存在于过程主体中,而主体现在是工程师编写的变体。 ## 当前代码库中有覆盖的客户 `script/客户/` 下有 18 个客户子目录: ```text 统兴 重庆展印 嘉诚 安徽金印 万昌 无锡中江 扬州浩宇 金九 亚明威 快马 金宣发 千彩 高旺 朝阳 上海亚峰 湛江新澳 福雅 远传 ``` 每个目录有一到十几个 `.sql` 文件。多数是自定义过程(`Sp_*`)和自定义视图(`viw_*`)。读取目录列表本身就是系统文档:这里的客户和功能,是框架无法表达、需要 bespoke 行为的地方。 ## 覆盖如何进入系统 没有自动通道。搜索 Java 代码中加载 `script/客户` 或遍历这些目录的部署服务,没有结果。机制是运维性的: 1. 工程师以标准过程为起点编写覆盖 `.sql` 并修改主体。 2. 文件提交到 `script/客户//`,用于可追溯性。 3. 使用 `mysql --defaults-file=... < the-file.sql` 或等价方式,**手工**对目标客户 MySQL schema 执行。 4. 从那以后,该客户 schema 中的过程主体就不同于其他客户;框架代码不变。 把文件放在代码库中很重要:工程团队可以一眼看出哪些客户偏离标准。但这不表示这些文件会作为任何 build 的一部分发布。 ## 为什么这不同于切片 4 切片 4 的 `gdsconfigformcustomslave` 和切片 1 的元数据 Add/Update 路径都留在框架内部。客户特定行为是叠加在共享代码库之上:每个租户运行时读取相同 Java、相同 MyBatis mapper、相同标准过程。 切片 5 的通道位于框架之下。客户 schema 中有一个同名但主体不同的存储过程。调用 `Sp_SalSalesCheck` 的 Java / MyBatis 代码不知道另一端是标准过程还是重庆展印变体。框架不知道,也无法分辨。 因此覆盖具有: - **强能力。** MySQL 存储过程 SQL 能写出的任何东西,都可替换标准行为。 - **运维脆弱。** 客户 schema 重建、恢复或迁移时,覆盖必须重新应用或保持存活。它不跟随代码库备份,只跟随数据库备份。 - **推理困难。** 维护人员读标准 `Sp_SalSalesCheck` 源码时,必须记得*某些客户*实时 DB 上同名过程是另一段代码。stack trace 和“这个过程做什么”取决于你连到哪个 schema。 经验规则:优先选择切片 4 的元数据定制。只有元数据模型确实无法表达客户需求时,才使用切片 5 SQL 覆盖。 ## 重庆展印 `Sp_SalSalesCheck` 的不同点 文件顶部显示: - 它从 `SysSystemSettings` 读取 `'CbxSrcNoCheck'` 行,决定哪些计费类型进入销售对账报表。这是标准过程可能没有暴露的客户特定开关。 - 它调用全局 `Fun_GetLookCustomer(sLoginId, sBrId, sSuId)` helper 做权限作用域,与标准过程一致。 - 它接受与标准过程相同的参数列表(`sLoginId` / `sCustomerId` / `sBrId` / `sSuId` / `bFilter` / `pageNum` / `pageSize` 等),因此框架调用点不变。 未来修订可以与标准 `Sp_SalSalesCheck` 做并排 diff,精确解释分歧业务规则。当前最重要的是结构事实:过程形状和参数列表与标准一致,主体不同。 配套视图 `viw_salsaleschecking_pro.sql` 也出于同一原因存在:当覆盖需要标准没有的 join 形状时,工程师编写客户特定视图,并与过程一起应用到该客户 schema。 ## 本切片引入的概念 - *两条定制通道*:通过**后台**编辑元数据(切片 1、2、4)vs. 直接应用到客户 schema 的原始 SQL 覆盖。 - *客户间 schema 分歧*:同一过程名在不同客户 DB 中可能表示不同过程,影响维护人员分析运行时行为。 ## 参考项 应新增维护人员页:*每客户 SQL 覆盖*,记录 `script/客户//` 约定、手工应用流程、运维影响和审计模式。 ## 待验证项 1. **脚本应用是否真的完全手工?** 还是存在 Quartz job / `DbToDbController` 机制?需要读 `DbToDbServiceImpl.java` 确认。 2. **审计。** 写小脚本连接客户 DB,把每个 `Sp_*` / `viw_*` 主体与标准 diff。意外分歧是运维风险。 3. **并排 `Sp_SalSalesCheck` diff。** 当前只描述结构,未来应纳入实际主体差异,说明重庆展印改变了哪条业务规则以及原因。 4. **生命周期。** 客户升级、恢复、重建 schema 时,每个覆盖如何重新应用?部署章节需要 runbook。