Commit bbcde3e6069b61413751a843e8b2616ed31bda07

Authored by zichun
1 parent ae638619

docs: en wiki — five-pass verification audit, fix divergences inline

Audit every concrete claim in the 41 hand-written en pages against the
three primary sources (DB, source on cleanup branch, source-tree
inventory). Fix divergent claims in place; preserve framing where
verified.

Substantive corrections:

- request-lifecycle / runtime / slice 01: the metadata read sources from
  five tables/families (gdsconfigformmaster + overlays, gdsformconst,
  sysjurisdiction, sysbillnosettings, sysreport), not four. The map key
  `gdsjurisdiction` is misleading — the per-user grant read queries
  `sysjurisdiction`; `gdsjurisdiction` is the builder-side action
  catalogue. `gdsformconst`, `gdsconfigformmaster`, `gdsconfigformslave`
  are NOT tenant-scoped; they filter by form-id only.
- multi-tenancy: four metadata tables (gdsformconst, gdsmodule,
  gdsconfigformmaster, gdsconfigformslave) are an explicit exception to
  the "every table tenant-scoped" promise — `sTableNameList` strips
  sBrandsId/sSubsidiaryId from writes against them.
- sSaveProName / sSaveProNameBefore are pre/post-save HOOKS on top of
  the always-running base path (BusinessBaseServiceImpl.add/update),
  not either/or branches. Default add/update path is in
  BusinessBaseServiceImpl, not AddDelUpdCommonServiceImpl.
- cache-invalidation: redis cache is cleared synchronously in BACK via
  @CacheEvict on CleanRedisServiceImpl during save. The JMS
  CHANGE_GDS_MODULE queue triggers PRO_ERPMERGEBASEGDSMODULE (base-data
  merge), NOT cache invalidation despite the name. Cross-node
  coherence open question (no custom CacheManager bean configured).
- messaging: enumerate all 24 P2pQueue destinations grouped by intent;
  fix CHANGE_GDS_MODULE description; clarify single Consumer.java with
  24 @JmsListener methods (not 24 listener classes).
- API paths: /checkflow lowercase (mapping value, not class name);
  /procedureCall/doGenericProcedureCall (not /business/genericProcedureCall*).
- tech-stack: Druid 6 java imports + 16 yml mentions (was 25 conflated);
  fastjson per-module xlyInterface 9 (was 10); commons-lang3 39 (was 41);
  @Document classes 20 PLAT_* + 2 DIKE_TEST* (was "all PLAT_*"); xlyPersist
  activiti hit is IdGen.java (was BaseDao.java); add Springfox to
  declared-but-no-imports table; reconcile module list to 11 framework
  core + xlyPlc plugin + xlyPlatConstant utility.
- index.md: clarify xlyFace as "in build, not documented"; add xlyErpTask
  / xlyPlatTask scheduler bullet; correct MongoDB framing (caller is in
  xlyPersist with no consumers, not xlyPlat*); add xlyPlc note; extend
  backup-table OOS to cover *_copy1 / *_history / *YYYYMMDD[HHMMSS].
- deployment.md: split deployable Boot apps from library modules;
  enumerate 12 commented-out includes (was 3); remove xlyPlatConstant from
  out-of-scope Plat* list; split profile permutations by service.
- activiti.md: add xlyApi to 5.17 dependency list; replace speculative
  BPMN path hint with verified state; name actual ActivitiConfig.java;
  note act_id_* are views projecting xly users into Activiti shapes.
- api-reference/external.md: fix bearer-token validation flow (sysapibrand
  via AES-decrypted corpid, not sysapithirdtoken); /online/* are page
  renders not API execution; /pro/* mostly returns Thymeleaf views; mark
  sysapidbtodb as xlyFlow-owned; /token/getToken accepts GET and POST.
- api-reference/webhooks.md: add Swagger Docket caveat (UI shell ships but
  no Docket bean → /v2/api-docs effectively empty); flag /send/sendQw as
  stub (returns "ok").
- slices/03-report.md: fix dir path xlyEntry/com/xly/report/ →
  xlyEntry/com/xly/web/report/; reframe PrintReportControllerOld as dead
  source (file body fully commented out).
- concepts/modules-forms-vtables.md: add 22-prefix glossary table
  (gds/sys/sis/sft/ele/mft/sal/quo/acc/pur/ops/cah/sgd/ept/mit/pit/qly/
  kpi/udf/viw_/plat_/ai_/act_/qrtz_) so a maintainer can enumerate
  business-data domains at a glance.
- concepts/master-slave.md: disambiguate document-row pattern from
  DataSource master/slave (different concept, name overlap).
- proc-dispatch.md: add proc-name molds (Sp_*_BeforeSave/AfterSave/
  SaveReturn, sp_btn_*, PRO_ERPMERGE*) + function-layer paragraph
  (Fun_*/Fn_*/get_*; SQL-called, not Java-dispatched).
- concepts/index.md: schema label MySQL\nxlyweberp → xlyweberp_*.

Pass E (live behavioural traces) deferred — source/DB-side audit was
thorough; live traces best done as a follow-up sweep against a running
instance.
en/docs/api-reference/external.md
@@ -31,8 +31,12 @@ The flow: @@ -31,8 +31,12 @@ The flow:
31 2. Look up the `sysapi` row keyed by `sApiCode` (via 31 2. Look up the `sysapi` row keyed by `sApiCode` (via
32 `ApiServiceImpl.invoke` → 32 `ApiServiceImpl.invoke` →
33 `SELECT … FROM sysapi WHERE sApiCode = #{sApiCode}`). 33 `SELECT … FROM sysapi WHERE sApiCode = #{sApiCode}`).
34 -3. If the row's `bHasToken` flag is set, validate the token against  
35 - `sysapithirdtoken` (or the equivalent token store the row points at). 34 +3. If the row's `bHasToken` flag is set, AES-decrypt the bearer token to
  35 + recover the `corpid`, then validate that `corpid` against `sysapibrand`
  36 + via `BrandServiceImpl.selectByCorpid`. If the brand row's `iLossTime`
  37 + is non-zero, also check the token's embedded timestamp hasn't expired.
  38 + (`sysapithirdtoken` is for *outbound* tokens — xly calling third-party
  39 + APIs — not for validating inbound bearer tokens here.)
36 4. Run the SQL template stored in `sysapi.sDataSql` with the request 40 4. Run the SQL template stored in `sysapi.sDataSql` with the request
37 body merged into the parameter map. 41 body merged into the parameter map.
38 5. Log the call to `sysapilog`. 42 5. Log the call to `sysapilog`.
@@ -67,7 +71,7 @@ dry-run against the test fixture in `sDataTest`. @@ -67,7 +71,7 @@ dry-run against the test fixture in `sDataTest`.
67 71
68 | Endpoint | Method | Purpose | 72 | Endpoint | Method | Purpose |
69 |---|---|---| 73 |---|---|---|
70 -| `/token/getToken?corpid=&corpsecret=` | POST | Issue a bearer token for an integrator's `(corpid, corpsecret)` pair. | 74 +| `/token/getToken?corpid=&corpsecret=` | GET / POST | Issue a bearer token for an integrator's `(corpid, corpsecret)` pair. (Mapping is method-agnostic.) |
71 75
72 The token returned is what `/api/invoke/{sApiCode}` expects in 76 The token returned is what `/api/invoke/{sApiCode}` expects in
73 `Authorization`. The full implementation is in 77 `Authorization`. The full implementation is in
@@ -84,13 +88,15 @@ These are smaller specialised APIs hosted in the same WAR: @@ -84,13 +88,15 @@ These are smaller specialised APIs hosted in the same WAR:
84 88
85 | Endpoint root | Controller | Purpose | 89 | Endpoint root | Controller | Purpose |
86 |---|---|---| 90 |---|---|---|
87 -| `/online/api/{sApiCode}` | `OnlineController` | Read-only execution of a `sysapi` row (no write). Useful for public-data endpoints. |  
88 -| `/online/onlineword/{sApiCode}` | `OnlineController` | Variant returning a "word"-style template payload. |  
89 -| `/online/onlinelist`, `/online/getToken` | `OnlineController` | Listing of online APIs and their token issuance. |  
90 -| `/pro/get/{sProName}` | `ProContentController` | Fetch metadata about a stored procedure by name. |  
91 -| `/pro/getData/{sProName}` | `ProContentController` | Execute the named stored procedure and return its result-set. |  
92 -| `/pro/alert/{redisId}`, `/pro/getAlertValue/{redisId}` | `ProContentController` | Read alert/notification values keyed by Redis id. |  
93 -| `/pro/executeSql` | `ProContentController` | Execute a parameterised SQL template (admin-tier). | 91 +| `/online/api/{sApiCode}` | `OnlineController` | Renders the BACK in-browser API debug/console page for the given `sysapi` row (returns a Thymeleaf view, not API execution). |
  92 +| `/online/onlineword/{sApiCode}` | `OnlineController` | Renders the "word"-style API documentation page. |
  93 +| `/online/onlinelist` | `OnlineController` | Renders the online-API listing page. |
  94 +| `/online/getToken` | `OnlineController` | Renders the in-browser token-acquisition helper page. |
  95 +| `/pro/get/{sProName}` | `ProContentController` | Renders the BACK page that displays a stored procedure's source. |
  96 +| `/pro/getData/{sProName}` | `ProContentController` | Returns the stored procedure's source text (not its result-set). |
  97 +| `/pro/alert/{redisId}` | `ProContentController` | Renders the alert/notification display page for the given Redis key. |
  98 +| `/pro/getAlertValue/{redisId}` | `ProContentController` | Returns the value cached in Redis under `redisId`. |
  99 +| `/pro/executeSql` | `ProContentController` | Executes a `sSql` payload directly (admin-tier dev tool). |
94 | `/thirdparty/*` | `ThirdPartyController` | CRUD over third-party-API definitions and the `checkPartyApi` validator. Backed by `sysapithirdparty`. | 100 | `/thirdparty/*` | `ThirdPartyController` | CRUD over third-party-API definitions and the `checkPartyApi` validator. Backed by `sysapithirdparty`. |
95 | `/thirdtoken/*` | `ThirdTokenController` | CRUD over outbound-token configs. Backed by `sysapithirdtoken`. | 101 | `/thirdtoken/*` | `ThirdTokenController` | CRUD over outbound-token configs. Backed by `sysapithirdtoken`. |
96 | `/brand/*` | `BrandController` | CRUD over the partner-supplier list (`sysapibrand`). | 102 | `/brand/*` | `BrandController` | CRUD over the partner-supplier list (`sysapibrand`). |
@@ -110,8 +116,8 @@ These tables hold the API metadata. All carry `sBrandsId` / @@ -110,8 +116,8 @@ These tables hold the API metadata. All carry `sBrandsId` /
110 | `sysapibrand` | Partner / supplier directory. | 116 | `sysapibrand` | Partner / supplier directory. |
111 | `sysapithirdparty` | Outbound third-party endpoint definitions. | 117 | `sysapithirdparty` | Outbound third-party endpoint definitions. |
112 | `sysapithirdtoken` | Outbound third-party token configs. | 118 | `sysapithirdtoken` | Outbound third-party token configs. |
113 -| `sysapidbtodb` | DB-to-DB sync API definitions. |  
114 -| `sysapidbtodblog` | DB-to-DB sync run log. | 119 +| `sysapidbtodb` | DB-to-DB sync API definitions. **Owned by `xlyFlow`'s `DbToDbController`, not xlyApi** — listed here because the table lives in xlyApi's `sysapi.sql`. |
  120 +| `sysapidbtodblog` | DB-to-DB sync run log. Same — written by xlyFlow. |
115 121
116 ## How an integrator uses this 122 ## How an integrator uses this
117 123
en/docs/api-reference/internal.md
@@ -52,15 +52,17 @@ virtual tables) there is a parallel surface in @@ -52,15 +52,17 @@ virtual tables) there is a parallel surface in
52 | `/treegrid/*` | `BusinessTreeGridController` | Tree-grid endpoints (the proc-backed path is implemented in this branch). | 52 | `/treegrid/*` | `BusinessTreeGridController` | Tree-grid endpoints (the proc-backed path is implemented in this branch). |
53 | `/procedureCall/*` | `GenericProcedureCallController` | Generic stored-procedure invocation by name + parameters — see [generic procedure dispatch](../reference/maintainer/proc-dispatch.md). | 53 | `/procedureCall/*` | `GenericProcedureCallController` | Generic stored-procedure invocation by name + parameters — see [generic procedure dispatch](../reference/maintainer/proc-dispatch.md). |
54 | `/panel/*` | `ConfigformPanelController` | Panel-layout persistence in `gdsconfigformpanel`. | 54 | `/panel/*` | `ConfigformPanelController` | Panel-layout persistence in `gdsconfigformpanel`. |
55 -| `/checkFlow/*` | `CheckFlowController` | Activiti workflow surface (approve / reject / view) — only meaningful in deployments that run a flow. | 55 +| `/checkflow/*` | `CheckFlowController` | Activiti workflow surface (approve / reject / view) — only meaningful in deployments that run a flow. The class file is `CheckFlowController.java` (camelCase) but the `@RequestMapping` value is all-lowercase `/checkflow`. |
56 56
57 ## Reporting and printing 57 ## Reporting and printing
58 58
59 The print surface lives under `xlyEntry/src/main/java/com/xly/web/report/`: 59 The print surface lives under `xlyEntry/src/main/java/com/xly/web/report/`:
60 60
61 - `PrintReportController` — current jxls / iText print path. 61 - `PrintReportController` — current jxls / iText print path.
62 -- `PrintReportControllerOld` — legacy print path retained for older  
63 - templates. 62 +- `PrintReportControllerOld.java` — file exists but its class body is
  63 + fully commented out (and the commented-out class inside is named
  64 + `PrintReportController`, not `*Old`). It is dead source kept for
  65 + reference, not an active controller.
