Commit d21ed9699612096dee79bf341367aa99290b1302
1 parent
bbcde3e6
docs: en wiki — finish remaining passes (B P1/P2/P3 gaps, D, E)
Second commit completing the verification plan after the first commit
covered Pass A + most of Pass B P0/P1 + Pass C1/C2.
P1 gaps:
- slices/02-multi-tenancy.md: add the four-table sTableNameList
exception (gdsformconst, gdsmodule, gdsconfigformmaster,
gdsconfigformslave strip tenant cols at write); rewrite query-shape
section to distinguish global framework metadata from tenant-scoped
overlays + business state; flag dev DB has only 8S_001 in
sisversionflow despite gdsmodule.sVersionFlowCode referencing
EBC-SD/EBC-RD/EBC-MDM/etc.; add live row counts (1002 untagged,
322 8S_001, 15 EBC-SD-002, …).
- api-reference/internal.md, messaging.md, notifications.md verified
as-is (Pass A surface fixes were sufficient, all file paths exist).
P2:
- slices/04-custom-field.md: close out the merge-code open item —
merge is Java at BusinessBaseServiceImpl.java:246-248
(getFormSlaveData + getFormCustomSlaveData); the two views supply
the master⋈slave shape, the merge itself is in Java.
- slices/06-hardware.md: PlcScheduledTasks uses Spring @Scheduled cron
(every 30s + every 1s), not Quartz directly.
- concepts/thesis.md: fix the "four-table read" framing — actual reads
are from gdsconfigformmaster (with overlays), gdsformconst,
sysjurisdiction, sysbillnosettings, sysreport. Soften slice-1
reference to remove the "four-table" misframing.
- slices/03-report.md, concepts/api-surface.md, customization-channels,
customization-layers, semantic-fk verified clean (script/客户/
customer dirs = 18, 0 FK / 0 triggers on xly tables, both overlay
views exist).
P3:
- reference/builder/define-form.md: rewrite cache-invalidation section
to match the corrected architecture (synchronous @CacheEvict, not the
JMS path).
- builder/define-vtable, builder/permissions, builder/index,
builder/attach-workflow, slices/07-workflow, glossary/index,
reference/maintainer/index, slices/index, contributing/index
verified appropriately marked deferred / consistent.
Pass D consistency sweep:
- Ports (8080/8090/8000/8091/8597/8598) consistent across pages.
- Schema names consistently use xlyweberp_saas_ai;
running-locally.md correctly documents the xlyweberp_saas vs _ai
divergence.
- Counts (1358 gdsmodule rows, 311 views, 1687 procs, 177 functions)
consistent.
- Module/Modle/Models codebase quirk consistently surfaced.
- xlyPlc plugin framing consistent across index.md, tech-stack.md,
slice 06.
- vtable / virtual table — single canonical form ("virtual table") used
in narrative; URL-style filename `define-vtable` preserved.
- proc / procedure / stored procedure usage clean (long form for
emphasis, short for lists).
Pass E live traces (partial):
- Slice 1 read flow CORROBORATED end-to-end at BACK admin/123 edition
基础版/8s. Both documented URLs captured exactly:
GET /xlyEntry/business/getModelBysId/13?sModelsId=13 → 200
POST /xlyEntry/business/getBusinessDataByFormcustomId/19211681019715574676360040?sModelsId=13 → 200
including the redundant ?sModelsId=13 alongside the path variable.
Updated slice-01 open verification items to mark the read item closed.
- Slice 1 save flow + slices 2-6 traces deferred (would mutate shared
dev DB; FROUNT enumeration declined; monthly usage limit reached).
Pass C3 frontend inventory (partial):
- BACK admin sidebar = 10 framework-builder modules: 系统模块配置,
数据表内容配置, 界面显示内容配置, 接口自定义配置, 系统常量配置,
系统权限配置, 常用操作配置, 用户信息配置, Mysql脚本配置, 图表配置.
- 8/10 covered by existing wiki pages; 2 silent (常用操作配置,
图表配置 — admin BACK utilities, not framework runtime).
- FROUNT enumeration declined by user.
Showing
6 changed files
with
85 additions
and
42 deletions
en/docs/concepts/thesis.md
| ... | ... | @@ -22,10 +22,13 @@ engineers) own the metadata and therefore own the application. |
| 22 | 22 | |
| 23 | 23 | Three costs are baked into this design and worth being explicit about: |
| 24 | 24 | |
| 25 | -1. **Per-request metadata reads.** Every page load runs at least four | |
| 26 | - table reads (`gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`, | |
| 27 | - `gdsjurisdiction`) plus tenant filters. The runtime caches aggressively, | |
| 28 | - but those reads are unavoidable on cache miss. | |
| 25 | +1. **Per-request metadata reads.** Every page load runs five queries | |
| 26 | + on cache miss: `gdsconfigformmaster` (with personalize/customslave | |
| 27 | + overlays for the matching slave rows), `gdsformconst`, | |
| 28 | + `sysjurisdiction` (per-user grants — the map key is named | |
| 29 | + `gdsjurisdiction` but the actual table read is `sysjurisdiction`; | |
| 30 | + skipped for ADMIN), `sysbillnosettings`, `sysreport`. The runtime | |
| 31 | + caches aggressively, but those reads are unavoidable on cache miss. | |
| 29 | 32 | |
| 30 | 33 | 2. **A schema that won't stop growing.** New module = a row in |
| 31 | 34 | `gdsmodule` plus 1-50 rows in `gdsconfigformslave` plus a backing |
| ... | ... | @@ -66,7 +69,7 @@ customization to maintain. |
| 66 | 69 | ## What this means for reading the wiki |
| 67 | 70 | |
| 68 | 71 | Every slice in this wiki documents one *application of the thesis*. Slice 1 |
| 69 | -is the four-table read on a CRUD module. Slice 2 is multi-tenant scoping | |
| 72 | +is the metadata read on a CRUD module — the canonical instance. Slice 2 is multi-tenant scoping | |
| 70 | 73 | through every layer. Slice 3 is the read-only / view-backed variant. Slice |
| 71 | 74 | 4 is the customization overlay. Slice 5 is the escape hatch when the |
| 72 | 75 | overlay isn't enough. Together they cover the data-driven design from | ... | ... |
en/docs/reference/builder/define-form.md
| ... | ... | @@ -106,9 +106,14 @@ Click the new sidebar item — the form should render. If it doesn't: |
| 106 | 106 | |
| 107 | 107 | ## Cache invalidation |
| 108 | 108 | |
| 109 | -After inserting, the runtime's cache holds the *previous* (empty) state | |
| 110 | -until a JMS message fires. xly's cache-invalidation listener | |
| 111 | -(`ConsumerChangeGdsModuleThread`) handles this when the BACK builder | |
| 112 | -saves changes; if you're inserting via raw SQL, you may need to bounce | |
| 113 | -the running services or wait for the TTL to expire. See | |
| 114 | -[Cache invalidation on metadata change](../maintainer/cache-invalidation.md). | |
| 109 | +After inserting, the runtime's cache holds the *previous* (empty) | |
| 110 | +state until something clears it. When BACK saves the change, the save | |
| 111 | +service synchronously calls `BusinessCleanRedisData.delCleanRedisData*`, | |
| 112 | +which fires `@CacheEvict` on the relevant cache regions in | |
| 113 | +`CleanRedisServiceImpl`. If you're inserting via raw SQL, **no eviction | |
| 114 | +runs** — you'll need to either invoke `BusinessCleanRedisDataImpl` | |
| 115 | +methods directly from inside the application, bounce the running | |
| 116 | +services, or wait for the TTL to expire. (The JMS path with the | |
| 117 | +similarly-named `ConsumerChangeGdsModuleThread` does base-data merging | |
| 118 | +via stored proc, NOT cache invalidation despite the name — see | |
| 119 | +[Cache invalidation on metadata change](../maintainer/cache-invalidation.md).) | ... | ... |
en/docs/slices/01-hello-world.md
| ... | ... | @@ -256,19 +256,31 @@ End of trace. |
| 256 | 256 | **Maintainer track:** |
| 257 | 257 | |
| 258 | 258 | - [The runtime: BusinessBaseController & friends](../reference/maintainer/runtime.md) — the controller and service layer that did the four-table read. |
| 259 | -- [Cache invalidation on metadata change](../reference/maintainer/cache-invalidation.md) — JMS-driven Redis flush. | |
| 259 | +- [Cache invalidation on metadata change](../reference/maintainer/cache-invalidation.md) — synchronous `@CacheEvict` (the JMS path serves a different purpose). | |
| 260 | 260 | - [Multi-service deployment](../reference/maintainer/deployment.md) — `xlyEntry` vs `xlyApi` vs `xlyInterface`; this slice runs entirely on `xlyEntry`. |
| 261 | 261 | |
| 262 | 262 | ## Open verification items |
| 263 | 263 | |
| 264 | -Two items still open; one is now closed. | |
| 265 | - | |
| 266 | -1. **A live captured save.** The endpoint, handler, and payload shape are | |
| 264 | +Read flow live-corroborated; save flow still pending. | |
| 265 | + | |
| 266 | +1. ~~**A live captured read.**~~ **CLOSED** — clicking 系统常量配置 in BACK | |
| 267 | + (`http://118.178.19.35:8597`, admin/123, edition `基础版/8s`) produced | |
| 268 | + exactly the documented HTTP exchange: | |
| 269 | + ``` | |
| 270 | + GET /xlyEntry/business/getModelBysId/13?sModelsId=13 → 200 OK | |
| 271 | + POST /xlyEntry/business/getBusinessDataByFormcustomId/19211681019715574676360040?sModelsId=13 → 200 OK | |
| 272 | + ``` | |
| 273 | + Both URLs match the wiki verbatim, including the redundant `?sModelsId=13` | |
| 274 | + query param alongside the path variable. The post-login URL stays at | |
| 275 | + `/xtmkpz` rather than navigating to `/xtclpz` — confirming that URL | |
| 276 | + fragments are display state, not route drivers (the SPA loads the | |
| 277 | + form on demand from the sidebar click). | |
| 278 | +2. **A live captured save.** The endpoint, handler, and payload shape are | |
| 267 | 279 | confirmed from source; the *body of an actual save request* hasn't been |
| 268 | 280 | captured. This requires writing to the dev DB and we deferred it. Closing |
| 269 | 281 | this means: open the module, click 新增, fill the form, click 保存, capture |
| 270 | 282 | the JSON body and the response. |
| 271 | -2. **The exact SQL emitted by save/delete**, captured from `syslog4j` or a | |
| 283 | +3. **The exact SQL emitted by save/delete**, captured from `syslog4j` or a | |
| 272 | 284 | MyBatis debug log, to close the loop end-to-end. |
| 273 | 285 | 3. ~~**`sTable` validation in `addUpdateDelBusinessData`.**~~ **CLOSED** — |
| 274 | 286 | the runtime does **not** cross-check the frontend-supplied `sTable` | ... | ... |
en/docs/slices/02-multi-tenancy.md
| ... | ... | @@ -26,12 +26,17 @@ applied at module-list load time. Different mechanisms, different layers. |
| 26 | 26 | |
| 27 | 27 | ### How wide |
| 28 | 28 | |
| 29 | -Essentially every base table and every view in the schema carries both | |
| 30 | -`sBrandsId` and `sSubsidiaryId` — both business-data tables and | |
| 31 | -framework-metadata tables. The convention is followed almost without | |
| 32 | -exception; the few tables that lack one or both columns are either | |
| 33 | -single-tenant lookups (e.g., shared dictionary tables) or third-party | |
| 34 | -schemas the framework doesn't author. | |
| 29 | +Essentially every business-data table and view in the schema carries | |
| 30 | +both `sBrandsId` and `sSubsidiaryId`. **Most framework-metadata tables | |
| 31 | +also carry the columns**, but four of them — `gdsformconst`, | |
| 32 | +`gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave` — are an | |
| 33 | +explicit exception: `BusinessBaseServiceImpl.sTableNameList` (lines | |
| 34 | +162-169) lists them as "不需要公司子公司的表", and lines 1078-1084 strip | |
| 35 | +`sBrandsId`/`sSubsidiaryId` from the write payload for those tables. | |
| 36 | +In practice they hold a single sentinel tenant value shared across | |
| 37 | +all customers. The other tables that lack one or both columns are | |
| 38 | +single-tenant shared dictionaries or third-party schemas | |
| 39 | +(`act_*`, `qrtz_*`). | |
| 35 | 40 | |
| 36 | 41 | ### How they're injected |
| 37 | 42 | |
| ... | ... | @@ -68,16 +73,23 @@ session via the `@CurrentUser` argument resolver. |
| 68 | 73 | |
| 69 | 74 | ### How it shows up in queries |
| 70 | 75 | |
| 71 | -The Slice-1 `getModelBysId` call ended up reading metadata via: | |
| 76 | +In the Slice-1 `getModelBysId` call, the per-tenant predicate | |
| 72 | 77 | |
| 73 | 78 | ```sql |
| 74 | 79 | WHERE sBrandsId = #{sBrandsId} AND sSubsidiaryId = #{sSubsidiaryId} |
| 75 | 80 | ``` |
| 76 | 81 | |
| 77 | -…on every metadata table (`gdsmodule`, `gdsconfigformmaster`, | |
| 78 | -`gdsconfigformslave`, `gdsformconst`, `gdsjurisdiction`, `sysbillnosettings`). | |
| 79 | -The same predicate appears in essentially every business-data query in the | |
| 80 | -codebase. This is the multi-tenancy boundary at runtime. | |
| 82 | +is added on **per-tenant overlay reads** (`gdsconfigformpersonalize`, | |
| 83 | +`gdsconfigformcustomslave`) and on the **business-state** reads | |
| 84 | +(`sysbillnosettings`, `sysreport`, plus `sysjurisdiction` joined to | |
| 85 | +`sftlogininfojurisdictiongroup` for per-user grants — note the | |
| 86 | +returned map key is `gdsjurisdiction` even though the actual table | |
| 87 | +read is `sysjurisdiction`). It is *not* added on the framework-metadata | |
| 88 | +reads (`gdsmodule`, `gdsconfigformmaster`, `gdsconfigformslave`, | |
| 89 | +`gdsformconst`) — those are global and filtered by form-id only. The | |
| 90 | +same per-tenant predicate appears in essentially every business-data | |
| 91 | +query in the codebase. This is the multi-tenancy boundary at runtime | |
| 92 | +for tenant-owned state; framework metadata is intentionally global. | |
| 81 | 93 | |
| 82 | 94 | ### Failure mode |
| 83 | 95 | |
| ... | ... | @@ -101,8 +113,8 @@ extras. |
| 101 | 113 | |
| 102 | 114 | ### Where editions are defined |
| 103 | 115 | |
| 104 | -Editions live in the `sisversionflow` table — one row per edition. The | |
| 105 | -key columns: | |
| 116 | +Editions are defined in the `sisversionflow` lookup table — one row | |
| 117 | +per edition. The key columns: | |
| 106 | 118 | |
| 107 | 119 | | Column | Meaning | |
| 108 | 120 | |---|---| |
| ... | ... | @@ -111,6 +123,14 @@ key columns: |
| 111 | 123 | | `sFlowName` | Display name (e.g., `基础版`) | |
| 112 | 124 | | `bEbcErpPremium`, `bEbcMes`, `bEbcMesStandard`, `bSass` | Flags marking which product variants this edition belongs to | |
| 113 | 125 | |
| 126 | +> **State of this dev DB:** `sisversionflow` currently defines only | |
| 127 | +> one row — `8S_001 / 基础版`. The other edition codes shown in | |
| 128 | +> `gdsmodule.sVersionFlowCode` (`EBC-SD-002`, `EBC-RD-007`, | |
| 129 | +> `EBC-MDM-002`, etc.) are referenced as tags on module rows but have | |
| 130 | +> **no matching row in the `sisversionflow` lookup table** here. A | |
| 131 | +> production tenant of the SaaS likely populates the lookup table with | |
| 132 | +> the full edition catalog; the dev DB doesn't. | |
| 133 | + | |
| 114 | 134 | ### How modules are filtered per edition |
| 115 | 135 | |
| 116 | 136 | `sVersionFlowId` lives on `gdsmodule` and on a couple of historical |
| ... | ... | @@ -121,14 +141,15 @@ tenant is on, then filters the visible module list to those matching |
| 121 | 141 | `gdsmodule.sVersionFlowId`. From there, every loaded module reads its |
| 122 | 142 | data with `sBrandsId`/`sSubsidiaryId` scoping as normal. |
| 123 | 143 | |
| 124 | -Within `gdsmodule`, three tagging patterns coexist: | |
| 144 | +Within `gdsmodule` (1358 rows in the dev DB), three tagging patterns coexist: | |
| 125 | 145 | |
| 126 | -- **Untagged rows** (`sVersionFlowId` empty) — framework-internal modules | |
| 146 | +- **Untagged rows** (`sVersionFlowCode` empty) — 1002 rows. Framework-internal modules | |
| 127 | 147 | and screens the edition gate does not apply to. The bulk of the catalog. |
| 128 | -- **Essentials-tagged rows** (`sVersionFlowCode = '8S_001'`) — the | |
| 148 | +- **Essentials-tagged rows** (`sVersionFlowCode = '8S_001'`) — 322 rows. The | |
| 129 | 149 | universally-licensed core every edition gets. |
| 130 | -- **Edition-specific rows** (`EBC-SD-*`, `EBC-MDM-*`, `EBC-RD-*`, | |
| 131 | - `EBC-COM-*`, `EBC_*`) — add-ons gated by the customer's licence. | |
| 150 | +- **Edition-specific rows** — `EBC-SD-002` (15), `EBC-RD-007` (6), | |
| 151 | + `EBC-MDM-002` (5), `EBC_001` (4), `EBC-SD-003` (2), `EBC-SD-001` (1), | |
| 152 | + `EBC-COM-001` (1) — add-ons gated by the customer's licence. | |
| 132 | 153 | |
| 133 | 154 | ## Concepts this slice introduces |
| 134 | 155 | ... | ... |
en/docs/slices/04-custom-field.md
| ... | ... | @@ -143,12 +143,14 @@ forms never use. |
| 143 | 143 | 1. **Find the live BACK page that edits `gdsconfigformcustomslave`.** |
| 144 | 144 | Most likely `界面显示内容配置` from the sidebar; confirm by clicking |
| 145 | 145 | in BACK and checking which table the save endpoint writes to. |
| 146 | -2. **Trace the merge code.** Locate the exact join logic — is the merge | |
| 147 | - done in MyBatis (the two views) or in Java | |
| 148 | - (`BusinessGdsconfigformsServiceImpl.getFormSlaveData` + | |
| 149 | - `getFormCustomSlaveData`, called in | |
| 150 | - `BusinessBaseServiceImpl.java:246-248`)? Both look plausible from | |
| 151 | - the source we've seen. | |
| 146 | +2. ~~**Trace the merge code.**~~ **CLOSED** — the merge happens in | |
| 147 | + Java at `BusinessBaseServiceImpl.java:246-248`: it calls | |
| 148 | + `businessGdsconfigformsService.getFormSlaveData(map)` then | |
| 149 | + `getFormCustomSlaveData(map)` and `addAll`s them into one | |
| 150 | + `slaveList`. The two views (`gdsconfigformslavemasterview`, | |
| 151 | + `gdsconfigformcustomslavemasterview`) supply the joined-with-master | |
| 152 | + shape that each call reads — the *merge* is Java; the | |
| 153 | + *master-with-slave join* is SQL. | |
| 152 | 154 | 3. **`bVisible = false` semantics.** Does setting `bVisible = false` on |
| 153 | 155 | a `gdsconfigformcustomslave` row *hide* an existing base field |
| 154 | 156 | (an override-to-remove pattern), or only suppress the override | ... | ... |
en/docs/slices/06-hardware.md
| ... | ... | @@ -20,7 +20,7 @@ generic framework picks up. |
| 20 | 20 | |---|---| |
| 21 | 21 | | **Module** | `xlyPlc` (a sibling Spring Boot service in the codebase) | |
| 22 | 22 | | **Direction** | One-way: PLC → ERP DB (no commands sent back to the press) | |
| 23 | -| **Cadence** | Polled on a schedule (Quartz `PlcScheduledTasks`) | | |
| 23 | +| **Cadence** | Polled on a schedule (Spring `@Scheduled` cron in `PlcScheduledTasks`, e.g. `0/30 * * * * ?` and `0/1 * * * * ?`) | | |
| 24 | 24 | | **Per-press differentiation** | Spring profile (`-S10`, `-T0`, `-T1`, `-15S`, `-CT`, `-yt`, `-pro`) | |
| 25 | 25 | | **Database tables it writes to** | `mftProduceReportMachineState`, and related machine-state slaves | |
| 26 | 26 | |
| ... | ... | @@ -31,7 +31,7 @@ generic framework picks up. |
| 31 | 31 | | File | Role | |
| 32 | 32 | |---|---| |
| 33 | 33 | | `PlcApplicationBoot.java` | Spring Boot entry point | |
| 34 | -| `web/scheduler/PlcScheduledTasks.java` | Quartz-driven poll loop | | |
| 34 | +| `web/scheduler/PlcScheduledTasks.java` | `@Component` with two `@Scheduled` cron methods (every 30 s and every 1 s) driving the poll loop | | |
| 35 | 35 | | `web/scheduler/PlcRunStatus.java` | In-memory state for the current poll cycle | |
| 36 | 36 | | `web/scheduler/service/PlcToErpService.java` | Interface | |
| 37 | 37 | | `web/scheduler/service/impl/PlcToErpServiceImpl.java` | The implementation: read PLC, write DB | | ... | ... |