01-hello-world.md 9.58 KB

切片 1 — CRUD 模块(Hello World)

xly 中最简单的端到端动作:打开一个系统模块、查看数据、编辑一行、保存。我们从 URL 栏一直追踪到运行时渲染到表格中的行,中间经过每一层。

本章基于观察到的网络流量和匹配源码。仍为假设的内容(例如尚未实际执行的保存路径)会明确标注。

记录对象

模块 系统常量配置(System Constants Config)
URL 片段 /xtclpz
模块 sId 13
支撑表 gdsformconst(dev 中 2,986 行)
表单 sId 19211681019715574676360040
字段数 10
保存 / 删除过程 无,使用框架默认路径
审批工作流 无(bCheck = 0

这个模块本身就是框架的一部分:它是 PM 编辑系统常量的 后台 页面。记录它具有“元”意义:我们会看到框架如何配置框架。

为什么选这个模块

第一,sId = 13 说明它是靠近根部的基础模块。第二,它没有自定义过程:保存 / 删除使用框架默认 CRUD 路径,这是读者应先学会的规范代码路径。自定义过程属于后续切片。

四个元数据层

运行时读取四张元数据表来渲染页面:

含义
1. 模块目录 gdsmodule 模块树。sId='13' 是本切片对象。
2. URL 白名单(客户端侧) gdsroute URL /xtclpz 在这里注册。SPA 读取该列表决定渲染哪些侧边栏 / deep-link;服务端不会对未注册路径返回 404。
3. 表单布局 gdsconfigformmaster 一行 sParentId='13' 声明表单:支撑表、类型、默认 SQL / where / order、分页和权限。
4. 字段布局 gdsconfigformslave 10 行,sParentId 等于表单 sId。每行是一个字段:控件类型、多语言标签、校验、默认值、下拉 SQL 等。

这次四表读取就是 xly 每个元数据驱动页面的生命周期。理解它之后,框架其余部分只是变体。

追踪与证据

0. 服务形状

后台 URL http://118.178.19.35:8597xlyEntry WAR 提供(观察:每个 /business/* 调用的 context-path 都是 /xlyEntry/)。早期猜测是 xlyApi,这是错误的;xlyApi 是兄弟 WAR,有自己的 application.yml 和 context-path /xlyApi,角色不同。部署章节会覆盖二者拆分。

1. 浏览器 → 服务端(页面 shell)

GET /xtclpz

/xtclpzgdsroute 中的一项,但运行时它只是显示状态,不是路由驱动。登录后直接访问该 URL,不会深链到“系统常量配置”模块,而是加载与其他 URL 相同的 SPA shell,并显示默认落地模块。用户仍需点击侧边栏让 SPA 切换模块。

需要修正的早期假设:gdsroute 不在服务端强制。探测未注册路径 GET /xtclpz_NOT_A_REAL_ROUTE 也返回同一 200 + SPA shell。白名单是客户端构造:SPA 读取 gdsroute 判断愿意挂载哪些侧边栏 / deep-link;真正分发发生在 SPA 加载后。

2. 用户点击侧边栏,SPA 拉取元数据

GET /xlyEntry/business/getModelBysId/13?sModelsId=13   →  200 OK

路径变量和 query 参数都是 13,即 gdsmodule 中的模块 sId。handler 是 BusinessBaseController.getModelBysId()

命名提醒: 参数叫 sModelsId,helper 叫 getModelConfigByModleId(Modle 是 Model 拼写错误),但它们实际都接收模块 sId。原 Javadoc 写“传入窗体sId”,也是错的。阅读代码时可把 Models / Modle / Model 统一理解为“module”。

BusinessBaseServiceImpl.getModelBysId() 返回一个包含五个 key 的复合 map:

Key 来源 内容
formData gdsmodulegdsconfigformmastergdsconfigformslave(+ personalize) 表单布局主干
gdsformconst sBrandsId / sSubsidiaryId / language 过滤的 gdsformconst 表单级常量、标签、默认值、下拉文本
gdsjurisdiction 用户角色的 gdsjurisdiction 按钮和数据权限;ADMIN 用户跳过,管理员看到全部
billnosetting 该模块的 sysbillnosettings 单据编号规则;对 gdsformconst 无关但总会加载
report 关联到该表单的打印模板 打印报表定义

多租户在这次读取中执行:每个子查询都带有从认证 session 注入的 sBrandsIdsSubsidiaryId。租户之间看不到对方元数据。

3. SPA → 服务端(初始数据加载)

POST /xlyEntry/business/getBusinessDataByFormcustomId/19211681019715574676360040?sModelsId=13   →  200 OK

路径变量是表单 sId,query 参数是模块 sId。handler 是 BusinessBaseController.getBusinessDataByFormcustomId()

handler 根据请求 body 是否包含 sGroupList 分支:没有 group-by 就走 getBusinessDataByFormcustomId,有则走 getBusinessDataByGroup。本模块走简单路径。

SQL 由 gdsconfigformmaster.sSqlStr + sWhere + sOrder 组合,并带入用户租户和语言参数后通过 MyBatis 执行。同一路径也处理编辑时按明细 ID 拉取一行。

4. 用户编辑一行并点击保存 {#4-user-edits-a-row-clicks-save}

保存、删除、新增都进入同一通用端点:

POST /xlyEntry/business/addUpdateDelBusinessData?sModelsId={moduleId}

请求体形状:

{
  "addData":    [{"sTable": "<table>", "column": {"sId": "", "...": "..."}}],
  "updateData": [{"sTable": "<table>", "column": {"sId": "", "...": "..."}}],
  "delData":    [{"sTable": "<table>", "column": {"sId": "", "...": "..."}}]
}

三种操作打包成一个原子请求。前端告诉后端要写哪张表哪些列;没有每模块专用写 API,元数据驱动 UI 根据 gdsconfigformmaster.sTbNamegdsconfigformslave 字段列表生成 payload。

  • sSaveProName 为空时,运行时走 AddDelUpdCommonServiceImpl.java 的默认 Add/Update 路径,生成参数化 INSERT / UPDATE / DELETE
  • sSaveProName 非空时,运行时调用指定存储过程。xlyEntry/src/main/resources/templates/templesql/sSaveProName.sql 是工程师编写这类过程时使用的脚手架。

待验证: 真实保存请求体、响应体以及 syslog4j 中的实际 SQL 还未捕获。为了避免修改共享 dev DB 中的框架常量表,本轮没有执行保存。端点、handler 和 payload 形状已从源码确认。

安全相关架构备注: 前端在 payload 中直接提供 sTableBusinessBaseServiceImpl.addUpdateDelBusinessData 会读取该值并分发删除 / 更新 / 新增。类级 sTableNameList 主要作为缓存失效 gate,而不是“该表是否被此表单授权”的 gate。已确认的缓解包括租户作用域自动注入,以及模块可通过 sSaveProNameBefore / sSaveProName 做验证;但权限规则是按钮级,不是表级。净结论:通用保存端点信任前端的 sTable 值,值得维护 ticket。

5. 缓存失效

修改任何四张元数据表中的 gds* 行,都会让每个运行节点失效缓存副本。xly 通过 JMS 消息做到这一点:xlyErpJmsConsumer/.../ConsumerChangeGdsModuleThread.java 监听“module changed”事件并清相关 Redis key。见元数据变更后的缓存失效

6. 浏览器确认

保存返回成功;前端要么就地 patch 该行,要么用同一 getBusinessDataByFormcustomId 端点重新拉取表格。追踪结束。

本切片引入的概念

本切片使用的参考

配置人员:

维护人员:

待验证项 {#open-verification-items}

  1. 真实捕获一次保存。 端点、handler 和 payload 形状已从源码确认,但实际保存请求体尚未捕获。需要打开模块、点击新增、填写、保存,并捕获 JSON body 和响应。
  2. 保存 / 删除发出的精确 SQL,从 syslog4j 或 MyBatis debug log 捕获。
  3. addUpdateDelBusinessData 中的 sTable 校验。 已关闭:运行时不会把前端提供的 sTable 与表单授权支撑表交叉检查。已作为维护关注点记录。

前两项属于切片 1 v2,需要对 dev DB 做实际写入;为避免修改共享状态而暂缓。