64 66
65 The frontend's "打印" / "导出" buttons hit these controllers, which load a 67 The frontend's "打印" / "导出" buttons hit these controllers, which load a
66 template from `sysreport`, run the matching view-backed query, and stream 68 template from `sysreport`, run the matching view-backed query, and stream
en/docs/api-reference/messaging.md
@@ -5,31 +5,73 @@ runs two message brokers, each with a different role: @@ -5,31 +5,73 @@ runs two message brokers, each with a different role:
5 5
6 | Broker | Used for | Producer | Consumer | 6 | Broker | Used for | Producer | Consumer |
7 |---|---|---|---| 7 |---|---|---|---|
8 -| **ActiveMQ / JMS** | Cache invalidation, in-cluster fan-out events. The metadata-change pipeline ([cache invalidation](../reference/maintainer/cache-invalidation.md)) rides on this. | `xlyErpJmsProductor` | `xlyErpJmsConsumer` | 8 +| **ActiveMQ / JMS** | In-cluster fan-out events: base-data merge jobs (consolidating per-tenant rows into flattened lookup tables) and document-update / document-delete notifications. **Despite the historical naming of one queue, this channel is NOT used for Redis cache invalidation** — see [Cache invalidation on metadata change](../reference/maintainer/cache-invalidation.md) for the actual cache-evict path. | `xlyErpJmsProductor` | `xlyErpJmsConsumer` |
9 | **RocketMQ** | Other integration flows where the ActiveMQ assumptions don't fit. | `RocketMQServiceImpl` (in `xlyBusinessService`) | (varies — service-specific) | 9 | **RocketMQ** | Other integration flows where the ActiveMQ assumptions don't fit. | `RocketMQServiceImpl` (in `xlyBusinessService`) | (varies — service-specific) |
10 10
11 This page is a pointer rather than a deep dive — exact queue names and 11 This page is a pointer rather than a deep dive — exact queue names and
12 payloads are documented at the consumer-thread level in 12 payloads are documented at the consumer-thread level in
13 `xlyErpJmsConsumer/src/main/java/com/xly/xlyerpjmsconsumer/`. 13 `xlyErpJmsConsumer/src/main/java/com/xly/xlyerpjmsconsumer/`.
14 14
15 -## ActiveMQ / JMS — the cache-invalidation channel 15 +## ActiveMQ / JMS — base-data merge + fan-out channel
16 16
17 Producer-side queue declarations live in 17 Producer-side queue declarations live in
18 `xlyErpJmsProductor/src/main/java/com/xly/xlyerpjmsproductor/config/P2pQueue.java`. 18 `xlyErpJmsProductor/src/main/java/com/xly/xlyerpjmsproductor/config/P2pQueue.java`.
19 -Notable destinations the framework uses today (read the file for the  
20 -full list): 19 +The full set is **24 destinations**, grouped by intent:
  20 +
  21 +### Module / control (2)
  22 +
  23 +| Constant | Purpose |
  24 +|---|---|
  25 +| `ERP_JMS_ACTIVEMQ_CHANGE_GDS_MODULE` | "Module metadata changed" — `ConsumerChangeGdsModuleThread` runs the stored proc `PRO_ERPMERGEBASEGDSMODULE` to merge per-tenant `gdsmodule` rows into a flattened base lookup table. **Does not invalidate Redis caches** despite the name — Redis cache eviction happens synchronously via `@CacheEvict` in BACK during save. See [Cache invalidation on metadata change](../reference/maintainer/cache-invalidation.md). |
  26 +| `ERP_JMS_ACTIVEMQ_CHANGE_WORK_ORDER_CONTROL` | Work-order control state changed (status/aggregate flags) — fan-out for downstream recalculation. |
  27 +
  28 +### Document operations (6)
  29 +
  30 +| Constant | Purpose |
  31 +|---|---|
  32 +| `ERP_JMS_ACTIVEMQ_UPD_SALE_ORDER` / `_UPD_WORK_ORDER` / `_UPD_PRODUCTION_REPORT` | "Document was updated" notifications consumed by background workers (totals recalculation, downstream invalidations). |
  33 +| `ERP_JMS_ACTIVEMQ_DEL_SALE_ORDER` / `_DEL_WORK_ORDER` / `_DEL_PRODUCTION_REPORT` | Document-delete notifications. |
  34 +
  35 +### Element-master change fan-out (7) — `CHANGE_ELE_*`
  36 +
  37 +| Constant | Purpose |
  38 +|---|---|
  39 +| `_CHANGE_ELE_CUSTOMER` | Customer-master change. |
  40 +| `_CHANGE_ELE_EMPLOYEE` | Employee-master change. |
  41 +| `_CHANGE_ELE_MACHINE` | Shop-floor machine-master change. |
  42 +| `_CHANGE_ELE_MATERIALS` | Materials-master change. |
  43 +| `_CHANGE_ELE_PRODUCT` | Product-master change. |
  44 +| `_CHANGE_ELE_PROCESS` | Process-master change. |
  45 +| `_CHANGE_ELE_TEAM` | Team-master change. |
  46 +
  47 +### System-info / lookup-table change fan-out (9) — `CHANGE_SIS_*`
21 48
22 | Constant | Purpose | 49 | Constant | Purpose |
23 |---|---| 50 |---|---|
24 -| `ERP_JMS_ACTIVEMQ_CHANGE_GDS_MODULE` | "Module metadata changed" — triggers `ConsumerChangeGdsModuleThread` to bust the relevant Redis caches across nodes. See [Cache invalidation on metadata change](../reference/maintainer/cache-invalidation.md). |  
25 -| `ERP_JMS_ACTIVEMQ_CHANGE_ELE_CUSTOMER` | Customer-master change fan-out. |  
26 -| `ERP_JMS_ACTIVEMQ_CHANGE_ELE_EMPLOYEE` | Employee-master change fan-out. |  
27 -| `ERP_JMS_ACTIVEMQ_CHANGE_ELE_MACHINE` | Shop-floor machine-master change fan-out. |  
28 -| `ERP_JMS_ACTIVEMQ_UPD_SALE_ORDER`, `ERP_JMS_ACTIVEMQ_UPD_WORK_ORDER`, `ERP_JMS_ACTIVEMQ_UPD_PRODUCTION_REPORT` | "Document was updated" notifications consumed by background workers (totals recalculation, downstream invalidations). |  
29 -| `ERP_JMS_ACTIVEMQ_DEL_SALE_ORDER`, `ERP_JMS_ACTIVEMQ_DEL_WORK_ORDER`, `ERP_JMS_ACTIVEMQ_DEL_PRODUCTION_REPORT` | Document-delete notifications. |  
30 -  
31 -Each destination has a corresponding `Consumer*Thread` class under  
32 -`xlyErpJmsConsumer/.../thread/` that handles the message asynchronously. 51 +| `_CHANGE_SIS_CUSTOMER_CLASSIFY` | Customer-classification change. |
  52 +| `_CHANGE_SIS_DELIVER` | Delivery-method change. |
  53 +| `_CHANGE_SIS_FORMULA` | Calculation-formula change. |
  54 +| `_CHANGE_SIS_PAYMENT` | Payment-method change. |
  55 +| `_CHANGE_SIS_PROCESS_CLASSIFY` | Process-classification change. |
  56 +| `_CHANGE_SIS_PRODUCT_CLASSIFY` | Product-classification change. |
  57 +| `_CHANGE_SIS_SALES_MAN` | Sales-personnel change. |
  58 +| `_CHANGE_SIS_TAX` | Tax-rate change. |
  59 +| `_CHANGE_SIS_WORK_CENTER` | Work-centre change. |
  60 +
  61 +(Constant prefixes elided to `_…` after the first table — the full literal is `ERP_JMS_ACTIVEMQ_…`.)
  62 +
  63 +### Listener side
  64 +
  65 +`xlyErpJmsConsumer/.../consumer/Consumer.java` is a single class that
  66 +hosts **all 24 `@JmsListener` methods** — one per destination. Each
  67 +method dispatches the payload to a corresponding `Consumer*Thread`
  68 +class under `xlyErpJmsConsumer/.../thread/`, which executes the
  69 +domain-specific work (typically calling a `PRO_ERPMERGEBASE*` stored
  70 +proc that consolidates per-tenant rows into a flattened base lookup
  71 +table) asynchronously. There is *one* listener class with 24 methods,
  72 +*not* 24 listener classes. None of the consumer threads invoke
  73 +`@CacheEvict` or `cleanRedis*` — Redis cache invalidation is
  74 +synchronous in BACK during save, see [cache-invalidation.md](../reference/maintainer/cache-invalidation.md).
33 75
34 ## RocketMQ — other flows 76 ## RocketMQ — other flows
35 77
en/docs/api-reference/webhooks.md
@@ -21,6 +21,14 @@ http://<host>/xlyInterface/swagger-ui.html @@ -21,6 +21,14 @@ http://<host>/xlyInterface/swagger-ui.html
21 (or the equivalent JSON descriptor at 21 (or the equivalent JSON descriptor at
22 `http://<host>/xlyInterface/v2/api-docs`). 22 `http://<host>/xlyInterface/v2/api-docs`).
23 23
  24 +> **Caveat:** the project pulls the SpringFox jars but does **not**
  25 +> register a `Docket` bean (no `@EnableSwagger2` or
  26 +> `@Bean Docket api()` anywhere in `xly-src`). The `swagger-ui.html`
  27 +> shell is served from the jar's static resources, but `/v2/api-docs`
  28 +> returns an effectively empty descriptor — the UI is "the dependency
  29 +> ships" rather than "a populated try-it-out console". A maintainer
  30 +> who wants the live API listed has to add a `Docket` bean.
  31 +
