request-lifecycle.md
7.87 KB
元数据驱动的请求生命周期
这张图会反复用到。xly 中每个元数据驱动页面都遵循同一流程;理解它之后,框架其余部分只是主题变奏。
流程
Browser
1. 任意 URL 加载 SPA shell(服务端对每个路径返回同一 shell;
gdsroute 是客户端侧边栏 / deep-link 白名单,不是服务端 404 闸门)
2. 用户点击侧边栏项或在 SPA 内导航
3. SPA 决定加载哪个模块 → 调用 /business/...
GET /xlyEntry/business/getModelBysId/{sModelsId}?sModelsId={sModelsId}
(模块 id 同时出现在 path 和 query 中;controller 绑定路径变量,
但 service 从 @RequestParam map 读取 sModelsId,所以 SPA 也必须在 query string 中传它)
xlyEntry — BusinessBaseController.getModelBysId()
RequestAddParamUtil.addParams(params, userInfo)
→ sBrandsId、sSubsidiaryId、sUserId、sLanguage 等
→ 共 16 个 key(详见 runtime.md)
→ 租户作用域变成下游查询可用的参数。框架元数据读取按 form-id 过滤;
每租户覆盖与业务数据读取才按 sBrandsId / sSubsidiaryId 过滤。
BusinessBaseService.getModelBysId(map)
├── 1. formData
│ └── gdsconfigformmaster(按 sParentId = sModelsId 过滤;
│ gdsmodule 本身不被 SELECT,只通过 id 引用)
│ + LEFT JOIN gdsconfigformpersonalize(每租户覆盖)
│ + 每个 master 的 gdsconfigformslave
│ + 每个 master 的 gdsconfigformcustomslave(每租户覆盖)
├── 2. gdsformconst(仅按 sParentId 过滤;不按租户过滤;
│ sLanguage 决定返回哪一列标签)
├── 3. sysjurisdiction(按用户 / 用户组授权,JOIN
│ sftlogininfojurisdictiongroup + sisjurisdictionclassify;
│ ADMIN 跳过。返回 map key 仍叫 `gdsjurisdiction`,
│ 这个名字有误导性:gdsjurisdiction 表是配置侧动作目录,
│ 不是这里读取的授权表)
├── 4. sysbillnosettings(每租户、每表单)
└── 5. sysreport(每租户、每表单)
返回一个复合 map:
{ formData, gdsformconst, gdsjurisdiction, billnosetting, report }
Browser SPA
使用 formData 渲染表单;使用 gdsformconst 和按控件另取的 SQL 填充下拉项
调用 /business/getBusinessDataByFormcustomId/{formId}
并带 sModelsId={moduleId} 加载实际数据行
xlyEntry — BusinessBaseController.getBusinessDataByFormcustomId()
从 formMaster.sSqlStr / sWhere / sOrder 拼出参数化 SQL,注入
sBrandsId / sSubsidiaryId,并在表单支撑对象(table | view | proc)上执行
用户看到表格
同一流程的时序图
上面的 ASCII 图强调执行顺序;下面的时序图强调谁调用谁。排查真实请求时,后者通常更有用。
sequenceDiagram
autonumber
participant SPA as Browser SPA
participant CTRL as BusinessBaseController
participant SVC as BusinessBaseServiceImpl
participant FORMS as BusinessGdsconfigformsServiceImpl
participant DB as MySQL
participant REDIS as Redis (RedisCacheManager)
SPA->>CTRL: GET /business/getModelBysId/{sModelsId}<br/>?sModelsId=...&Authorization=<bearer>
Note over CTRL: AuthorizationInterceptor.preHandle<br/>从 Redis 解析 UserInfo<br/>RequestAddParamUtil.addParams(16 个 key)
CTRL->>SVC: getModelBysId(map)
Note over SVC: getModelConfigByModleId(继承自 BaseServiceImpl)<br/>编排每个 form-master 的主表单 + 从表加载
SVC->>FORMS: getFormmasterData / getGdsconfigformslaveShow<br/>(form-master + slaves + overlays)
REDIS-->>FORMS: cache hit?
FORMS->>DB: SELECT ... gdsconfigformmaster ⋈ personalize;每个 master 再读 gdsconfigformslave + gdsconfigformcustomslave
DB-->>FORMS: rows
FORMS-->>SVC: formData
SVC->>FORMS: getFormconstData(只按 form-id,不按租户)
FORMS->>DB: SELECT ... gdsformconst WHERE sParentId=...
DB-->>FORMS: rows
FORMS-->>SVC: gdsformconst
alt sUserType != ADMIN
SVC->>FORMS: getJurisdictionData(每用户授权)
FORMS->>DB: SELECT ... sysjurisdiction ⋈ sftlogininfojurisdictiongroup
DB-->>FORMS: rows
FORMS-->>SVC: gdsjurisdiction(map key;源表是 sysjurisdiction)
else ADMIN
Note over SVC: 跳过权限加载
end
SVC->>FORMS: getBillnosettingData
FORMS->>DB: SELECT ... sysbillnosettings WHERE sFormId=... AND tenant
DB-->>FORMS: row
FORMS-->>SVC: billnosetting
SVC->>DB: SELECT ... sysreport WHERE sFormId=... AND tenant
DB-->>SVC: report rows
SVC-->>CTRL: composite Map(5 个 key)
CTRL-->>SPA: AjaxResult{code:1, dataset:{...}}
SPA->>CTRL: POST /business/getBusinessDataByFormcustomId/{formId}<br/>?sModelsId=...
Note over CTRL,SVC: 同一次 RequestAddParamUtil 注入<br/>随后使用每表单 sSqlStr / sWhere / sOrder
CTRL->>DB: 对表单支撑 table/view/proc 执行参数化 SELECT
DB-->>CTRL: rows
CTRL-->>SPA: dataset
图中第 1 行和第 22 行是两个 HTTP 往返。中间全部是服务端工作,SPA 看不到。
五键复合结果
getModelBysId 返回一个 Java Map,按顺序包含这些 key:
| Key | 来源 | SPA 用途 |
|---|---|---|
formData |
gdsconfigformmaster(按 sParentId = sModelsId 过滤)⋈ gdsconfigformpersonalize(每租户覆盖);每个 master 行再加载 gdsconfigformslave + gdsconfigformcustomslave 覆盖。gdsmodule 只通过 id 引用。 |
表单布局本身:每个字段、控件、标签、校验规则 |
gdsformconst |
仅按 sParentId 过滤的 gdsformconst 行;不按租户过滤;sLanguage 决定返回哪一列标签 |
表单级常量:标签、默认值、下拉文本 |
gdsjurisdiction |
用户的 sysjurisdiction 行,或通过 sftlogininfojurisdictiongroup ⋈ sisjurisdictionclassify 读取用户组授权;ADMIN 跳过。map key 名称 gdsjurisdiction 有误导性:那张表是配置侧动作目录,不是这里读取的授权表。 |
按钮和数据权限 |
billnosetting |
该模块的 sysbillnosettings 行(每租户) |
单据编号规则(工单号、报价单号) |
report |
关联到该表单的 sysreport 行(每租户) |
打印模板(jxls Excel、iText PDF) |
不在这个生命周期中的内容
-
保存路径。 保存有自己的端点
POST /business/addUpdateDelBusinessData,把新增 / 更新 / 删除打包进一个请求。见切片 1。 -
打开已有行编辑的读取。 与表格加载使用同一端点
getBusinessDataByFormcustomId,但 body 中带行sId,handler 按请求一行还是多行分支。 -
工作流步骤。 如果模块有活动审批流(
bCheck = 1、gdsmoduleflow已配置、Activiti 流程已部署,并且ConstantUtils.bCheckflowCheck = true),会插入额外步骤。当前 dev DB 没有这些数据;见切片 7(暂缓)。 -
缓存失效。 后台修改元数据行时,保存服务会同步调用
BusinessCleanRedisData/CleanRedisServiceImpl,从共享 Redis 中驱逐 Spring cache region。JMS 的ConsumerChangeGdsModuleThread是另一条基础数据合并通道,不是缓存失效。
其他切片覆盖的变体
- 切片 1:规范实例,带观察到的网络流量。
- 切片 2:多租户过滤如何贯穿每一步。
- 切片 3:视图支撑而非表支撑。
-
切片 4:
gdsconfigformcustomslave合并步骤。 - 切片 5:某租户的存储过程主体被替换时。
读一次,并保持打开
如果后续章节出现你不认识的术语,它几乎一定指向上图中的某个框。回到这里即可。