runtime.md 6.05 KB

The runtime: BusinessBaseController & friends

xlyEntry/src/main/java/com/xly/web/businessweb/ is where the metadata-driven runtime lives. This page is the maintainer's map of the controllers and services that carry most of the generic form runtime.

The load-bearing controllers

Class Package Role Most-cited endpoints
BusinessBaseController web/businessweb/ Universal CRUD for metadata-driven modules. The default API surface for every form. /business/getModelBysId/{moduleId}, /business/getBusinessDataByFormcustomId/{formId}, /business/addUpdateDelBusinessData, /business/getSelectDataBysControlId/{controlId}
BusinessConfigformController web/businessweb/ Per-user / per-group display customization for an existing form, not the base form-definition CRUD. /configform/getConfigformData/{moduleId}, /configform/sHandleConfigform, /configform/sCopyConfigform
GdsmoduleController web/systemweb/ Module-tree and module-definition CRUD used by the builder side. /gdsmodule/getModuleTreePro, /gdsmodule/addGdsmodule, /gdsmodule/updateGdsmodule
GdsconfigformController web/systemweb/ Form-master and form-slave metadata CRUD. endpoints under /gdsconfigform/*
GdsconfigtbController web/systemweb/ Virtual-table master/slave metadata CRUD. endpoints under /gdsconfigtb/*
BusinessTreeGridController web/businessweb/ Tree-grid endpoints. In this branch, the proc-backed path is implemented; the plain getTreeGrid service method is still a stub. /treegrid/getTreeGridByPro/{formId}
GenericProcedureCallController web/businessweb/ Generic stored-procedure invocation by name + parameters. /procedureCall/doGenericProcedureCall
ConfigformPanelController web/businessweb/ Panel-layout persistence in gdsconfigformpanel. /panel/get/{sFormId}, /panel/save/{sFormId}
CheckFlowController web/businessweb/ Activiti workflow surface (approve / reject / view) — only meaningful when workflow is deployed. endpoints under /checkFlow/*

Note that the controllers split across two packages: businessweb/ hosts the runtime-facing endpoints, while systemweb/ hosts the metadata-CRUD endpoints used by the builder side. Both compile into the same xlyEntry WAR.

The four-table read

For any metadata-driven module, the request lifecycle (see Concepts → request lifecycle) boils down to:

public Map<String, Object> getModelBysId(Map<String, Object> map) {
    List<Map<String, Object>> formList = this.getModelConfigByModleId(map);   // 1. join gdsmodule⋈form-master⋈form-slave
    List<Map<String, Object>> fList = businessGdsconfigformsService.getFormconstData(qMap);   // 2. gdsformconst
    List<Map<String, Object>> jList = businessGdsconfigformsService.getJurisdictionData(qMap); // 3. gdsjurisdiction (skipped for ADMIN)
    Map<String, Object> billnosettingMap = businessGdsconfigformsService.getBillnosettingData(param); // 4. sysbillnosettings
    List<Map<String, Object>> reportList = printReportService.getReportData(qMap);            // 5. sysreport
    return composite(formList, fList, jList, billnosettingMap, reportList);
}

BusinessBaseServiceImpl.java. Read this method first; the rest of the runtime is variations on it.

The five-key composite returned

Key Source Frontend uses for
formData gdsmodulegdsconfigformmastergdsconfigformslave (+ overlays) Form layout
gdsformconst gdsformconst Form-level constants, dropdown labels
gdsjurisdiction gdsjurisdiction Per-button / per-data permissions
billnosetting sysbillnosettings Document numbering rules
report sysreport Print templates

The save endpoint

POST /business/addUpdateDelBusinessData bundles add+update+delete in one transactional batch. The frontend supplies sTable and a column map per row, in this shape:

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

When gdsmodule.sSaveProName is empty, the framework's default Add/Update path runs — AddDelUpdCommonServiceImpl.java. When it's populated, the named stored proc is invoked instead.

Multi-tenant boundary

Every controller method's first non-trivial line is:

RequestAddParamUtil.me().addParams(params, userInfo);

xlyPersist/.../RequestAddParamUtil.java, 56 lines (xlyApi has its own near-identical 57-line copy at xlyApi/.../api/util/RequestAddParamUtil.java). This is the single point where the user's session identity (sBrandsId, sSubsidiaryId, sBrId, sSuId, sLoginId, sLanguage, sTeamId, sMachineId, plus eight more — sixteen keys total) is injected into the downstream params map. Every MyBatis query and stored-procedure call that references #{sBrandsId}/#{sSubsidiaryId} is automatically scoped from there.

A new controller method that doesn't call RequestAddParamUtil is a multi-tenant bug.

Security concerns to audit

Two flagged in slices that belong here permanently:

  1. sTable validation in addUpdateDelBusinessData. The frontend names the target table directly. The runtime must cross-check that the supplied table is one of the form's authorised backing tables, or it's a privilege-escalation surface. Confirm the check exists; if it doesn't, raise it as a security ticket. Tracked in the Slice 1 v2 follow-up.
  2. ADMIN bypasses permissions. BusinessBaseServiceImpl skips the gdsjurisdiction load entirely for UserType.ADMIN. ADMIN account governance must come from outside the app.

Cache invalidation

When BACK saves a metadata change, a JMS message fires and ConsumerChangeGdsModuleThread (in xlyErpJmsConsumer) clears the cached metadata on every running node. See cache invalidation on metadata change.