24 ## The data-driven receiver — `/interfaceDefine/*` 32 ## The data-driven receiver — `/interfaceDefine/*`
25 33
26 Mirror of the [external API's `/api/invoke`](external.md) pattern, but 34 Mirror of the [external API's `/api/invoke`](external.md) pattern, but
@@ -49,7 +57,7 @@ they have to match a partner&#39;s fixed URL spec. @@ -49,7 +57,7 @@ they have to match a partner&#39;s fixed URL spec.
49 | `/Pull` | POST | Vendor pull-pattern receiver. | 57 | `/Pull` | POST | Vendor pull-pattern receiver. |
50 | `/getKey/{key}` | GET | Public key fetch for a partner-named `key`. | 58 | `/getKey/{key}` | GET | Public key fetch for a partner-named `key`. |
51 | `/getKeyTest` | GET | Test-mode variant of `/getKey`. | 59 | `/getKeyTest` | GET | Test-mode variant of `/getKey`. |
52 -| `/send/sendQw` | POST | Enterprise-WeChat (企业微信) outbound message. | 60 +| `/send/sendQw` | POST | Enterprise-WeChat (企业微信) outbound message. **Stub on this branch** — the method body in `SendQwController` is `return "ok";`; scaffolded for token-fetch but not finished. |
53 61
54 Handlers: `xlyInterface/src/main/java/com/xly/web/WX_VendorWeb.java` 62 Handlers: `xlyInterface/src/main/java/com/xly/web/WX_VendorWeb.java`
55 and `xlyInterface/src/main/java/com/xly/wechat/test/SendQwController.java`. 63 and `xlyInterface/src/main/java/com/xly/wechat/test/SendQwController.java`.
en/docs/concepts/api-surface.md
@@ -35,7 +35,7 @@ sacrifice clarity: @@ -35,7 +35,7 @@ sacrifice clarity:
35 35
36 ## What each tier looks like at runtime 36 ## What each tier looks like at runtime
37 37
38 -- **Internal** — see [the four-table read](../reference/maintainer/runtime.md#the-four-table-read). One 38 +- **Internal** — see [the five-key read](../reference/maintainer/runtime.md#the-five-key-read). One
39 endpoint (`/business/getModelBysId`) returns the entire form layout; 39 endpoint (`/business/getModelBysId`) returns the entire form layout;
40 another (`/business/addUpdateDelBusinessData`) writes any row in any 40 another (`/business/addUpdateDelBusinessData`) writes any row in any
41 table the metadata names. Few endpoints, generic shapes. 41 table the metadata names. Few endpoints, generic shapes.
en/docs/concepts/index.md
@@ -27,7 +27,7 @@ flowchart TB @@ -27,7 +27,7 @@ flowchart TB
27 XMSG[/"xlyMsg<br/>library"/] 27 XMSG[/"xlyMsg<br/>library"/]
28 end 28 end
29 29
30 - DB[("MySQL<br/>xlyweberp")] 30 + DB[("MySQL<br/>xlyweberp_*")]
31 REDIS[(Redis)] 31 REDIS[(Redis)]
32 AMQ([ActiveMQ]) 32 AMQ([ActiveMQ])
33 XEJMSC[xlyErpJmsConsumer] 33 XEJMSC[xlyErpJmsConsumer]
en/docs/concepts/master-slave.md
1 # The master / slave document pattern 1 # The master / slave document pattern
2 2
  3 +> **Two unrelated "master / slave" concepts coexist in this codebase.**
  4 +> This page is about the **document-row** pattern: one header row plus N
  5 +> detail rows for a quotation / sales order / work order. The
  6 +> **DataSource** master / slave (write-vs-read connection routing via
  7 +> `MasterDataSourceConfig` / `SlaveDataSourceConfig` in `xlyApi`, paired
  8 +> with `MasterBaseMapper.xml` / `SlaveBaseMapper.xml` in `xlyPersist`) is
  9 +> a different concept covered in the [Tech-stack HikariCP row](../reference/maintainer/tech-stack.md#3-cache-in-memory)
  10 +> and indirectly in the runtime page. The two senses overlap in name only.
  11 +
3 Almost every business document in xly — a quotation, a sales order, a work 12 Almost every business document in xly — a quotation, a sales order, a work
4 order, a payment voucher — is stored as **one header row plus N detail 13 order, a payment voucher — is stored as **one header row plus N detail
5 rows**. xly's term for this is **master / slave**. The master holds the 14 rows**. xly's term for this is **master / slave**. The master holds the
en/docs/concepts/modules-forms-vtables.md
@@ -91,3 +91,41 @@ documented in [Slice 1](../slices/01-hello-world.md) — knows how to @@ -91,3 +91,41 @@ documented in [Slice 1](../slices/01-hello-world.md) — knows how to
91 render any module / form / virtual-table combination. There is no 91 render any module / form / virtual-table combination. There is no
92 per-module Java code. PMs creating new modules are creating new rows; 92 per-module Java code. PMs creating new modules are creating new rows;
93 they are not creating new code paths. 93 they are not creating new code paths.
  94 +
  95 +## Business-data table prefixes
  96 +
  97 +The wiki treats business modules as illustrations rather than subjects,
  98 +but the schema names them in a regular pattern. A maintainer can map
  99 +any business-data table to its domain by the three-letter prefix:
  100 +
  101 +| Prefix | Domain | Sample tables (live count) |
  102 +|---|---|---|
  103 +| `gds` | Framework metadata (modules, forms, fields, permissions, parameters) | `gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`, `gdsjurisdiction`, `gdsroute`, `gdsformconst`, `gdsparameter`, … |
  104 +| `sys` | Framework system (numbering, jurisdiction grants, reports, search, billing settings) — distinct from `gds*` "definition" tier | `sysjurisdiction`, `sysbillnosettings`, `sysreport`, `syssearch`, `sysapi`, `SysSystemSettings`, … (66 tables) |
  105 +| `sis` | Shared lookup tables / classifiers backing dropdowns | `sisbank`, `siscolor`, `sisversionflow`, `sisjurisdictionclassify`, … (78 tables) |
  106 +| `sft` | Login-session / group-permission link tables | `sftlogininfo*`, `sftlogininfojurisdictiongroup`, … (8 tables) |
  107 +| `ele` | Master data ("element"): customer, employee, machine, materials, product, process, semigoods, costframe | `elecustomer*`, `eleemployee*`, `elemachine*`, `elematerials*`, `eleproduct*`, … (88 tables) |
  108 +| `mft` | Manufacturing: work-order, production-plan, production-report | `mftworkordermaster`, `mftproductionplan*`, `mftproductionreport*`, … (72 tables) |
  109 +| `sal` | Sales | `salsalesordermaster`, `salsalesorderslave`, `salsalesorderprocess`, … (65 tables) |
  110 +| `quo` | Quotation | `quoquotationmaster`, `quoquotationslave`, `quoquotationcalc_tmp`, … (12 tables) |
  111 +| `acc` | Accounting | `accordercostanalysis`, `accordercostanalysisoperation`, … (31 tables) |
  112 +| `pur` | Purchasing | `purpurchaseapply`, `purpurchasearrive`, `purpurchasechecking`, … (28 tables) |
  113 +| `ops` | Outside-processing / outsourcing | `opsoutsidearrive`, `opsoutsidechecking`, `opsoutsideinstore`, … (23 tables) |
  114 +| `cah` | Cashier / financial | `cahcashierinit`, `cahcostchange`, `cahpayment`, `cahreceipt`, … (22 tables) |
  115 +| `sgd` | Semi-goods (半成品) | `sgdsemigoodscheck`, `sgdsemigoodsinstore`, `sgdsemigoodsmatchbill`, … (21 tables) |
  116 +| `ept` | Equipment / machine fixed assets | `eptmachinefixedborrow`, `eptmachinefixedchange`, `eptmachinefixedinstore`, … (21 tables) |
  117 +| `mit` | Materials inventory transactions | `mitmaterialsadjust`, `mitmaterialscheck`, `mitmaterialsinstore`, … (19 tables) |
  118 +| `pit` | Product inventory transactions | `pitproductadjust`, `pitproductbarcode`, `pitproductcheck`, `pitproductinstore`, … (18 tables) |
  119 +| `qly` | Quality testing | `qlycomematerialstest`, `qlyproducttest`, `qlyprocesstest`, … (8 tables) |
  120 +| `kpi` | KPI tracking | `kpimaster`, `kpidetail`, `kpimoduleuserday`, … (7 tables) |
  121 +| `udf` | User-defined / generic-voucher framework | `udfaccountno`, `udfvouchermaster`, `udfvouchertemplatemaster`, … (5 tables) |
  122 +| `viw_` / `Viw_` | Database **views** (case inconsistent across schema) | `viw_mftworkorderprocess`, `viw_corebusinessreport`, `viw_accordercostanalysisnew`, … (311 views in total) |
  123 +| `plat_` | B2B printing-platform layer (out of scope per [index](../index.md#whats-out-of-scope)) | 92 tables — not documented here |
  124 +| `ai_` | AI / LLM features (out of scope) | 7 tables — not documented here |
  125 +| `act_`, `qrtz_` | Third-party schemas (Activiti workflow, Quartz scheduler) | covered transitively under [Activiti](../reference/maintainer/activiti.md) and [tech-stack Quartz](../reference/maintainer/tech-stack.md#4-workflow-scheduling) |
  126 +
  127 +The business-domain prefixes (`ele`, `mft`, `sal`, `quo`, `acc`, `pur`,
  128 +`ops`, `cah`, `sgd`, `ept`, `mit`, `pit`, `qly`, `kpi`, `udf`) and
  129 +their slaves all follow the same metadata-driven runtime — there is no
  130 +per-prefix Java code, just rows in `gdsconfigformmaster` /
  131 +`gdsconfigformslave` pointing at each backing table or view.
en/docs/concepts/multi-tenancy.md
@@ -16,10 +16,16 @@ two-paragraph summary you can link from anywhere. @@ -16,10 +16,16 @@ two-paragraph summary you can link from anywhere.
16 | **`sSubsidiaryId`** (子公司ID) | almost every business row | per-row | the user's session | 16 | **`sSubsidiaryId`** (子公司ID) | almost every business row | per-row | the user's session |
17 | **`sVersionFlowId`** (版本流程ID) | `gdsmodule` only | per-module | the user's edition (against `sisversionflow`) | 17 | **`sVersionFlowId`** (版本流程ID) | `gdsmodule` only | per-module | the user's edition (against `sisversionflow`) |
18 18
19 -Per-row scoping is universal: both `sBrandsId` and `sSubsidiaryId` appear  
20 -on essentially every business-data table and every framework-metadata  
21 -table. The convention is "if a row represents tenant-owned state, both  
22 -columns are present." 19 +Per-row scoping is universal across business-data tables: both
  20 +`sBrandsId` and `sSubsidiaryId` appear on essentially every one. Most
  21 +framework-metadata tables also carry the columns, but four of them
  22 +(`gdsformconst`, `gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`)
  23 +are an explicit exception — `BusinessBaseServiceImpl.sTableNameList`
  24 +(lines 162-169) lists them as "不需要公司子公司的表" and lines 1078-1084
  25 +strip `sBrandsId`/`sSubsidiaryId` from the write payload for those
  26 +tables. In practice they hold a single sentinel tenant value shared
  27 +across all customers. Convention: "if a row represents tenant-owned
  28 +state, both columns are present *and populated from the session*."
23 29
24 Per-module gating (`sVersionFlowId`) is the opposite — it lives on 30 Per-module gating (`sVersionFlowId`) is the opposite — it lives on
25 `gdsmodule` only. So edition gating is a one-time filter at module- 31 `gdsmodule` only. So edition gating is a one-time filter at module-
en/docs/concepts/request-lifecycle.md
@@ -16,8 +16,12 @@ variations on a theme. @@ -16,8 +16,12 @@ variations on a theme.
16 │ 3. SPA decides which module to load → calls /business/... │ 16 │ 3. SPA decides which module to load → calls /business/... │
17 └──────────────────────────────────────────────────────────────────────┘ 17 └──────────────────────────────────────────────────────────────────────┘
18 18
19 - │ GET /xlyEntry/business/getModelBysId/{moduleId}  
20 - │ ?sModelsId={moduleId} 19 + │ GET /xlyEntry/business/getModelBysId/{sModelsId}
  20 + │ ?sModelsId={sModelsId}
  21 + │ (the module's id appears in BOTH path and query —
  22 + │ the controller binds the path variable, but the
  23 + │ service reads sModelsId from the @RequestParam map,
  24 + │ so the SPA must include it in the query string too)
21 25
22 ┌──────────────────────────────────────────────────────────────────────┐ 26 ┌──────────────────────────────────────────────────────────────────────┐
23 │ xlyEntry — BusinessBaseController.getModelBysId() │ 27 │ xlyEntry — BusinessBaseController.getModelBysId() │
@@ -25,22 +29,36 @@ variations on a theme. @@ -25,22 +29,36 @@ variations on a theme.
25 │ ┌──────────────────────────────────────────────────────────────┐ │ 29 │ ┌──────────────────────────────────────────────────────────────┐ │
26 │ │ RequestAddParamUtil.addParams(params, userInfo) │ │ 30 │ │ RequestAddParamUtil.addParams(params, userInfo) │ │
27 │ │ → sBrandsId, sSubsidiaryId, sUserId, sLanguage, … │ │ 31 │ │ → sBrandsId, sSubsidiaryId, sUserId, sLanguage, … │ │
28 -│ │ → tenant scope is now baked into every downstream query │ │ 32 +│ │ (16 keys total — see runtime.md) │ │
  33 +│ │ → tenant scope is now AVAILABLE for any downstream query │ │
  34 +│ │ that wants it. Framework-metadata reads filter by │ │
  35 +│ │ form-id only; per-tenant overlays + business data │ │
  36 +│ │ reads filter by sBrandsId/sSubsidiaryId. │ │
29 │ └──────────────────────────────────────────────────────────────┘ │ 37 │ └──────────────────────────────────────────────────────────────┘ │
30 │ │ 38 │ │
31 │ BusinessBaseService.getModelBysId(map) │ 39 │ BusinessBaseService.getModelBysId(map) │
32 │ │ │ 40 │ │ │
33 -│ ├── load gdsmodule row (the module) │  
34 -│ ├── load gdsconfigformmaster row(s) │  
35 -│ │ (joined to module via sParentId) │  
36 -│ ├── load gdsconfigformslave rows │  
37 -│ │ (joined to form-master via sParentId) │  
38 -│ ├── merge gdsconfigformpersonalize (per tenant) │  
39 -│ ├── merge gdsconfigformcustomslave (per tenant) │  
40 -│ ├── load gdsjurisdiction (skipped for ADMIN) │  
41 -│ ├── load gdsformconst (form-level constants) │  
42 -│ ├── load sysbillnosettings (document-numbering) │  
43 -│ └── load sysreport rows linked to this form │ 41 +│ ├── 1. formData │
  42 +│ │ └── gdsconfigformmaster (filtered by │
  43 +│ │ sParentId = sModelsId; gdsmodule itself │
  44 +│ │ is *not* SELECT-ed, only referenced by id) │
  45 +│ │ + LEFT JOIN gdsconfigformpersonalize │
  46 +│ │ (per-tenant overlay) │
  47 +│ │ + per-master gdsconfigformslave │
  48 +│ │ + per-master gdsconfigformcustomslave │
  49 +│ │ (per-tenant overlay) │
  50 +│ ├── 2. gdsformconst (filtered by sParentId only; │
  51 +│ │ NOT tenant-scoped; sLanguage selects which │
  52 +│ │ label column to return) │
  53 +│ ├── 3. sysjurisdiction (per-user/group grants joined │
  54 +│ │ to sftlogininfojurisdictiongroup + │
  55 +│ │ sisjurisdictionclassify; skipped for ADMIN. │
  56 +│ │ Returned under map key `gdsjurisdiction` — │
  57 +│ │ misleading name, the gdsjurisdiction table is │
  58 +│ │ the builder-side action catalogue, not what's │
  59 +│ │ read here) │
  60 +│ ├── 4. sysbillnosettings (per-tenant, per-form) │
  61 +│ └── 5. sysreport (per-tenant, per-form) │
44 └──────────────────────────────────────────────────────────────────────┘ 62 └──────────────────────────────────────────────────────────────────────┘
45 63
46 │ Returns one composite map: 64 │ Returns one composite map:
@@ -75,11 +93,11 @@ variations on a theme. @@ -75,11 +93,11 @@ variations on a theme.
75 93
76 | Key | Source | Used by the SPA for | 94 | Key | Source | Used by the SPA for |
77 |---|---|---| 95 |---|---|---|
78 -| `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave` (+ overlays) | The form layout itself — every field, control, label, validation rule |  
79 -| `gdsformconst` | `gdsformconst` rows scoped by tenant + language | Form-level constants — labels, defaults, dropdown text |  
80 -| `gdsjurisdiction` | `gdsjurisdiction` rows for the user's role | Per-button and per-data permissions |  
81 -| `billnosetting` | `sysbillnosettings` row for this module | Document-numbering rules (work-order numbers, quotation numbers) |  
82 -| `report` | `sysreport` rows linked to this form | Print templates (Excel via jxls, PDF via iText) | 96 +| `formData` | `gdsconfigformmaster` (filtered by `sParentId = sModelsId`) ⋈ `gdsconfigformpersonalize` (per-tenant overlay); per master row, `gdsconfigformslave` + `gdsconfigformcustomslave` overlays. `gdsmodule` is referenced only by id. | The form layout itself — every field, control, label, validation rule |
  97 +| `gdsformconst` | `gdsformconst` rows filtered by `sParentId` only — NOT tenant-scoped; `sLanguage` selects which label column to return | Form-level constants — labels, defaults, dropdown text |
  98 +| `gdsjurisdiction` | `sysjurisdiction` rows for the user (or for the user's group via `sftlogininfojurisdictiongroup` ⋈ `sisjurisdictionclassify`); skipped for ADMIN. The map-key name `gdsjurisdiction` is misleading — that table is the builder-side action *catalogue*, not what's read here. | Per-button and per-data permissions |
  99 +| `billnosetting` | `sysbillnosettings` row for this module (per-tenant) | Document-numbering rules (work-order numbers, quotation numbers) |
  100 +| `report` | `sysreport` rows linked to this form (per-tenant) | Print templates (Excel via jxls, PDF via iText) |
83 101
84 ## What's *not* in this lifecycle 102 ## What's *not* in this lifecycle
85 103
en/docs/index.md
@@ -35,15 +35,18 @@ which Reference chapter you go deep on. @@ -35,15 +35,18 @@ which Reference chapter you go deep on.
35 35
36 - The B2B printing-platform layer (`plat_*` tables, all `xlyPlat*` modules **except** `xlyPlatConstant` — see below). 36 - The B2B printing-platform layer (`plat_*` tables, all `xlyPlat*` modules **except** `xlyPlatConstant` — see below).
37 - AI / LLM features (`ai_*` tables, `AiController`) — too new, still moving. 37 - AI / LLM features (`ai_*` tables, `AiController`) — too new, still moving.
38 -- Face recognition (`xlyFace`) — niche. 38 +- Face recognition (`xlyFace`) — niche; still an active include in `settings.gradle` (built and deployed) but intentionally undocumented here.
39 - File-management module (`xlyFile`) and serial-port module (`xlyRxtx`) — niche / hardware-adjacent. 39 - File-management module (`xlyFile`) and serial-port module (`xlyRxtx`) — niche / hardware-adjacent.
  40 +- Scheduler modules (`xlyErpTask`, `xlyPlatTask`) — commented out in `settings.gradle`; cron / Quartz wiring is not part of the wiki's framework runtime.
40 - Test scaffolding modules (`xlyTestService`, `xlyTestController`) — historical, not part of the framework runtime. 41 - Test scaffolding modules (`xlyTestService`, `xlyTestController`) — historical, not part of the framework runtime.
41 - Per-tenant schema drift between `xlyweberp_*` databases — wiki targets one schema. 42 - Per-tenant schema drift between `xlyweberp_*` databases — wiki targets one schema.
42 -- Backup tables (`*_bak`, `*0302`, etc.).  
43 -- The MongoDB document store (`spring.data.mongodb.uri` in the yaml profiles, document classes under `xlyEntity/.../mongo/`). Every `@Document` class is `PLAT_*`-named and every `MongoTemplate` caller lives in an `xlyPlat*` module — so MongoDB is part of the plat tier above. The framework layer this wiki covers is MySQL-only. 43 +- Backup tables (`*_bak`, `*0302`, `*_copy1`, `*_history`, `*YYYYMMDD[HHMMSS]`-suffixed snapshots, etc.) — the auto-catalog generates a page for each because they exist on disk; the prose pages don't cover them as a family. ~56 such tables in the live schema.
  44 +- The MongoDB document store (`spring.data.mongodb.uri` in the yaml profiles, document classes under `xlyEntity/.../mongo/`). Of 22 `@Document` classes there, 20 are `PLAT_*`-named — the only outliers are two `DIKE_TEST*` scratch classes. The single `MongoTemplate` caller is `xlyPersist/.../dao/platmongo/BaseMongoDao` (the `dao/platmongo/` package gives away its plat-tier intent), which has no in-tree consumers on the cleanup branch — the `xlyPlat*` modules that used to extend it are all commented out of `settings.gradle`. The framework layer this wiki covers is MySQL-only; the Mongo wiring stays compiled but dormant.
44 45
45 > **Note on `xlyPlatConstant`.** It carries the `xlyPlat*` prefix but is in scope: `xlyPersist` imports two utility classes from it (`com.xly.xlyplatconstant.contant.thread.MultiThreadServer`, `com.xly.xlyplatconstant.contant.TimeContant`). Treat it as a misnamed shared-utility module, not a platform-tier module. 46 > **Note on `xlyPlatConstant`.** It carries the `xlyPlat*` prefix but is in scope: `xlyPersist` imports two utility classes from it (`com.xly.xlyplatconstant.contant.thread.MultiThreadServer`, `com.xly.xlyplatconstant.contant.TimeContant`). Treat it as a misnamed shared-utility module, not a platform-tier module.
46 47
  48 +> **Note on `xlyPlc`.** The PLC / hardware-bridge plugin is in scope as the canonical example of how a non-core module hooks into the framework. See [Slice 06 — Hardware](slices/06-hardware.md).
  49 +
47 ## How to fix something in this wiki 50 ## How to fix something in this wiki
48 51
49 Edit the markdown file. That is the wiki. Static HTML is generated from these `.md` 52 Edit the markdown file. That is the wiki. Static HTML is generated from these `.md`
en/docs/reference/maintainer/activiti.md
@@ -14,7 +14,7 @@ The dependency tree carries **two** Activiti versions: @@ -14,7 +14,7 @@ The dependency tree carries **two** Activiti versions:
14 14
15 | Module | Version | Notes | 15 | Module | Version | Notes |
16 |---|---|---| 16 |---|---|---|
17 -| `xlyPersist` | `org.activiti:activiti-engine:5.17.0` | Older 5.x line | 17 +| `xlyPersist`, `xlyApi` | `org.activiti:activiti-engine:5.17.0` | Older 5.x line — declared in both modules |
18 | `xlyFlow` | `org.activiti:activiti-spring-boot-starter-rest-api:6.0.0`, `activiti-json-converter:6.0.0` | Newer 6.0 line | 18 | `xlyFlow` | `org.activiti:activiti-spring-boot-starter-rest-api:6.0.0`, `activiti-json-converter:6.0.0` | Newer 6.0 line |
19 19
20 This is a real version mismatch. Activiti's 5.x and 6.x schemas overlap 20 This is a real version mismatch. Activiti's 5.x and 6.x schemas overlap
@@ -26,9 +26,9 @@ but diverge in some `act_*` tables and migration paths. Possibilities: @@ -26,9 +26,9 @@ but diverge in some `act_*` tables and migration paths. Possibilities:
26 3. Both are in the classpath but only one is initialised at runtime. 26 3. Both are in the classpath but only one is initialised at runtime.
27 27
28 A future maintainer attacking this should: (a) remove the unused 28 A future maintainer attacking this should: (a) remove the unused
29 -version to avoid confusion, (b) document which version the live  
30 -schema uses, (c) verify the `act_*` table layout matches that version  
31 -exactly. 29 +version from both `xlyPersist` and `xlyApi` to avoid confusion,
  30 +(b) document which version the live schema uses, (c) verify the
  31 +`act_*` table layout matches that version exactly.
32 32
33 One extra code fact matters in this branch: `xlyFlow/build.gradle` pulls 33 One extra code fact matters in this branch: `xlyFlow/build.gradle` pulls
34 in the Activiti 6 starter, but `xlyFlow/src/main/java/com/xly/XlyFlowApplicationBoot.java` 34 in the Activiti 6 starter, but `xlyFlow/src/main/java/com/xly/XlyFlowApplicationBoot.java`
@@ -37,10 +37,14 @@ does not currently present `xlyFlow` as a clearly runnable standalone app. @@ -37,10 +37,14 @@ does not currently present `xlyFlow` as a clearly runnable standalone app.
37 37
38 ## The `act_*` schema 38 ## The `act_*` schema
39 39
40 -The framework ships the expected Activiti `act_*` tables (deployment,  
41 -process-definition, runtime task, history etc.) — they are present even  
42 -in deployments that don't yet run a flow. They populate only when a BPMN  
43 -process is deployed and a process instance is started. 40 +The framework ships the expected Activiti `act_*` schema — 24 base
  41 +tables (deployment, process-definition, runtime task, history etc.)
  42 +plus 3 *views* (`act_id_user`, `act_id_group`, `act_id_membership`).
  43 +The base tables populate only when a BPMN process is deployed and a
  44 +process instance is started. The identity views are notable: xly does
  45 +not maintain real Activiti identity tables; it projects its own
  46 +user/group schema into the `act_id_*` shapes via views, so Activiti
  47 +sees xly's logins as if they were native Activiti users.
44 48
45 ## xly's wrapper layer 49 ## xly's wrapper layer
46 50
@@ -69,12 +73,21 @@ eventually completes. @@ -69,12 +73,21 @@ eventually completes.
69 fleshed out): 73 fleshed out):
70 74
71 - `xlyFlow`'s `pom`-equivalent gradle build pulls in Activiti 6.0. 75 - `xlyFlow`'s `pom`-equivalent gradle build pulls in Activiti 6.0.
72 -- The Spring Boot config for Activiti's process engine. 76 +- `xlyFlow/src/main/java/com/xly/activiti/config/ActivitiConfig.java` —
  77 + `@Configuration` implementing `ProcessEngineConfigurationConfigurer`,
  78 + the Spring Boot wire-up for Activiti's process engine.
73 - `CheckFlowController` in `xlyEntry/com/xly/web/businessweb/` is one 79 - `CheckFlowController` in `xlyEntry/com/xly/web/businessweb/` is one
74 surface the SPA hits to drive workflow (approve / reject / view). 80 surface the SPA hits to drive workflow (approve / reject / view).
75 -- BPMN process definitions, when present, live under `xlyFlow/src/main/resources/`  
76 - (a `processes/` subdirectory or similar). Whether anything ships in  
77 - the codebase depends on the build profile. 81 + Note: the URL prefix is `/checkflow` (lowercase), not the camelCase
  82 + class name.
  83 +- `xlyFlow/src/main/java/com/xly/XlyFlowApplicationBoot.java` is fully
  84 + commented out on this branch — the workflow code is consumed as a
  85 + library through xlyEntry rather than as a standalone runnable.
  86 +- **No BPMN definitions ship in this repo** under
  87 + `xlyFlow/src/main/resources/` (no `processes/` subdir, no `*.bpmn*`
  88 + files). Deployments must supply them at runtime, e.g. via the
  89 + Activiti modeler whose static assets live at
  90 + `xlyFlow/src/main/resources/static/modeler/`.
78 91
79 ## What's needed to make Activiti work 92 ## What's needed to make Activiti work
80 93
en/docs/reference/maintainer/cache-invalidation.md
1 # Cache invalidation on metadata change 1 # Cache invalidation on metadata change
2 2
3 When a PM saves a change in BACK — adds a column to a form, updates a 3 When a PM saves a change in BACK — adds a column to a form, updates a
4 -permission, registers a new module — every running node has to drop  
5 -its cached interpretation of the old metadata. xly does this through  
6 -JMS, not by polling. 4 +permission, registers a new module — the framework drops the cached
  5 +interpretation of the old metadata. **The cache-clear is synchronous
  6 +in the BACK process via Spring's `@CacheEvict`**, NOT a JMS fan-out.
  7 +A separate JMS path with similarly-named classes exists for a
  8 +different purpose (base-data merge); the two are easy to confuse and
  9 +this page calls them out explicitly.
7 10
8 -## The path 11 +## The actual cache-invalidation path (synchronous, in-process)
9 12
10 ``` 13 ```
11 PM saves in BACK 14 PM saves in BACK
12 15
13 16
14 -BACK controller writes the changed gds_* row 17 +BACK controller (e.g. /business/addUpdateDelBusinessData) calls
  18 +BusinessBaseServiceImpl.addBusinessData / updateBusinessData / deleteBusinessData
15 19
16 20
17 -Controller publishes a JMS "module changed" message 21 +Save service calls businessCleanRedisData.delCleanRedisData(...)
  22 + (e.g., BusinessBaseServiceImpl.java:1122, 1224, 1375, 1441, 1597, 1677)
18 23
19 24
20 -Every node's xlyErpJmsConsumer receives it 25 +BusinessCleanRedisDataImpl.delCleanRedisDataByTableName(<sTable>, ...)
  26 + dispatches to one of the named cleaners on CleanRedisServiceImpl
21 27
22 28
23 -ConsumerChangeGdsModuleThread.run() clears the relevant Redis keys 29 +CleanRedisServiceImpl.cleanRedisByTableNameGdsModle() (or similar)
  30 + fires @CacheEvict against a fixed list of named cache regions
24 31
25 32
26 -Next /business/getModelBysId call on any node re-reads the table  
27 - and re-populates the cache with the new value 33 +Spring CacheManager evicts the named entries
  34 + │
  35 + ▼
  36 +Next /business/getModelBysId call re-reads from DB and re-populates
  37 + the cache.
28 ``` 38 ```
29 39
30 -The handler is in  
31 -`xlyErpJmsConsumer/src/main/java/com/xly/xlyerpjmsconsumer/thread/ConsumerChangeGdsModuleThread.java`.  
32 -  
33 -## Why JMS, not poll-and-bust  
34 -  
35 -xly often runs across multiple nodes (xlyEntry, xlyApi, xlyInterface  
36 -each on their own JVM, sometimes scaled horizontally). Polling for  
37 -"has the metadata changed?" would either be slow (the change isn't  
38 -visible until the next poll) or chatty (constant heartbeats). JMS  
39 -fans out the invalidation to every node within milliseconds.  
40 -  
41 -xly uses both **ActiveMQ** and **RocketMQ** in the codebase, but the  
42 -metadata-change path documented here is the **ActiveMQ / JMS** one:  
43 -`xlyErpJmsConsumer` listens on `P2pQueue.ERP_JMS_ACTIVEMQ_CHANGE_GDS_MODULE`  
44 -with `@JmsListener`, and `ConsumerChangeGdsModuleThread` handles the  
45 -cache-bust work. `RocketMQServiceImpl` exists for other integration flows.  
46 -  
47 -## Which keys get cleared 40 +The cleaner methods are in
  41 +`xlyBusinessService/src/main/java/com/xly/service/impl/CleanRedisServiceImpl.java`.
  42 +A representative one — invoked when `gdsmodule` rows change — evicts
  43 +17 cache regions in a single call:
48 44
49 -The Redis cache holds:  
50 -  
51 -- Module metadata by `sId`.  
52 -- Form metadata by `sId`.  
53 -- Field-list slaves keyed by form `sId`.  
54 -- Per-tenant overlay merges (a derived cache).  
55 -- Permission rules per (module, role). 45 +```
  46 +@CacheEvict(value = {
  47 + "getGdsmoduleTree", "getGdsmoduleList", "getModuleTreePro",
  48 + "getSysjurisdictionTreePro", "getsDisplayTypeAll",
  49 + "businessBaseServiceGetMenuList", "getBuMenu", "getMenu",
  50 + "getsAuthsId", "businessCommonServicegetModulelistAll",
  51 + "gdsmoduleById", "getSaveProName", "businessParameterGetParameter",
  52 + "getPrcName", "getKpiModelByUser", "getUserByFromId",
  53 + "getUserByActionId", "getModuleTreeProAll"
  54 +}, allEntries = true)
  55 +public void cleanRedisByTableNameGdsModle() { … }
  56 +```
56 57
57 -The consumer thread receives the changed row's IDs and clears each  
58 -cache key family that could plausibly include it. **Over-invalidating  
59 -is the safe option here** — the cost of an extra DB read on the next  
60 -request is far smaller than the cost of serving stale metadata. 58 +Other table-named cleaners on the same class evict the regions
  59 +relevant to `gdsconfigformmaster`, `gdsconfigformslave`,
  60 +`gdsconfigtbmaster`, `gdsformconst`, `gdsjurisdiction`,
  61 +`gdsconfigcharmaster`, login-info, billnosetting, kpimaster,
  62 +`SysSystemSettings`, etc.
  63 +
  64 +## What the JMS `CHANGE_GDS_MODULE` queue actually does (NOT cache-bust)
  65 +
  66 +The framework has a JMS queue
  67 +`P2pQueue.ERP_JMS_ACTIVEMQ_CHANGE_GDS_MODULE` and a consumer thread
  68 +`ConsumerChangeGdsModuleThread`, both of which sound like they should
  69 +be doing cache invalidation — but they don't.
  70 +`ConsumerChangeGdsModuleThread.run()` resolves a
  71 +`changeGdsModuleService` bean (`ChangeGdsModuleServiceImpl`) and calls
  72 +`changeTableData(sGdsModuleId, sJobId)`, which invokes the stored
  73 +procedure `PRO_ERPMERGEBASEGDSMODULE` (via `proDao.proErpMergeBaseGdsModule`,
  74 +mapped in `ProMapper.xml`). That proc consolidates per-tenant
  75 +`gdsmodule` rows into a flattened "base" lookup table — a base-data
  76 +merge job, not a cache evict. A `grep` of `xlyErpJmsConsumer/` for
  77 +`@CacheEvict` or `cleanRedis*` returns zero hits — the consumer side
  78 +clears nothing in Redis.
  79 +
  80 +The same goes for the other 23 `ERP_JMS_ACTIVEMQ_*` queues in
  81 +[`P2pQueue.java`](../../api-reference/messaging.md): each one drives a
  82 +domain-specific base-data merge or fan-out work item, not cache
  83 +invalidation.
  84 +
  85 +## The cross-node coherence question (open)
  86 +
  87 +`@EnableCaching` is on `EntryApplicationBoot.java:22` and
  88 +`ApiApplicationBoot.java:24`. **No custom `CacheManager` bean is
  89 +declared anywhere in the in-scope source** (no `RedisCacheManager`,
  90 +no `@Bean CacheManager`-returning method, no `implements CacheManager`,
  91 +no `spring.cache.*` property in any `application*.yml`). Spring Boot
  92 +2.2.5 will then auto-pick a `CacheManager` based on what's on the
  93 +classpath; the most likely outcome with `spring-boot-starter-data-redis`
  94 +present is that Spring auto-configures Redis-backed caching, which
  95 +would make `@CacheEvict` clear the shared Redis store and therefore
  96 +fan out across nodes implicitly. **This has not been confirmed by live
  97 +inspection of a running node** — it's the natural reading of the
  98 +config but worth verifying when a deployment is available. If the
  99 +auto-picked manager is in fact `ConcurrentMapCacheManager` (in-memory,
  100 +per-JVM), the multi-node coherence story is broken under this code
  101 +path and would need a separate fix.
61 102
62 ## When you change metadata directly via SQL 103 ## When you change metadata directly via SQL
63 104
64 -Inserts/updates done through MyBatis or BACK *trigger* the JMS event.  
65 -Inserts/updates done by an engineer running raw `UPDATE gdsmodule SET  
66 -...` against the production DB do **not** trigger it. The cache will  
67 -serve stale metadata until either: 105 +Inserts/updates done through MyBatis or BACK trigger
  106 +`businessCleanRedisData.delCleanRedisData*`. Raw `UPDATE gdsmodule SET …`
  107 +against the DB does **not** trigger any cleaner. The cache will serve
  108 +stale metadata until either:
68 109
69 1. The cache TTL expires (check the cache config for the actual TTL). 110 1. The cache TTL expires (check the cache config for the actual TTL).
70 -2. A bounce of the application servers.  
71 -3. A manual JMS message is sent (see `BusinessCleanRedisDataImpl` in  
72 - `xlyBusinessService`).  
73 -  
74 -The third option is the supported workaround; option (2) is the  
75 -brute-force fallback. 111 +2. A bounce of the application servers (one node at a time if the
  112 + cache is local; once if shared).
  113 +3. A manual call to one of the
  114 + `BusinessCleanRedisDataImpl.delCleanRedisDataByTableName(<table>, …)`
  115 + methods is invoked from inside the application (e.g., via a
  116 + maintenance endpoint). Note this clears whatever the local
  117 + `CacheManager` is bound to; if that turns out to be in-memory,
  118 + the cleanup must run on every node.
76 119
77 ## Common bug: the cache is the bug 120 ## Common bug: the cache is the bug
78 121
79 When something looks like "I changed it but the page still shows the 122 When something looks like "I changed it but the page still shows the
80 old value", check (in this order): 123 old value", check (in this order):
81 124
82 -1. Did the change actually commit? (Confirm with `SELECT` against  
83 - the DB.)  
84 -2. Is the JMS broker reachable from the BACK node? (If not, the  
85 - invalidation event silently isn't published.)  
86 -3. Are all consumer nodes running? (If a node is paused, it'll serve  
87 - stale metadata until restarted.)  
88 -4. Did the change happen via raw SQL? (Then no JMS event was  
89 - published — manually trigger it.)  
90 -  
91 -The "five-table read" of [Slice 1](../../slices/01-hello-world.md) 125 +1. Did the change actually commit? (Confirm with `SELECT` against the DB.)
  126 +2. Did the change go through a path that invokes
  127 + `BusinessCleanRedisData`? (Direct DB writes or controllers that
  128 + bypass `BusinessBaseServiceImpl` won't.)
  129 +3. Is the cache shared across nodes (Redis-backed) or local
  130 + (`ConcurrentMapCacheManager`)? Confirm by inspecting the active
  131 + `CacheManager` bean on a running node.
  132 +4. If the cache is local, did every node get the eviction call?
  133 +
  134 +The five-key composite returned by
  135 +[`getModelBysId` in Slice 1](../../slices/01-hello-world.md)
92 re-runs from the cache; understanding which layer is stale is the key 136 re-runs from the cache; understanding which layer is stale is the key
93 to the bug. 137 to the bug.
en/docs/reference/maintainer/deployment.md
@@ -6,14 +6,29 @@ as dependencies by `xlyEntry`. @@ -6,14 +6,29 @@ as dependencies by `xlyEntry`.
6 6
7 ## The main modules 7 ## The main modules
8 8
9 -| Service / module | Role | Code-backed notes |  
10 -|---|---|---|  
11 -| **xlyEntry** | Main runtime and builder/admin surface. Hosts `/business/*`, `/gdsmodule/*`, `/gdsconfigform/*`, `/gdsconfigtb/*`, reporting, login, and other framework controllers. | Depends on `xlyManage`, `xlyBusinessService`, and `xlyFlow`. |  
12 -| **xlyApi** | API-oriented module for `/api/*`, `/online/*`, `/pro/*`, `/thirdparty/*`, and related endpoints. | Separate Spring Boot application class in `ApiApplicationBoot`. |  
13 -| **xlyInterface** | External-integration module with Swagger dependencies and third-party integration code. | Separate Spring Boot application class in `InterfaceApplicationBoot`. |  
14 -| **xlyPlc** | Shop-floor PLC bridge ([Slice 6](../../slices/06-hardware.md)). | Separate Spring Boot application class in `PlcApplicationBoot`. |  
15 -| **xlyFace** | Optional face-recognition module. | Separate module; still present in the Gradle build. |  
16 -| **xlyFlow** | Workflow / Activiti code and controllers. | Present as its own module, but in this branch `XlyFlowApplicationBoot` is commented out, so treat it as code that is currently consumed through `xlyEntry` rather than a clearly runnable standalone app. | 9 +### Deployable Spring Boot applications
  10 +
  11 +| Service / module | Role | Default profile / port | Boot class |
  12 +|---|---|---|---|
  13 +| **xlyEntry** | Main runtime and builder/admin surface. Hosts `/business/*`, `/gdsmodule/*`, `/gdsconfigform/*`, `/gdsconfigtb/*`, reporting, login, and other framework controllers. | `dev` → 8080, context `/xlyEntry` | `EntryApplicationBoot` |
  14 +| **xlyApi** | API-oriented module for `/api/*`, `/online/*`, `/pro/*`, `/thirdparty/*`, and related endpoints. | `local` (default in repo) → 8090, dev/win/linux → 8080, context `/xlyApi` | `ApiApplicationBoot` |
  15 +| **xlyInterface** | External-integration module with Swagger dependencies and third-party integration code. | `dev` → 8080, context `/xlyInterface` | `InterfaceApplicationBoot` |
  16 +| **xlyPlc** | Shop-floor PLC bridge ([Slice 6](../../slices/06-hardware.md)). | `dev` → 8000, named profiles (15S, S10, T0, T1, CT, yt, pro) → 8080, context `/xlyEntry` *(shares xlyEntry's context-path)* | `PlcApplicationBoot` |
  17 +| **xlyFace** | Face-recognition module. In build (`settings.gradle` keeps it active per user) but **out of documentation scope** for this wiki. | `win` (default in repo) → 8080, local → 8091, context `/xlyFace` | `XlyFaceApplicationBoot` |
  18 +| **xlyErpJmsConsumer** | JMS consumer worker. Has a Boot main but no `application*.yml` of its own — runtime config inherits from peer services. | n/a (inherits) | `JmsConsumerApplicationBoot` |
  19 +
  20 +### Library modules (in `settings.gradle`, not standalone runnable)
  21 +
  22 +| Module | Role |
  23 +|---|---|
  24 +| **xlyManage** | Backend metadata-management services (`Gds*ServiceImpl` family); pulled into xlyEntry. |
  25 +| **xlyBusinessService** | Business-logic service tier (`BusinessBaseServiceImpl` and ~100 sibling `*ServiceImpl` classes); pulled into xlyEntry. |
  26 +| **xlyFlow** | Workflow / Activiti code. `XlyFlowApplicationBoot.java` is fully commented out on this branch; consumed as a library through xlyEntry. Also shares context-path `/xlyEntry`. |
  27 +| **xlyEntity** | Shared entity / DTO classes (~83 Java files, including 22 Mongo `@Document` classes). |
  28 +| **xlyPersist** | Persistence helpers (DAOs, MyBatis mapper XMLs, `RequestAddParamUtil`, etc.). |
  29 +| **xlyMsg** | Notification helpers (DingTalk, WeChat, email); no Boot main. |
  30 +| **xlyErpJmsProductor** | JMS producer code (queue declarations in `P2pQueue.java`); no Boot main. |
  31 +| **xlyPlatConstant** | Shared utility constants (`MultiThreadServer`, `TimeContant`) consumed by `xlyPersist`. The single active Plat* module. |
17 32
18 Each has its own `application.yml` + several `application-<profile>.yml` 33 Each has its own `application.yml` + several `application-<profile>.yml`
19 files. The active profile is selected at startup via 34 files. The active profile is selected at startup via
@@ -21,33 +36,41 @@ files. The active profile is selected at startup via @@ -21,33 +36,41 @@ files. The active profile is selected at startup via
21 36
22 ## Disabled in `settings.gradle` 37 ## Disabled in `settings.gradle`
23 38
24 -```  
25 -//include 'xlyErpTask'  
26 -//include 'xlyRxtx'  
27 -//include 'xlyFile'  
28 -```  
29 -  
30 -Three modules are present on disk but excluded from the active Gradle build: 39 +The cleanup branch comments out 12 `include` lines. Three are non-Plat
  40 +modules present on disk:
31 41
32 - `xlyErpTask` — long-running background tasks. 42 - `xlyErpTask` — long-running background tasks.
33 -- `xlyRxtx` — native serial-port library. Disabled when xlyPlc doesn't  
34 - need direct serial access (some press models use TCP/Ethernet 43 +- `xlyRxtx` — native serial-port library. May be re-enabled when xlyPlc
  44 + needs direct serial access (some press models use TCP/Ethernet
35 instead). 45 instead).
36 -- `xlyFile` — older file-management module, superseded by Aliyun OSS  
37 - integration in `xlyPlatFileUpload`. 46 +- `xlyFile` — older file-management module, superseded by
  47 + `xlyPlatFileUpload` (also commented out).
  48 +
  49 +The remaining nine commented-out includes are `xlyTestService`,
  50 +`xlyTestController`, and the full `xlyPlat*` family except
  51 +`xlyPlatConstant` — i.e. `xlyPlatTask`, `xlyPlatJmsProductor`,
  52 +`xlyPlatJmsConsumer`, `xlyPlatReportForm`, `xlyPlatFileUpload`,
  53 +`xlyPlatMarketingService`, `xlyPlatUserService`, `xlyPlatSmsService`,
  54 +`xlyPlatMerchantController`, `xlyPlatWebsocket`, `xlyPlatPayService`,
  55 +`xlyPlatCainiaoWaybillSevice`. (`xlyTestService` / `xlyTestController`
  56 +directories are not on disk; only `TestController.java` exists inside
  57 +`xlyEntry/.../businessweb/` as a stub.)
38 58
39 A maintainer cleaning up the codebase should consider whether to delete 59 A maintainer cleaning up the codebase should consider whether to delete
40 -these or keep them as historical reference. They take up disk space but  
41 -do not affect the build. 60 +the on-disk-but-excluded `xlyErpTask` / `xlyRxtx` / `xlyFile`
  61 +directories or keep them as historical reference. They take up disk
  62 +space but do not affect the build.
42 63
43 ## Plat* family 64 ## Plat* family
44 65
45 The `xlyPlat*` modules (`xlyPlatMerchantController`, `xlyPlatUserService`, 66 The `xlyPlat*` modules (`xlyPlatMerchantController`, `xlyPlatUserService`,
46 `xlyPlatPayService`, `xlyPlatMarketingService`, `xlyPlatCainiaoWaybillSevice`, 67 `xlyPlatPayService`, `xlyPlatMarketingService`, `xlyPlatCainiaoWaybillSevice`,
47 `xlyPlatSmsService`, `xlyPlatReportForm`, `xlyPlatFileUpload`, 68 `xlyPlatSmsService`, `xlyPlatReportForm`, `xlyPlatFileUpload`,
48 -`xlyPlatJmsConsumer`/`Productor`, `xlyPlatTask`, `xlyPlatWebsocket`,  
49 -`xlyPlatConstant`) are the **B2B printing-platform layer** and remain  
50 -out-of-scope for this wiki. 69 +`xlyPlatJmsConsumer`/`Productor`, `xlyPlatTask`, `xlyPlatWebsocket`)
  70 +are the **B2B printing-platform layer** and remain out-of-scope for
  71 +this wiki. The single exception is `xlyPlatConstant`, which is still
  72 +`include`d in `settings.gradle` and consumed as a shared constants
  73 +utility by `xlyPersist` (`MultiThreadServer`, `TimeContant`).
51 74
52 ## How services find each other 75 ## How services find each other
53 76
@@ -75,14 +98,25 @@ are deployment details rather than code-backed facts in this repository. @@ -75,14 +98,25 @@ are deployment details rather than code-backed facts in this repository.
75 98
76 ## Profile permutations 99 ## Profile permutations
77 100
78 -`application-saas.yml`, `application-linux.yml`, `application-win.yml`,  
79 -`application-15S.yml`, `application-S10.yml`, `application-pro.yml`,  
80 -`application-T0.yml`, `application-T1.yml`, … cover combinations of:  
81 -  
82 -- Operating system (linux / win)  
83 -- Customer category (saas, 15S, S10, …)  
84 -- Environment (dev, pro)  
85 -- Press-model (for xlyPlc) 101 +Profiles split by service:
  102 +
  103 +- **xlyEntry**: `dev`, `local`, `win`, `linux`, `15s`, `s10`, `saas`,
  104 + `bgj` (lowercase). `dev` is the in-repo default.
  105 +- **xlyApi**: `local` (default in repo), `dev`, `linux`, `win`.
  106 +- **xlyInterface**: `dev` only.
  107 +- **xlyFlow**: `dev` (empty file).
  108 +- **xlyFace**: `win` (default), `dev`, `linux`, `local`.
  109 +- **xlyPlc**: `dev` (default) plus 7 press-model profiles
  110 + (`15S`, `S10`, `T0`, `T1`, `CT`, `yt`, `pro` — uppercase / mixed-case,
  111 + distinct from xlyEntry's lowercase `15s` / `s10`).
  112 +
  113 +The press-model profiles (`T0`, `T1`, `CT`, `yt`, `pro`, `15S`, `S10`)
  114 +are **xlyPlc-specific** — they don't exist for the other services. The
  115 +cross-service profiles cover combinations of:
  116 +
  117 +- Operating system (`linux` / `win`)
  118 +- Environment (`dev`, `local`, `saas`, `bgj`)
  119 +- Customer/edition (`15s`, `s10` for xlyEntry)
86 120
87 A given deployment selects exactly one profile per service. The 121 A given deployment selects exactly one profile per service. The
88 mapping from "this customer" → "these profiles" lives in deployment 122 mapping from "this customer" → "these profiles" lives in deployment
en/docs/reference/maintainer/proc-dispatch.md
1 # Generic procedure dispatch 1 # Generic procedure dispatch
2 2
3 -When `gdsmodule.sSaveProName` (or `sDeleteProName`, `sCalcProName`,  
4 -`sProcName`, `sSaveProNameBefore`) is **non-empty**, the framework  
5 -invokes the named stored procedure instead of falling through to its  
6 -default Add/Update path. The same machinery handles button-press  
7 -calculations and on-demand custom logic. 3 +When the metadata names a stored procedure — via columns like
  4 +`gdsmodule.sSaveProName`, `sSaveProNameBefore`, `sDeleteProName`,
  5 +`sCalcProName`, `sProcName`, or `gdsconfigformslave.sButtonParam` —
  6 +the framework dispatches that proc by name. `sSaveProName` and
  7 +`sSaveProNameBefore` are **hooks**: they run as post-save / pre-save
  8 +phases on top of the always-running base add/update path
  9 +(`BusinessBaseServiceImpl.addBusinessData` /
  10 +`updateBusinessData`), invoked by `checkUpdate(...,"sSaveProName")`
  11 +at `BusinessBaseServiceImpl.java:1824` and `:1778`. The other
  12 +columns drive on-demand calls: `sCalcProName` for button-press
  13 +calculations, `sProcName` for custom-fetch flows, etc. The same
  14 +generic-dispatch machinery handles all of them.
8 15
9 The handler is `GenericProcedureCallController` in 16 The handler is `GenericProcedureCallController` in
10 `xlyEntry/com/xly/web/businessweb/`. 17 `xlyEntry/com/xly/web/businessweb/`.
11 18
12 ## Endpoint shape 19 ## Endpoint shape
13 20
14 -The frontend POSTs to a URL under `/business/genericProcedureCall*`  
15 -with: 21 +The frontend POSTs to `/procedureCall/doGenericProcedureCall` with:
16 22
17 - The proc name (often resolved from `gdsmodule` or `gdsconfigformslave.sButtonParam`). 23 - The proc name (often resolved from `gdsmodule` or `gdsconfigformslave.sButtonParam`).
18 - The parameter values (frontend supplies them, the framework injects 24 - The parameter values (frontend supplies them, the framework injects
@@ -79,6 +85,40 @@ These are templates an engineer fills in to author a new proc — they @@ -79,6 +85,40 @@ These are templates an engineer fills in to author a new proc — they
79 are not used by the runtime to generate procs on the fly. See [SQL 85 are not used by the runtime to generate procs on the fly. See [SQL
80 templates](sql-templates.md) for the loader and the placeholder syntax. 86 templates](sql-templates.md) for the loader and the placeholder syntax.
81 87
  88 +## Proc-name molds in the live schema
  89 +
  90 +The 1687 procedures in the live DB cluster around a few naming molds
  91 +beyond the bare `Sp_*` family:
  92 +
  93 +| Mold | Approx count | Role |
  94 +|---|---:|---|
  95 +| `Sp_*` | most | The dominant family, dispatched by `sSaveProName` / `sCalcProName` / `sProcName` etc. |
  96 +| `Sp_*_BeforeSave` | ~62 | Pre-save hooks. Pair with `sSaveProNameBefore`. |
  97 +| `Sp_*_AfterSave` / `Sp_*_SaveReturn` | ~62 / ~54 | Post-save hooks; `_SaveReturn` writes back into the parent transaction. |
  98 +| `Sp_*_Calc` | ~178 | Calculation procs invoked by button-press flow (`sCalcProName` / `sButtonParam`). |
  99 +| `sp_btn_*` | ~65 | Button-event sub-family — typically `sp_btn_calc*` / `sp_btn_validate*` (lowercase by convention). |
  100 +| `PRO_ERPMERGE*` | ~11 | Data-migration utilities. **Not dispatched by the runtime** — engineer-only. |
  101 +| `PRO_*` (other) | ~12 | Other one-off utilities. |
  102 +| `Get_*`, `del_*`, `Cal*`, `Tj_*` | small handfuls | Legacy / domain-specific helpers. Not part of the generic-dispatch contract. |
  103 +
  104 +A typo in any of the dispatched columns gets an `Sp_*`-shaped target,
  105 +so other molds never resolve via `sSaveProName` / `sCalcProName` etc.
  106 +The non-`Sp_*` procs are reachable only via direct invocation in
  107 +mapper XML or other procs.
  108 +
  109 +## The function layer
  110 +
  111 +The schema also ships **177 user-defined functions** following parallel
  112 +naming molds: `Fun_*` (~150), `Fn_*` (~8), `get_*` (~10).
  113 +
  114 +These are **not Java-dispatched**. They are invoked from inside other
  115 +procedures, view definitions, and mapper-XML SELECT statements. There
  116 +is no `gdsmodule.sFunctionName` column or analogous metadata —
  117 +functions are picked up by the SQL that mentions them. A maintainer
  118 +investigating a slow report should grep procs and views for `Fun_*` /
  119 +`Fn_*` / `get_*` references; the framework's Java side does not see
  120 +them at all.
  121 +
82 ## Failure modes to watch 122 ## Failure modes to watch
83 123
84 1. **Mismatched parameter order.** Generic dispatch binds positionally; 124 1. **Mismatched parameter order.** Generic dispatch binds positionally;
en/docs/reference/maintainer/runtime.md
@@ -16,14 +16,14 @@ controllers and services that carry most of the generic form runtime. @@ -16,14 +16,14 @@ controllers and services that carry most of the generic form runtime.
16 | `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}` | 16 | `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}` |
17 | `GenericProcedureCallController` | `web/businessweb/` | Generic stored-procedure invocation by name + parameters. | `/procedureCall/doGenericProcedureCall` | 17 | `GenericProcedureCallController` | `web/businessweb/` | Generic stored-procedure invocation by name + parameters. | `/procedureCall/doGenericProcedureCall` |
18 | `ConfigformPanelController` | `web/businessweb/` | Panel-layout persistence in `gdsconfigformpanel`. | `/panel/get/{sFormId}`, `/panel/save/{sFormId}` | 18 | `ConfigformPanelController` | `web/businessweb/` | Panel-layout persistence in `gdsconfigformpanel`. | `/panel/get/{sFormId}`, `/panel/save/{sFormId}` |
19 -| `CheckFlowController` | `web/businessweb/` | Activiti workflow surface (approve / reject / view) — only meaningful when workflow is deployed. | endpoints under `/checkFlow/*` | 19 +| `CheckFlowController` | `web/businessweb/` | Activiti workflow surface (approve / reject / view) — only meaningful when workflow is deployed. | endpoints under `/checkflow/*` (the class file is `CheckFlowController.java` in camelCase but the `@RequestMapping` value is all-lowercase) |
20 20
21 Note that the controllers split across **two packages**: `businessweb/` 21 Note that the controllers split across **two packages**: `businessweb/`
22 hosts the runtime-facing endpoints, while `systemweb/` hosts the 22 hosts the runtime-facing endpoints, while `systemweb/` hosts the
23 metadata-CRUD endpoints used by the builder side. Both compile into the 23 metadata-CRUD endpoints used by the builder side. Both compile into the
24 same `xlyEntry` WAR. 24 same `xlyEntry` WAR.
25 25
26 -## The four-table read 26 +## The five-key read
27 27
28 For any metadata-driven module, the request lifecycle (see 28 For any metadata-driven module, the request lifecycle (see
29 [Concepts → request lifecycle](../../concepts/request-lifecycle.md)) 29 [Concepts → request lifecycle](../../concepts/request-lifecycle.md))
@@ -31,11 +31,23 @@ boils down to: @@ -31,11 +31,23 @@ boils down to:
31 31
32 ```java 32 ```java
33 public Map<String, Object> getModelBysId(Map<String, Object> map) { 33 public Map<String, Object> getModelBysId(Map<String, Object> map) {
34 - List<Map<String, Object>> formList = this.getModelConfigByModleId(map); // 1. join gdsmodule⋈form-master⋈form-slave  
35 - List<Map<String, Object>> fList = businessGdsconfigformsService.getFormconstData(qMap); // 2. gdsformconst  
36 - List<Map<String, Object>> jList = businessGdsconfigformsService.getJurisdictionData(qMap); // 3. gdsjurisdiction (skipped for ADMIN)  
37 - Map<String, Object> billnosettingMap = businessGdsconfigformsService.getBillnosettingData(param); // 4. sysbillnosettings  
38 - List<Map<String, Object>> reportList = printReportService.getReportData(qMap); // 5. sysreport 34 + // 1. formData — gdsconfigformmaster filtered by sParentId=sModelsId,
  35 + // LEFT JOIN gdsconfigformpersonalize (per-tenant), then for each
  36 + // master row load its gdsconfigformslave + gdsconfigformcustomslave
  37 + // overlays. gdsmodule itself is referenced only by id, not SELECT-ed.
  38 + List<Map<String, Object>> formList = this.getModelConfigByModleId(map);
  39 + // 2. gdsformconst — by sParentId only; sLanguage selects which label
  40 + // column to return; NOT tenant-scoped.
  41 + List<Map<String, Object>> fList = businessGdsconfigformsService.getFormconstData(qMap);
  42 + // 3. sysjurisdiction (per-user grants joined to sftlogininfojurisdictiongroup
  43 + // + sisjurisdictionclassify); skipped for ADMIN.
  44 + // Returned under map key `gdsjurisdiction` despite the source table
  45 + // being sysjurisdiction.
  46 + List<Map<String, Object>> jList = businessGdsconfigformsService.getJurisdictionData(qMap);
  47 + // 4. sysbillnosettings (per-tenant, per-form).
  48 + Map<String, Object> billnosettingMap = businessGdsconfigformsService.getBillnosettingData(param);
  49 + // 5. sysreport (per-tenant, per-form).
  50 + List<Map<String, Object>> reportList = printReportService.getReportData(qMap);
39 return composite(formList, fList, jList, billnosettingMap, reportList); 51 return composite(formList, fList, jList, billnosettingMap, reportList);
40 } 52 }
41 ``` 53 ```
@@ -47,11 +59,11 @@ rest of the runtime is variations on it. @@ -47,11 +59,11 @@ rest of the runtime is variations on it.
47 59
48 | Key | Source | Frontend uses for | 60 | Key | Source | Frontend uses for |
49 |---|---|---| 61 |---|---|---|
50 -| `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave` (+ overlays) | Form layout |  
51 -| `gdsformconst` | `gdsformconst` | Form-level constants, dropdown labels |  
52 -| `gdsjurisdiction` | `gdsjurisdiction` | Per-button / per-data permissions |  
53 -| `billnosetting` | `sysbillnosettings` | Document numbering rules |  
54 -| `report` | `sysreport` | Print templates | 62 +| `formData` | `gdsconfigformmaster` (filtered by `sParentId = sModelsId`) ⋈ `gdsconfigformpersonalize` overlay; per master row, `gdsconfigformslave` + `gdsconfigformcustomslave`. `gdsmodule` is referenced only as the id source, not joined. | Form layout |
  63 +| `gdsformconst` | `gdsformconst` (filtered by `sParentId` only; `sLanguage` selects which label column to return; NOT tenant-scoped) | Form-level constants, dropdown labels |
  64 +| `gdsjurisdiction` | `sysjurisdiction` (joined to `sftlogininfojurisdictiongroup` + `sisjurisdictionclassify` for per-user/group grants); skipped for ADMIN. **Note:** the map-key name `gdsjurisdiction` is misleading — `gdsjurisdiction` is the builder-side action *catalogue* table; the per-user *grant* read here actually queries `sysjurisdiction`. | Per-button / per-data permissions |
  65 +| `billnosetting` | `sysbillnosettings` (per-tenant, per-form) | Document numbering rules |
  66 +| `report` | `sysreport` (per-tenant, per-form) | Print templates |
55 67
56 ## The save endpoint 68 ## The save endpoint
57 69
@@ -67,9 +79,22 @@ map per row, in this shape: @@ -67,9 +79,22 @@ map per row, in this shape:
67 } 79 }
68 ``` 80 ```
69 81
70 -When `gdsmodule.sSaveProName` is empty, the framework's default  
71 -Add/Update path runs — `AddDelUpdCommonServiceImpl.java`. When it's  
72 -populated, the named stored proc is invoked instead. 82 +The base add/update path always runs through
  83 +`BusinessBaseServiceImpl.addBusinessData` / `updateBusinessData`
  84 +(`xlyBusinessService/.../BusinessBaseServiceImpl.java:1014` and
  85 +`:1250`), which delegate to `businessBaseDao.add(map)` /
  86 +`businessBaseDao.update(map)` against the table named by `sTable`.
  87 +`gdsmodule.sSaveProName` (and its sibling `sSaveProNameBefore`) is
  88 +**not** an either/or branch that swaps the path; it names an extra
  89 +stored proc that runs as a post-save (or pre-save) hook on top of
  90 +the base path, dispatched by `checkUpdate(...,"sSaveProName")` at
  91 +`BusinessBaseServiceImpl.java:1824` and `CheckSaveServiceImpl.java`.
  92 +`AddDelUpdCommonServiceImpl` (`@Service("addDelUpdCommonService")`)
  93 +is a separate utility of reusable `insertByMap`/`updateByMap`/
  94 +`delByMap`/`addBatch` helpers used by domain services
  95 +(work-order-plan, oee, many-quo, order-procurement, etc.); it is
  96 +**not** the runtime's default add/update path for
  97 +`addUpdateDelBusinessData`.
73 98
74 ## Multi-tenant boundary 99 ## Multi-tenant boundary
75 100
@@ -95,12 +120,21 @@ A new controller method that doesn&#39;t call `RequestAddParamUtil` is a @@ -95,12 +120,21 @@ A new controller method that doesn&#39;t call `RequestAddParamUtil` is a
95 120
96 Two flagged in slices that belong here permanently: 121 Two flagged in slices that belong here permanently:
97 122
98 -1. **`sTable` validation in `addUpdateDelBusinessData`.** The frontend  
99 - names the target table directly. The runtime *must* cross-check  
100 - that the supplied table is one of the form's authorised backing  
101 - tables, or it's a privilege-escalation surface. Confirm the check  
102 - exists; if it doesn't, raise it as a security ticket. Tracked in  
103 - the [Slice 1 v2 follow-up](../../slices/01-hello-world.md#open-verification-items). 123 +1. **`sTable` validation in `addUpdateDelBusinessData` — CONFIRMED MISSING.**
  124 + The frontend names the target table directly and the runtime does
  125 + **not** cross-check that the supplied table is one of the form's
  126 + authorised backing tables. `BusinessBaseServiceImpl.sTableNameList`
  127 + (lines 162-169) is a multi-tenant scope-bypass list (the four
  128 + framework-metadata tables that are global, so `sBrandsId` /
  129 + `sSubsidiaryId` are stripped from writes against them — see the
  130 + `//不需要公司子公司的表` comment at line 165), not a backing-table
  131 + whitelist. The hardcoded special case at line 1768
  132 + (`mftproductionplanslave`) is the only module/table cross-check
  133 + in the whole flow. Mitigations exist (tenant scoping via
  134 + `RequestAddParamUtil`, optional pre/post-save proc validation),
  135 + but none of them is a backing-table whitelist. See
  136 + [Slice 1 follow-up](../../slices/01-hello-world.md#open-verification-items)
  137 + for the full trace.
104 2. **ADMIN bypasses permissions.** 138 2. **ADMIN bypasses permissions.**
105 `BusinessBaseServiceImpl` skips the `gdsjurisdiction` 139 `BusinessBaseServiceImpl` skips the `gdsjurisdiction`
106 load entirely for `UserType.ADMIN`. ADMIN account governance must 140 load entirely for `UserType.ADMIN`. ADMIN account governance must
@@ -108,7 +142,11 @@ Two flagged in slices that belong here permanently: @@ -108,7 +142,11 @@ Two flagged in slices that belong here permanently:
108 142
109 ## Cache invalidation 143 ## Cache invalidation
110 144
111 -When BACK saves a metadata change, a JMS message fires and  
112 -`ConsumerChangeGdsModuleThread` (in `xlyErpJmsConsumer`) clears the  
113 -cached metadata on every running node. See [cache invalidation on  
114 -metadata change](cache-invalidation.md). 145 +When BACK saves a metadata change, the save service synchronously
  146 +calls `BusinessCleanRedisData.delCleanRedisData*`, which fires
  147 +`@CacheEvict` on the relevant cache regions in `CleanRedisServiceImpl`.
  148 +A separate JMS path (`ConsumerChangeGdsModuleThread`) exists with a
  149 +similar name but does base-data merging via stored proc, not cache
  150 +invalidation. See [cache invalidation on metadata change](cache-invalidation.md)
  151 +for the full story (including the open question about cross-node
  152 +coherence).
en/docs/reference/maintainer/tech-stack.md
1 # Tech stack 1 # Tech stack
2 2
3 -A library inventory for the **in-scope** framework modules  
4 -(`xlyEntry`, `xlyApi`, `xlyManage`, `xlyBusinessService`, `xlyPersist`,  
5 -`xlyEntity`, `xlyFlow`, `xlyPlc`, `xlyInterface`, `xlyMsg`,  
6 -`xlyErpJms*`).  
7 -  
8 -The plat tier (`xlyPlat*` modules), `xlyFace`, and AI libraries are 3 +A library inventory for the **in-scope** framework — 11 framework-core
  4 +modules (`xlyEntry`, `xlyApi`, `xlyManage`, `xlyBusinessService`,
  5 +`xlyPersist`, `xlyEntity`, `xlyFlow`, `xlyInterface`, `xlyMsg`,
  6 +`xlyErpJmsProductor`, `xlyErpJmsConsumer`), one plugin (`xlyPlc`), and
  7 +one shared utility (`xlyPlatConstant` — load-bearing for `xlyPersist`'s
  8 +use of `MultiThreadServer` and `TimeContant`, despite the `Plat*`
  9 +naming).
  10 +
  11 +The other plat-tier modules (`xlyPlat*` except `xlyPlatConstant`),
  12 +`xlyFace` (in build, out of documentation scope), and AI libraries are
9 [out of scope](../../index.md#whats-out-of-scope) and are not listed 13 [out of scope](../../index.md#whats-out-of-scope) and are not listed
10 here. 14 here.
11 15
@@ -49,7 +53,7 @@ page records facts only. @@ -49,7 +53,7 @@ page records facts only.
49 | MySQL Connector/J | 8.0.13 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | yaml `spring.datasource.driverClassName: com.mysql.cj.jdbc.Driver` (e.g., `xlyEntry/.../application-local.yml:127`). | 53 | MySQL Connector/J | 8.0.13 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | yaml `spring.datasource.driverClassName: com.mysql.cj.jdbc.Driver` (e.g., `xlyEntry/.../application-local.yml:127`). |
50 | MSSQL JDBC | sqljdbc4 3.0 (Maven) + `mssql-jdbc-6.2.2.jre8.jar` (local jar in `xlyFlow/`, `xlyInterface/`) | `xlyApi/build.gradle`, `xlyInterface/build.gradle`, `xlyFlow/build.gradle` | 5 files: 3 in `xlyFlow/src/`, 2 in `xlyInterface/src/`. | 54 | MSSQL JDBC | sqljdbc4 3.0 (Maven) + `mssql-jdbc-6.2.2.jre8.jar` (local jar in `xlyFlow/`, `xlyInterface/`) | `xlyApi/build.gradle`, `xlyInterface/build.gradle`, `xlyFlow/build.gradle` | 5 files: 3 in `xlyFlow/src/`, 2 in `xlyInterface/src/`. |
51 | Oracle JDBC | `ojdbc6-11.2.0.4.jar` (local jar in `xlyFlow/`) | `xlyFlow/build.gradle` | 2 files in `xlyFlow/src/`. | 55 | Oracle JDBC | `ojdbc6-11.2.0.4.jar` (local jar in `xlyFlow/`) | `xlyFlow/build.gradle` | 2 files in `xlyFlow/src/`. |
52 -| Druid | `druid-spring-boot-starter` 1.2.16; `druid` 1.2.16 | `xlyPersist/build.gradle`, `xlyApi/build.gradle` | 25 files import `com.alibaba.druid.*` (xlyEntry=8, xlyPlc=8, xlyFlow=5, xlyBusinessService=2, xlyInterface=2). yaml: `xlyEntry/.../application-local.yml:126` sets `spring.datasource.type: com.alibaba.druid.pool.DruidDataSource`; lines 308-313 configure the `/druid/*` stat-view servlet. | 56 +| Druid | `druid-spring-boot-starter` 1.2.16; `druid` 1.2.16 | `xlyPersist/build.gradle`, `xlyApi/build.gradle` | 6 Java files import `com.alibaba.druid.*` (xlyBusinessService=2, xlyFlow=3, xlyInterface=1). 16 `application-*.yml` files reference Druid configuration (xlyEntry=8 yml profiles, xlyPlc=8 yml profiles). yaml: `xlyEntry/.../application-local.yml:126` sets `spring.datasource.type: com.alibaba.druid.pool.DruidDataSource`; lines 308-313 configure the `/druid/*` stat-view servlet. |
53 | HikariCP | 4.0.3 | `xlyApi/build.gradle` | 8 files reference `com.zaxxer.hikari` (xlyApi=6, xlyInterface=2). Java config: `xlyApi/.../api/config/MasterDataSourceConfig.java`, `SlaveDataSourceConfig.java`. yaml: `xlyApi/.../application-{local,dev,linux,win}.yml`. | 57 | HikariCP | 4.0.3 | `xlyApi/build.gradle` | 8 files reference `com.zaxxer.hikari` (xlyApi=6, xlyInterface=2). Java config: `xlyApi/.../api/config/MasterDataSourceConfig.java`, `SlaveDataSourceConfig.java`. yaml: `xlyApi/.../application-{local,dev,linux,win}.yml`. |
54 | Flyway | 5.2.1 | `xlyPersist/build.gradle` | No Java imports. Configured via yaml `spring.flyway.*` (e.g., `xlyEntry/.../application-local.yml:316-327`) with `enabled: false`. Migration scripts at `xlyEntry/src/main/resources/flyway/V*__*.sql`. | 58 | Flyway | 5.2.1 | `xlyPersist/build.gradle` | No Java imports. Configured via yaml `spring.flyway.*` (e.g., `xlyEntry/.../application-local.yml:316-327`) with `enabled: false`. Migration scripts at `xlyEntry/src/main/resources/flyway/V*__*.sql`. |
55 | PageHelper | 4.1.1 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 19 files import `com.github.pagehelper.*`. yaml `pagehelper.helperDialect: mysql` at `xlyEntry/.../application-local.yml:427`. | 59 | PageHelper | 4.1.1 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 19 files import `com.github.pagehelper.*`. yaml `pagehelper.helperDialect: mysql` at `xlyEntry/.../application-local.yml:427`. |
@@ -68,7 +72,7 @@ page records facts only. @@ -68,7 +72,7 @@ page records facts only.
68 72
69 | Library | Version | Where | In-scope source references | 73 | Library | Version | Where | In-scope source references |
70 |---|---|---|---| 74 |---|---|---|---|
71 -| Activiti Engine | 5.17.0 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`; consumed by `xlyFlow` | 35 files import `org.activiti.*` (xlyFlow=32, plus 1 each in xlyPersist, xlyApi, xlyEntry — the xlyEntry hit is the `SecurityAutoConfiguration` exclusion in `EntryApplicationBoot.java`; the xlyApi hit is `IdGen.java`; the xlyPersist hit is `BaseDao.java`). The version skew with the 6.0 modeler libs is documented in [Activiti integration](activiti.md). | 75 +| Activiti Engine | 5.17.0 | `xlyPersist/build.gradle`, `xlyApi/build.gradle`; consumed by `xlyFlow` | 35 files import `org.activiti.*` (xlyFlow=32, plus 1 each in xlyPersist, xlyApi, xlyEntry — the xlyEntry hit is the `SecurityAutoConfiguration` exclusion in `EntryApplicationBoot.java`; the xlyApi and xlyPersist hits are both `IdGen.java`, near-identical crypto-utility copies that import `org.activiti.engine.identity.User`). The version skew with the 6.0 modeler libs is documented in [Activiti integration](activiti.md). |
72 | Activiti Spring Boot REST API | 6.0.0 | `xlyFlow/build.gradle` | Consumed via Spring Boot autoconfig + REST endpoints under `xlyFlow`. | 76 | Activiti Spring Boot REST API | 6.0.0 | `xlyFlow/build.gradle` | Consumed via Spring Boot autoconfig + REST endpoints under `xlyFlow`. |
73 | Activiti JSON Converter | 6.0.0 | `xlyFlow/build.gradle` | (Used by xlyFlow's modeler save path.) | 77 | Activiti JSON Converter | 6.0.0 | `xlyFlow/build.gradle` | (Used by xlyFlow's modeler save path.) |
74 | Quartz | 2.3.0 | `xlyFlow/build.gradle` | 16 files import `org.quartz.*` (xlyEntry=8, xlyFlow=8). yaml: `xlyEntry/.../application-local.yml:329-365` configures `spring.quartz.*` with JDBC JobStore (`qrtz_*` tables), `instanceName: xlyflowScheduler`. | 78 | Quartz | 2.3.0 | `xlyFlow/build.gradle` | 16 files import `org.quartz.*` (xlyEntry=8, xlyFlow=8). yaml: `xlyEntry/.../application-local.yml:329-365` configures `spring.quartz.*` with JDBC JobStore (`qrtz_*` tables), `instanceName: xlyflowScheduler`. |
@@ -139,10 +143,10 @@ third-party code. @@ -139,10 +143,10 @@ third-party code.
139 143
140 | Library | Version | Where | In-scope source references | 144 | Library | Version | Where | In-scope source references |
141 |---|---|---|---| 145 |---|---|---|---|
142 -| FastJson | 1.2.15 (`xlyPersist`, `xlyApi`) / 1.2.60 (`xlyFlow`) | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 84 files import `com.alibaba.fastjson.*` across all in-scope modules (xlyBusinessService=39, xlyEntry=11, xlyInterface=10, xlyPersist=9, xlyFlow=6, xlyMsg=5, xlyApi=4). | 146 +| FastJson | 1.2.15 (`xlyPersist`, `xlyApi`) / 1.2.60 (`xlyFlow`) | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 83 files import `com.alibaba.fastjson.*` across in-scope modules (xlyBusinessService=39, xlyEntry=11, xlyInterface=9, xlyPersist=9, xlyFlow=6, xlyMsg=5, xlyApi=4). |
143 | Jackson | `jackson-databind` 2.9.7 (`xlyFlow` explicit) + transitive via Spring | `xlyFlow/build.gradle` | 22 files import `com.fasterxml.jackson.*` (xlyFlow=8, xlyInterface=9, xlyApi=2, xlyPersist=2, xlyEntry=1). | 147 | Jackson | `jackson-databind` 2.9.7 (`xlyFlow` explicit) + transitive via Spring | `xlyFlow/build.gradle` | 22 files import `com.fasterxml.jackson.*` (xlyFlow=8, xlyInterface=9, xlyApi=2, xlyPersist=2, xlyEntry=1). |
144 | Hutool | `hutool-all` 5.6.5 (`xlyPersist`) / 5.8.5 (`xlyApi`, `xlyFlow`) | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 271 files import `cn.hutool.*` across every in-scope module (xlyBusinessService=93, xlyFlow=47, xlyApi=37, xlyPersist=33, xlyEntry=25, xlyInterface=23, xlyMsg=10, xlyManage=2, xlyPlc=1). | 148 | Hutool | `hutool-all` 5.6.5 (`xlyPersist`) / 5.8.5 (`xlyApi`, `xlyFlow`) | `xlyPersist/build.gradle`, `xlyApi/build.gradle`, `xlyFlow/build.gradle` | 271 files import `cn.hutool.*` across every in-scope module (xlyBusinessService=93, xlyFlow=47, xlyApi=37, xlyPersist=33, xlyEntry=25, xlyInterface=23, xlyMsg=10, xlyManage=2, xlyPlc=1). |
145 -| commons-lang3 | 3.6 (`xlyPersist`) / 3.8.1 (`xlyFlow`) | `xlyPersist/build.gradle`, `xlyFlow/build.gradle` | 41 files import `org.apache.commons.lang3.*` (xlyFlow=24, xlyPersist=8, xlyEntry=3, xlyApi=2, xlyBusinessService=3, xlyMsg=1). | 149 +| commons-lang3 | 3.6 (`xlyPersist`) / 3.8.1 (`xlyFlow`) | `xlyPersist/build.gradle`, `xlyFlow/build.gradle` | 39 files import `org.apache.commons.lang3.*` (xlyFlow=23, xlyPersist=7, xlyEntry=3, xlyApi=2, xlyBusinessService=3, xlyMsg=1). |
146 | commons-collections4 | 4.1 | `xlyPersist/build.gradle` | 1 file in `xlyBusinessService/src/`. | 150 | commons-collections4 | 4.1 | `xlyPersist/build.gradle` | 1 file in `xlyBusinessService/src/`. |
147 | Groovy | `groovy-all` 3.0.2 | `xlyPersist/build.gradle`, `xlyApi/build.gradle` | 5 Java files import `groovy.util.logging.Slf4j` (xlyPersist=3, xlyApi=2). The annotation is from Groovy's runtime; the Java files using it appear to be vestigial — the import is present but the annotation does not affect Java compilation. | 151 | Groovy | `groovy-all` 3.0.2 | `xlyPersist/build.gradle`, `xlyApi/build.gradle` | 5 Java files import `groovy.util.logging.Slf4j` (xlyPersist=3, xlyApi=2). The annotation is from Groovy's runtime; the Java files using it appear to be vestigial — the import is present but the annotation does not affect Java compilation. |
148 | Struts2 JSON plugin | 2.5.30 | `xlyPersist/build.gradle` | 1 file: `xlyPersist/src/main/java/com/xly/utils/FeedPage.java`. The framework otherwise runs on Spring MVC. | 152 | Struts2 JSON plugin | 2.5.30 | `xlyPersist/build.gradle` | 1 file: `xlyPersist/src/main/java/com/xly/utils/FeedPage.java`. The framework otherwise runs on Spring MVC. |
@@ -205,6 +209,7 @@ since been removed, or may be vestigial. @@ -205,6 +209,7 @@ since been removed, or may be vestigial.
205 | commons-pool2 2.8.0 | `xlyPersist/build.gradle` | No direct imports. Likely transitive support for Jedis or similar. | 209 | commons-pool2 2.8.0 | `xlyPersist/build.gradle` | No direct imports. Likely transitive support for Jedis or similar. |
206 | Baidu SDK (`baidu-sdk-1.4.5.jar`, local) | `xlyInterface/build.gradle` | No `com.baidu` imports found. | 210 | Baidu SDK (`baidu-sdk-1.4.5.jar`, local) | `xlyInterface/build.gradle` | No `com.baidu` imports found. |
207 | `mchange-commons-java` 0.2.11 | `xlyFlow/build.gradle` | No direct imports. | 211 | `mchange-commons-java` 0.2.11 | `xlyFlow/build.gradle` | No direct imports. |
  212 +| Springfox (`springfox-swagger-ui` 2.9.2 + `springfox-swagger2` 2.9.2) | `xlyInterface/build.gradle` | No direct Java imports. Consumed via Spring Boot auto-config to serve `/swagger-ui.html` for the xlyInterface tier. Cited from [API Reference / webhooks](../../api-reference/webhooks.md). |
208 213
209 ## Notable version skew & local jars 214 ## Notable version skew & local jars
210 215
@@ -228,5 +233,5 @@ Pulled directly from the `build.gradle` files. Each is a fact, not a recommendat @@ -228,5 +233,5 @@ Pulled directly from the `build.gradle` files. Each is a fact, not a recommendat
228 233
229 - The plat tier (`xlyPlat*` modules) and dependencies declared only there — out of scope per [index](../../index.md#whats-out-of-scope). 234 - The plat tier (`xlyPlat*` modules) and dependencies declared only there — out of scope per [index](../../index.md#whats-out-of-scope).
230 - AI / LLM libraries (`com.theokanning.openai-gpt3-java:service` 0.11.1 and `com.unfbx:chatgpt-java` 1.0.8 in `xlyApi/build.gradle`) — out of scope. 235 - AI / LLM libraries (`com.theokanning.openai-gpt3-java:service` 0.11.1 and `com.unfbx:chatgpt-java` 1.0.8 in `xlyApi/build.gradle`) — out of scope.
231 -- The MongoDB starter declared in `xlyEntity/build.gradle` (`spring-boot-starter-data-mongodb` 2.2.5). The `xlyEntity` module contains 22 `@Document`-annotated classes under `xlyentity/mongo/`, all named `PLAT_*`. A grep for `MongoTemplate` and `MongoRepository` in in-scope modules returned only `xlyPersist/.../dao/platmongo/BaseMongoDao.java` (which serves the plat tier); no in-scope module invokes Mongo APIs. See the [out-of-scope note in index](../../index.md#whats-out-of-scope). 236 +- The MongoDB starter declared in `xlyEntity/build.gradle` (`spring-boot-starter-data-mongodb` 2.2.5). The `xlyEntity` module contains 22 `@Document`-annotated classes under `xlyentity/mongo/` — 20 named `PLAT_*` plus 2 `DIKE_TEST*` test fixtures. A grep for `MongoTemplate` and `MongoRepository` in in-scope modules returned only `xlyPersist/.../dao/platmongo/BaseMongoDao.java` (which serves the plat tier); no in-scope module invokes Mongo APIs. See the [out-of-scope note in index](../../index.md#whats-out-of-scope).
232 - `xlyFace` — out of scope. 237 - `xlyFace` — out of scope.
en/docs/slices/01-hello-world.md
@@ -105,16 +105,21 @@ returns a single composite map with five keys: @@ -105,16 +105,21 @@ returns a single composite map with five keys:
105 105
106 | Key | Source | What it contains | 106 | Key | Source | What it contains |
107 |---|---|---| 107 |---|---|---|
108 -| `formData` | `gdsmodule` ⋈ `gdsconfigformmaster` ⋈ `gdsconfigformslave` (+ personalize) | The form layout — the spine. |  
109 -| `gdsformconst` | `gdsformconst` rows scoped by `sBrandsId`/`sSubsidiaryId`/language | Form-level constants — labels, defaults, dropdown text. |  
110 -| `gdsjurisdiction` | `gdsjurisdiction` rows for the user's role | Per-button and per-data permissions. **Skipped for `ADMIN` users** (line 196-198) — admins see everything. |  
111 -| `billnosetting` | `sysbillnosettings` row for this module | Document-numbering rule (irrelevant for `gdsformconst` but always loaded). |  
112 -| `report` | print templates linked to this form | Printable-report definitions, if any. |  
113 -  
114 -**Multi-tenancy** is enforced inside this read: every sub-query is scoped by  
115 -`sBrandsId` (manufacturer) and `sSubsidiaryId` (subsidiary), pulled from the  
116 -authenticated user's session via `RequestAddParamUtil.me().addParams()`.  
117 -Tenants do not see each other's metadata. 108 +| `formData` | `gdsconfigformmaster` (filtered by `sParentId = sModelsId`) ⋈ `gdsconfigformpersonalize` (per-tenant overlay); per master row, `gdsconfigformslave` + `gdsconfigformcustomslave` overlays. `gdsmodule` is referenced only by id (sub-selected from the slave queries to resolve `sActiveName`), not joined into the master read. | The form layout — the spine. |
  109 +| `gdsformconst` | `gdsformconst` rows filtered by `sParentId` only. **Not tenant-scoped** — the row identifies the form, and `sLanguage` selects which label column to return. | Form-level constants — labels, defaults, dropdown text. |
  110 +| `gdsjurisdiction` | `sysjurisdiction` rows for the user's role (joined to `sftlogininfojurisdictiongroup` ⋈ `sisjurisdictionclassify`). **Skipped for `ADMIN` users** (line 196-198) — admins see everything. **Note:** the map-key name `gdsjurisdiction` is misleading — `gdsjurisdiction` is the builder-side action *catalogue* table; the per-user grant read here actually queries `sysjurisdiction`. | Per-button and per-data permissions. |
  111 +| `billnosetting` | `sysbillnosettings` row for this module (per-tenant) | Document-numbering rule (irrelevant for `gdsformconst` but always loaded). |
  112 +| `report` | `sysreport` rows linked to this form (per-tenant) | Printable-report definitions, if any. |
  113 +
  114 +**Multi-tenancy** is enforced where it matters: tenant-scoped reads
  115 +(`gdsconfigformpersonalize`, `gdsconfigformcustomslave`,
  116 +`sysbillnosettings`, `sysreport`) all filter by `sBrandsId` (manufacturer)
  117 +and `sSubsidiaryId` (subsidiary), pulled from the authenticated user's
  118 +session via `RequestAddParamUtil.me().addParams()`. The framework-metadata
  119 +tables themselves (`gdsconfigformmaster`, `gdsconfigformslave`,
  120 +`gdsformconst`) are global — they are filtered by form-id only. So a
  121 +tenant cannot see another tenant's *personalized overlays* or
  122 +*business data*, but the underlying form definition is shared.
118 123
119 ### 3. SPA → server (initial data load) 124 ### 3. SPA → server (initial data load)
120 125
@@ -166,17 +171,23 @@ POST /xlyEntry/business/addUpdateDelBusinessData?sModelsId={moduleId} @@ -166,17 +171,23 @@ POST /xlyEntry/business/addUpdateDelBusinessData?sModelsId={moduleId}
166 per-module write-API; the metadata-driven UI generates the payload from 171 per-module write-API; the metadata-driven UI generates the payload from
167 `gdsconfigformmaster.sTbName` and the `gdsconfigformslave` field list. 172 `gdsconfigformmaster.sTbName` and the `gdsconfigformslave` field list.
168 173
169 -- **What the framework does with empty `sSaveProName`:** the runtime takes  
170 - the default Add/Update path implemented in  
171 - `xlyBusinessService/.../AddDelUpdCommonServiceImpl.java`. That path  
172 - generates parameterised `INSERT`/`UPDATE`/`DELETE` against the table  
173 - named in the payload — no stored procedure invoked.  
174 -  
175 -- **What happens when `sSaveProName` is non-empty:** the runtime invokes  
176 - the named stored procedure (the SQL templates under  
177 - `xlyEntry/src/main/resources/templates/templesql/sSaveProName.sql` are  
178 - the *scaffold* engineers fill in when authoring such procs). That path  
179 - is exercised by Slice 2 (workflow), not this one. 174 +- **What the framework does with `sSaveProName`:** the base add/update
  175 + path always runs through
  176 + `BusinessBaseServiceImpl.addBusinessData` / `updateBusinessData`
  177 + (`xlyBusinessService/.../BusinessBaseServiceImpl.java:1014` and
  178 + `:1250`), which delegate to `businessBaseDao.add(map)` /
  179 + `businessBaseDao.update(map)` against the table named by `sTable`.
  180 + `sSaveProName` (and its sibling `sSaveProNameBefore`) is **not** an
  181 + either/or branch that swaps the path — it names an extra stored proc
  182 + that the framework runs as a post-save (or pre-save) hook on top of
  183 + the base path, dispatched by `checkUpdate(...,"sSaveProName")` at
  184 + `BusinessBaseServiceImpl.java:1824`. For Slice 1's `gdsformconst`,
  185 + `sSaveProName` is empty, so only the base path runs. (The SQL
  186 + templates under
  187 + `xlyEntry/src/main/resources/templates/templesql/sSaveProName.sql`
  188 + are the *scaffold* engineers fill in when authoring such hooks.) The
  189 + workflow-gated path with a non-empty `sSaveProName` is exercised by
  190 + Slice 2.
180 191
181 > **Open verification (would require an actual save):** capturing the live 192 > **Open verification (would require an actual save):** capturing the live
182 > request body, the response body, and the resulting SQL in `syslog4j` 193 > request body, the response body, and the resulting SQL in `syslog4j`
@@ -193,8 +204,11 @@ POST /xlyEntry/business/addUpdateDelBusinessData?sModelsId={moduleId} @@ -193,8 +204,11 @@ POST /xlyEntry/business/addUpdateDelBusinessData?sModelsId={moduleId}
193 > `sTableNameList` (`BusinessBaseServiceImpl.java:162-169` — only 204 > `sTableNameList` (`BusinessBaseServiceImpl.java:162-169` — only
194 > `gdsformconst`, `gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`) 205 > `gdsformconst`, `gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`)
195 > is consulted by some branches (e.g., line 1078, 1338, 1423, 1464) but 206 > is consulted by some branches (e.g., line 1078, 1338, 1423, 1464) but
196 -> only as a *cache-invalidation* gate, not as a "is this table authorised  
197 -> for this form?" gate. The hardcoded special case at line 1768 207 +> only as a *multi-tenant scope-bypass* gate (those four tables are global
  208 +> framework metadata, so `sBrandsId`/`sSubsidiaryId` are stripped from
  209 +> writes against them — see the `//不需要公司子公司的表` comment at
  210 +> `BusinessBaseServiceImpl.java:165`). It is **not** a "is this table
  211 +> authorised for this form?" gate. The hardcoded special case at line 1768
198 > (`mftproductionplanslave`) is the only module/table cross-check in the 212 > (`mftproductionplanslave`) is the only module/table cross-check in the
199 > whole flow. Mitigations that exist: 213 > whole flow. Mitigations that exist:
200 > 214 >
@@ -228,7 +242,7 @@ End of trace. @@ -228,7 +242,7 @@ End of trace.
228 242
229 - [The data-driven thesis](../concepts/thesis.md) — why xly stores layouts as data. 243 - [The data-driven thesis](../concepts/thesis.md) — why xly stores layouts as data.
230 - [Modules, forms, virtual tables](../concepts/modules-forms-vtables.md) — the three core nouns. 244 - [Modules, forms, virtual tables](../concepts/modules-forms-vtables.md) — the three core nouns.
231 -- [The metadata-driven request lifecycle](../concepts/request-lifecycle.md) — the four-table read + the three-key result map. 245 +- [The metadata-driven request lifecycle](../concepts/request-lifecycle.md) — the metadata read + the five-key result map.
232 - [Master / slave document pattern](../concepts/master-slave.md) — `gdsconfigformmaster`/`slave` is itself an instance of the pattern. 246 - [Master / slave document pattern](../concepts/master-slave.md) — `gdsconfigformmaster`/`slave` is itself an instance of the pattern.
233 - [No-FK, semantic-FK reality](../concepts/semantic-fk.md) — `gdsconfigformmaster.sParentId = gdsmodule.sId` is a semantic FK, not enforced. 247 - [No-FK, semantic-FK reality](../concepts/semantic-fk.md) — `gdsconfigformmaster.sParentId = gdsmodule.sId` is a semantic FK, not enforced.
234 248
en/docs/slices/03-report.md
@@ -131,10 +131,13 @@ PDF-via-iText. The mechanism is separate from the grid: @@ -131,10 +131,13 @@ PDF-via-iText. The mechanism is separate from the grid:
131 131
132 - `getModelBysId` returns the `report` array, populated from `sysreport` 132 - `getModelBysId` returns the `report` array, populated from `sysreport`
133 rows linked to the form via `sFormId`. 133 rows linked to the form via `sFormId`.
134 -- The frontend's "打印" / "导出" buttons hit `xlyEntry/com/xly/report/`  
135 - controllers (`PrintReportController`, `PrintReportControllerOld`),  
136 - which load a jxls / iText template, run the same view-backed query  
137 - with a "fetch all rows" wrapper, and stream a binary file back. 134 +- The frontend's "打印" / "导出" buttons hit
  135 + `xlyEntry/src/main/java/com/xly/web/report/` — `PrintReportController`
  136 + is the live class. (`PrintReportControllerOld.java` exists in the
  137 + same directory but its class body is fully commented out, dead source.)
  138 + The controller loads a jxls / iText template, runs the same
  139 + view-backed query with a "fetch all rows" wrapper, and streams a
  140 + binary file back.
138 - This module (`工单工序明细`) has no template attached, so we don't 141 - This module (`工单工序明细`) has no template attached, so we don't
139 exercise the print path here. **A future revision of this slice should 142 exercise the print path here. **A future revision of this slice should
140 pick a module that *does* — `print template` is a chapter of its own.** 143 pick a module that *does* — `print template` is a chapter of its own.**