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 行,或通过 sftlogininfojurisdictiongroupsisjurisdictionclassify 读取用户组授权;ADMIN 跳过。map key 名称 gdsjurisdiction 有误导性:那张表是配置侧动作目录,不是这里读取的授权表。 按钮和数据权限
billnosetting 该模块的 sysbillnosettings 行(每租户) 单据编号规则(工单号、报价单号)
report 关联到该表单的 sysreport 行(每租户) 打印模板(jxls Excel、iText PDF)

不在这个生命周期中的内容

  • 保存路径。 保存有自己的端点 POST /business/addUpdateDelBusinessData,把新增 / 更新 / 删除打包进一个请求。见切片 1
  • 打开已有行编辑的读取。 与表格加载使用同一端点 getBusinessDataByFormcustomId,但 body 中带行 sId,handler 按请求一行还是多行分支。
  • 工作流步骤。 如果模块有活动审批流(bCheck = 1gdsmoduleflow 已配置、Activiti 流程已部署,并且 ConstantUtils.bCheckflowCheck = true),会插入额外步骤。当前 dev DB 没有这些数据;见切片 7(暂缓)
  • 缓存失效。 后台修改元数据行时,保存服务会同步调用 BusinessCleanRedisData / CleanRedisServiceImpl,从共享 Redis 中驱逐 Spring cache region。JMS 的 ConsumerChangeGdsModuleThread 是另一条基础数据合并通道,不是缓存失效。

其他切片覆盖的变体

  • 切片 1:规范实例,带观察到的网络流量。
  • 切片 2:多租户过滤如何贯穿每一步。
  • 切片 3:视图支撑而非表支撑。
  • 切片 4gdsconfigformcustomslave 合并步骤。
  • 切片 5:某租户的存储过程主体被替换时。

读一次,并保持打开

如果后续章节出现你不认识的术语,它几乎一定指向上图中的某个框。回到这里即可。