Commit 4ea83bcc5e672b7f85e2471e21535d07858338dc

Authored by zichun
1 parent d21ed969

docs: en wiki — initiative pass to close open items

Third commit closing high-value gaps the user flagged in the
verification plan.

Cross-node cache coherence — LOCKED EMPIRICALLY:
- Connected to live Redis at 118.178.19.35:16379 db=0.
- 233 of 267 keys use Spring's `<cacheName>::<key>` separator.
- Confirmed key shapes match @Cacheable SpEL specs:
    businessGdsconfigformsServiceGetFormconstData::{...}  (37 entries)
    gdsmoduleById::gdsmoduleById_<sBrandsId>_<sSubsidiaryId>_<sLanguage>  (2 entries)
- Conclusion: Spring's RedisCacheManager IS the active CacheManager.
  @CacheEvict on any node clears the shared Redis store; cross-node
  coherence works without any JMS involvement. Removed the "open
  question" hedge in cache-invalidation.md.

New page — Metadata-management services (xlyManage):
- Closes the biggest documentation gap surfaced by Pass C2.
- Catalogs the 8 large Gds*ServiceImpl classes (878+729+555+489+
  362+319+243+221 = ~3,800 lines of metadata-CRUD logic) plus
  CommonServiceImpl (56) and SysbrandsServiceImpl (125).
- Documents the universal five-method shape every Gds*Service
  follows (get/getBysId/add/update/delete) and how it pairs with
  the corresponding Gds*Controller in xlyEntry/.../systemweb/.
- Maps each service to its BACK admin screen.
- Notes the cache-invalidation hookpoint (synchronous
  BusinessCleanRedisData.delCleanRedisData* on commit).
- Added to mkdocs nav under Reference (Maintainer).

Worked examples:
- Slice 04: gdsconfigformcustomslave is empty in the dev DB
  (0 rows). Updated the open verification item to confirm this
  rather than leave the framing "depends on the deployment".
- Slice 05: side-by-side diff of 重庆展印's Sp_SalSalesCheck vs
  the standard. Quantified differences (1714 vs 723 lines, same
  14-param signature, override adds CbxSrcNoCheck branch and
  strips temp-table aggregation, 12 sibling procs use the same
  CbxSrcNoCheck pattern). Added a copy-pasteable diff command.

Honest scope acknowledgement:
- api-reference/internal.md: explicit "what this catalog
  includes vs treats as illustrative" paragraph. ~19 framework-
  primitive controllers documented; ~52 business-domain
  controllers (workorder/salesorder/productionPlan/etc.) treated
  as illustrations of the framework at work, with grep guidance
  for maintainers who need to find them.

Auto-catalog regenerated:
- Ran scripts/gen_catalog.py against live DB. No changes
  (catalog was already current); 3081 generated pages.

Pass E save trace:
- Tried multiple angles (UI flow, hand-crafted POST with token
  in Authorization header, fetch interception). Edit-mode UI
  didn't yield a save fire under our setup (Ant Design grid +
  Vue SPA edit-mode peculiarity). Read trace remains
  fully verified end-to-end. Save body shape is documented in
  the Javadoc on BusinessBaseController.java:161-163 and
  reflected in the wiki; live save corroboration deferred again.
en/docs/api-reference/internal.md
@@ -82,6 +82,25 @@ get past `@Authorization`; if it does (e.g., an unannotated method), @@ -82,6 +82,25 @@ get past `@Authorization`; if it does (e.g., an unannotated method),
82 that method also bypasses the universal tenant injection in 82 that method also bypasses the universal tenant injection in
83 `RequestAddParamUtil` — and is therefore a multi-tenant bug. 83 `RequestAddParamUtil` — and is therefore a multi-tenant bug.
84 84
  85 +## Coverage policy — what this catalog includes
  86 +
  87 +`xlyEntry` hosts **~71 controllers** in total. This page enumerates
  88 +the ~19 that are part of the framework's universal runtime:
  89 +`/business/*`, `/configform/*`, `/treegrid/*`, `/procedureCall/*`,
  90 +`/panel/*`, `/checkflow/*`, `/gdsmodule/*`, `/gdsconfigform/*`,
  91 +`/gdsconfigtb/*`, plus the print surface. Every form in the system
  92 +flows through these.
  93 +
  94 +The remaining ~52 controllers are **business-domain modules**
  95 +(`/sysworkorder`, `/salesorder`, `/productionPlan`, `/oee`,
  96 +`/eleMaterialsStock`, etc.) — they implement specific industry-tier
  97 +flows on top of the framework primitives above. The wiki treats those
  98 +as *illustrations of the framework at work*, not as catalogued surface
  99 +of their own. Maintainers who need to find a specific business
  100 +controller should grep `xlyEntry/src/main/java/com/xly/web/` for the
  101 +URL prefix; the framework primitives on this page are what's worth
  102 +reading first.
  103 +
85 ## What this API is *not* 104 ## What this API is *not*
86 105
87 - **Not stable** — endpoint shapes change with the framework. 106 - **Not stable** — endpoint shapes change with the framework.
en/docs/reference/maintainer/cache-invalidation.md
@@ -82,23 +82,32 @@ The same goes for the other 23 `ERP_JMS_ACTIVEMQ_*` queues in @@ -82,23 +82,32 @@ The same goes for the other 23 `ERP_JMS_ACTIVEMQ_*` queues in
82 domain-specific base-data merge or fan-out work item, not cache 82 domain-specific base-data merge or fan-out work item, not cache
83 invalidation. 83 invalidation.
84 84
85 -## The cross-node coherence question (open) 85 +## Cross-node cache coherence — Redis-backed, confirmed
86 86
87 `@EnableCaching` is on `EntryApplicationBoot.java:22` and 87 `@EnableCaching` is on `EntryApplicationBoot.java:22` and
88 `ApiApplicationBoot.java:24`. **No custom `CacheManager` bean is 88 `ApiApplicationBoot.java:24`. **No custom `CacheManager` bean is
89 declared anywhere in the in-scope source** (no `RedisCacheManager`, 89 declared anywhere in the in-scope source** (no `RedisCacheManager`,
90 no `@Bean CacheManager`-returning method, no `implements CacheManager`, 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. 91 +no `spring.cache.*` property in any `application*.yml`). With
  92 +`spring-boot-starter-data-redis` present and no other cache provider
  93 +on the classpath (no Caffeine, EhCache, Hazelcast, JCache; the
  94 +`shiro-ehcache` jar in `xlyFlow` is for Shiro's own session cache,
  95 +not Spring Cache), Spring Boot 2.2.5 auto-configures
  96 +**`RedisCacheManager`**.
  97 +
  98 +**Empirically verified** against the live dev Redis at
  99 +`118.178.19.35:16379` (database 0): 233 of 267 keys use Spring Cache's
  100 +default `<cacheName>::<key>` separator. Sample key shape matching the
  101 +`@Cacheable` SpEL spec from `BusinessGdsconfigformsServiceImpl.java:209-211`:
  102 +
  103 +```
  104 +businessGdsconfigformsServiceGetFormconstData::{sLanguage=sChinese, sModelsId=…, sSubsidiaryId=1111111111, sUserId=…, sBrandsId=1111111111}
  105 +gdsmoduleById::gdsmoduleById_<sBrandsId>_<sSubsidiaryId>_<sLanguage>
  106 +```
  107 +
  108 +So `@CacheEvict` on any node clears the shared Redis store and the next
  109 +read on every node sees the eviction. Cross-node coherence works
  110 +through Redis, not through JMS.
102 111
103 ## When you change metadata directly via SQL 112 ## When you change metadata directly via SQL
104 113
en/docs/reference/maintainer/management-services.md 0 → 100644
  1 +# Metadata-management services (`xlyManage`)
  2 +
  3 +The `xlyManage` module is the **service tier behind the BACK builder**.
  4 +When a PM clicks 修改 / 新增 / 删除 in any of the 系统模块配置 /
  5 +界面显示内容配置 / 数据表内容配置 / 系统权限配置 / 系统常量配置 /
  6 +用户信息配置 / Mysql脚本配置 screens, the call lands on a
  7 +`Gds*Controller` in `xlyEntry/.../web/systemweb/`, which then delegates
  8 +to a `Gds*ServiceImpl` in `xlyManage/src/main/java/com/xly/service/systeminfo/impl/`.
  9 +
  10 +These services are the framework's **metadata-CRUD spine** — they own
  11 +the read/write logic for every `gds*` table. The runtime
  12 +([Slice 1](../../slices/01-hello-world.md), [runtime.md](runtime.md))
  13 +*reads* metadata; `xlyManage` *writes* it.
  14 +
  15 +## Services at a glance
  16 +
  17 +| Service | Lines | Owns | BACK page |
  18 +|---|---:|---|---|
  19 +| `GdsmoduleServiceImpl` | 729 | `gdsmodule` (modules), `gdsroute` (URL whitelist), module-tree CRUD, edition gating | 系统模块配置 |
  20 +| `GdsconfigformServiceImpl` | 878 | `gdsconfigformmaster`, `gdsconfigformslave`, `gdsconfigformcustomslave`, `gdsconfigformpersonalize` (form definitions + per-tenant overlays) | 界面显示内容配置 |
  21 +| `GdsconfigtbServiceImpl` | 555 | `gdsconfigtbmaster`, `gdsconfigtbslave` (virtual-table definitions) | 数据表内容配置 |
  22 +| `SqlScriptsServiceImpl` | 489 | DDL / proc / view scripts authored in BACK; uses templates from [`templesql/`](sql-templates.md) | Mysql脚本配置 |
  23 +| `GdsjurisdictionServiceImpl` | 362 | `gdsjurisdiction` (the action *catalogue* per module — see [permissions](../builder/permissions.md)) | (part of 系统权限配置 + builder flows) |
  24 +| `GdsparameterServiceImpl` | 319 | `gdsparameter` (system-wide parameters) | (parameter screens) |
  25 +| `GdsformconstServiceImpl` | 243 | `gdsformconst` (per-form constants — labels, default text). Slice 1 anchor table. | 系统常量配置 |
  26 +| `GdslogininfoServiceImpl` | 221 | `sftlogininfo*` family (user / login / group catalogue) | 用户信息配置 |
  27 +| `SysbrandsServiceImpl` | 125 | `sysbrands` (manufacturer / tenant master) | (tenant admin) |
  28 +| `CommonServiceImpl` | 56 | shared helpers used across the rest | n/a |
  29 +
  30 +Total: ~4,000 lines of metadata-CRUD logic — a meaningful share of the
  31 +framework runtime that this wiki had previously left undocumented.
  32 +
  33 +## Method-shape convention
  34 +
  35 +Every `Gds*Service` follows the same five-method shape:
  36 +
  37 +```java
  38 +Feedback<Map<String,Object>> getXxx(Map<String, Object> params); // list / paged read
  39 +Feedback<Map<String,Object>> getXxxBysId(Map<String, Object> params); // single-row read
  40 +Feedback<Map<String,Object>> addXxx(Map<String, Object> params); // insert
  41 +Feedback<Map<String,Object>> updateXxx(Map<String, Object> params); // update
  42 +Feedback<Map<String,Object>> deleteXxx(Map<String, Object> params); // delete (often soft, via bInvalid)
  43 +```
  44 +
  45 +— mirrored exactly by the corresponding `Gds*Controller`'s endpoint
  46 +methods. So the BACK admin surface is essentially a thin pass-through:
  47 +controllers in `xlyEntry/.../systemweb/`, services in
  48 +`xlyManage/.../systeminfo/impl/`. This is the inverse of the runtime
  49 +side ([runtime.md](runtime.md)), where one universal
  50 +`BusinessBaseController` handles every business-data CRUD against any
  51 +table the metadata names — here, every framework-metadata CRUD has its
  52 +own dedicated controller+service pair.
  53 +
  54 +## Notable specifics
  55 +
  56 +- **`GdsconfigformServiceImpl` is the largest** because it owns four
  57 + closely-coupled tables (form-master, form-slave, customslave,
  58 + personalize) plus the **DDL-script generation** flow. Methods like
  59 + `getFormslaveScriptSqlPro` and `getMasterSlaveScriptSqlPro` produce
  60 + SQL that an engineer can apply against the underlying physical
  61 + tables when adding fields. This is what makes the
  62 + customization-overlay model ([Slice 4](../../slices/04-custom-field.md))
  63 + end-to-end usable: the BACK builder can also generate the
  64 + schema-migration SQL the overlay implies.
  65 +- **`GdsmoduleServiceImpl` includes `getModuleTreePro`** — the
  66 + per-edition / per-tenant module-tree resolution called by the SPA at
  67 + login (the first `/gdsmodule/getModuleTreePro` request you see in
  68 + the live trace). Edition gating
  69 + ([Slice 2](../../slices/02-multi-tenancy.md)) happens here, as a
  70 + filter on `gdsmodule.sVersionFlowId` against the user's
  71 + `sisversionflow` row.
  72 +- **`SqlScriptsServiceImpl`** glues the
  73 + [`templesql/`](sql-templates.md) scaffolds into the BACK script
  74 + authoring screen. Engineers fill in the placeholder spec; the
  75 + service materialises a compilable proc/view body and runs it
  76 + against the connected schema.
  77 +- **`GdsjurisdictionServiceImpl` writes the action *catalogue***;
  78 + `sysjurisdiction` (the per-user grant table) is written elsewhere
  79 + (in `xlyBusinessService`'s permission-admin flow). See
  80 + [How to set permissions](../builder/permissions.md) for the
  81 + catalogue-vs-grant distinction.
  82 +- **`SysbrandsServiceImpl`** writes the tenant master
  83 + (`sysbrands` + `sBrandsId` rows); a fresh tenant onboarding flow
  84 + is essentially a row insert here plus seed metadata.
  85 +
  86 +## Cache-invalidation hookpoints
  87 +
  88 +Every write through these services synchronously calls
  89 +`BusinessCleanRedisData.delCleanRedisData*` on commit. This is why
  90 +metadata edits in BACK take effect immediately on every node — the
  91 +shared Redis cache (RedisCacheManager, see
  92 +[cache-invalidation.md](cache-invalidation.md)) gets the relevant
  93 +regions evicted in the same transaction the write commits. There is
  94 +**no JMS fan-out here for cache-bust** — that's a common
  95 +misconception, addressed in detail on the cache-invalidation page.
  96 +
  97 +## What's *not* in `xlyManage`
  98 +
  99 +- **Business-data CRUD.** That's the universal
  100 + `BusinessBaseController` + `BusinessBaseServiceImpl` path
  101 + ([runtime.md](runtime.md), [Slice 1](../../slices/01-hello-world.md)).
  102 +- **API metadata** (`sysapi`). That's `xlyApi`'s own admin surface —
  103 + see [external API](../../api-reference/external.md).
  104 +- **Workflow metadata** (`gdsmoduleflow`, `act_*`, `biz_flow`). That's
  105 + in `xlyFlow` — see [activiti.md](activiti.md).
  106 +
  107 +## Where to look first when something breaks
  108 +
  109 +| Symptom | First place to look |
  110 +|---|---|
  111 +| BACK 修改/新增 against `gdsconfigform*` returns "操作失败" | `GdsconfigformServiceImpl` — check field validation + the matching DDL-script generation path |
  112 +| Edition gating shows wrong modules | `GdsmoduleServiceImpl.getModuleTreePro` — verify the user's `sVersionFlowId` resolution |
  113 +| BACK script-authoring screen produces broken SQL | `SqlScriptsServiceImpl` + the [templesql scaffolds](sql-templates.md) |
  114 +| Permission catalogue (BtnAdd / BtnUpd / …) missing for a module | `GdsjurisdictionServiceImpl` — check the rows under that `sParentId` |
  115 +| User can log in to BACK but FROUNT is empty | `GdslogininfoServiceImpl` — check the `sftlogininfo*` link tables |
en/docs/slices/04-custom-field.md
@@ -53,10 +53,12 @@ implementer) does this without touching `gdsconfigformslave`. Instead: @@ -53,10 +53,12 @@ implementer) does this without touching `gdsconfigformslave`. Instead:
53 The next time anyone in that tenant loads the form, they see the extra 53 The next time anyone in that tenant loads the form, they see the extra
54 column. Every other tenant continues to see the unmodified base form. 54 column. Every other tenant continues to see the unmodified base form.
55 55
56 -> **Caveat.** Whether `gdsconfigformcustomslave` is populated depends on  
57 -> the deployment — the table is wired into the framework, but a deployment  
58 -> with no tenant-level field overrides will see it empty. The trace below  
59 -> is code-derived; observe a populated deployment to validate end-to-end. 56 +> **Confirmed against the dev DB:** `gdsconfigformcustomslave` is
  57 +> currently **empty** in `xlyweberp_saas_ai` (0 rows). The table is
  58 +> wired into the framework but no tenant on this dev DB has registered
  59 +> any field-level override. The trace below is code-derived; the
  60 +> end-to-end *observed* behaviour requires a tenant deployment that
  61 +> actually populates the table.
60 62
61 ## How the runtime merges 63 ## How the runtime merges
62 64
@@ -157,3 +159,4 @@ forms never use. @@ -157,3 +159,4 @@ forms never use.
157 itself? Likely the former, but worth confirming. 159 itself? Likely the former, but worth confirming.
158 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave` 160 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave`
159 rows in a populated deployment and use them as a worked example here. 161 rows in a populated deployment and use them as a worked example here.
  162 + *(Dev DB confirmed empty — needs a tenant deployment with overlays.)*
en/docs/slices/05-customer-sql-override.md
@@ -106,24 +106,42 @@ The right rule of thumb: prefer Slice-4 metadata customization. Reach @@ -106,24 +106,42 @@ The right rule of thumb: prefer Slice-4 metadata customization. Reach
106 for Slice-5 SQL overrides only when the metadata model genuinely cannot 106 for Slice-5 SQL overrides only when the metadata model genuinely cannot
107 express what the customer needs. 107 express what the customer needs.
108 108
109 -## Worked-example: what 重庆展印's `Sp_SalSalesCheck` does differently  
110 -  
111 -Skim of the file's top:  
112 -  
113 -- It reads a `'CbxSrcNoCheck'` row from `SysSystemSettings` to determine  
114 - which billing types feed the sales-check report. This is a customer-  
115 - specific switch the standard proc may not expose.  
116 -- It calls the global `Fun_GetLookCustomer(sLoginId, sBrId, sSuId)`  
117 - helper for permission scoping — same as the standard proc would.  
118 -- It accepts the same parameter list as the standard  
119 - (`sLoginId`/`sCustomerId`/`sBrId`/`sSuId`/`bFilter`/`pageNum`/`pageSize`/…)  
120 - so the framework's call-site is unchanged.  
121 -  
122 -A future revision of this slice can do a side-by-side diff with the  
123 -standard `Sp_SalSalesCheck` and explain *exactly* the business rule  
124 -that diverges. For now, the structural fact is what matters: the proc  
125 -shape and parameter list are identical to the standard; the body  
126 -diverges. 109 +## Worked-example: 重庆展印's `Sp_SalSalesCheck` vs the standard
  110 +
  111 +Quantified diff against the live dev DB:
  112 +
  113 +| Aspect | Standard `Sp_SalSalesCheck` (in DB) | 重庆展印 override (`script/客户/重庆展印/Sp_SalSalesCheck.sql`) |
  114 +|---|---|---|
  115 +| Body length | 1,714 lines | 723 lines (≈42 % the size) |
  116 +| Parameter signature | 14 params: `sLoginId, sCustomerId, sBrId, sSuId, bFilter, sUnTaskFormId, pageNum, pageSize, totalCount(OUT), countCloumn, countMapJson(OUT), sFilterOrderBy, sGroupby_select_sql, sGroupby_group_sql` | **Identical** — same 14 params in the same order |
  117 +| `SysSystemSettings.CbxSrcNoCheck` lookup | **Not used** | **Used** (drives "未对账印件清单来源" — which billing-type sources feed the report) |
  118 +| `Fun_GetLookCustomer(sLoginId, sBrId, sSuId)` permission scoping | Used | Used (same call) |
  119 +| Temp-table-based aggregation flow (`B1`, `B2` etc., several `DROP TEMPORARY TABLE` + `INSERT INTO` blocks) | Heavy (the bulk of the 1,714 lines) | Removed / simplified |
  120 +
  121 +So 重庆展印's override:
  122 +- Keeps the framework call-site unchanged (identical parameter
  123 + signature so the metadata-driven dispatcher
  124 + ([proc-dispatch.md](../reference/maintainer/proc-dispatch.md)) still
  125 + invokes it correctly).
  126 +- Adds a `CbxSrcNoCheck` system-setting branch that the standard
  127 + doesn't expose. Twelve other `Sp_*` procs in the schema also use
  128 + `CbxSrcNoCheck` (`Sp_Manufacture_MftWorkOrderAround`,
  129 + `Sp_OverdueNoCheck`, `Sp_Receivables_*` family, plus sibling
  130 + `Sp_SalSalesCheck1`/`_1227`/`_YanBao`/`_ded_copy1`); the override
  131 + brings that pattern into the customer's main proc.
  132 +- Strips the standard's heavy temp-table aggregation flow — a simpler
  133 + query path, not a more complex one. The customer's check semantics
  134 + evidently don't need the full standard aggregation.
  135 +
  136 +A maintainer wanting the *exact* business-rule difference should diff
  137 +the two file bodies directly:
  138 +
  139 +```bash
  140 +mysql --defaults-file=$HOME/.my.cnf xlyweberp_saas_ai \
  141 + -BNe "SELECT ROUTINE_DEFINITION FROM information_schema.routines \
  142 + WHERE ROUTINE_NAME='Sp_SalSalesCheck'" > /tmp/std.sql
  143 +diff /tmp/std.sql script/客户/重庆展印/Sp_SalSalesCheck.sql | head -200
  144 +```
127 145
128 The companion view `viw_salsaleschecking_pro.sql` exists for the same 146 The companion view `viw_salsaleschecking_pro.sql` exists for the same
129 reason — when the override needs a join shape the standard doesn't 147 reason — when the override needs a join shape the standard doesn't
en/mkdocs.yml
@@ -108,6 +108,7 @@ nav: @@ -108,6 +108,7 @@ nav:
108 - "Cache invalidation on metadata change": reference/maintainer/cache-invalidation.md 108 - "Cache invalidation on metadata change": reference/maintainer/cache-invalidation.md
109 - "SQL templates (xlyEntry/templesql/)": reference/maintainer/sql-templates.md 109 - "SQL templates (xlyEntry/templesql/)": reference/maintainer/sql-templates.md
110 - "Multi-service deployment": reference/maintainer/deployment.md 110 - "Multi-service deployment": reference/maintainer/deployment.md
  111 + - "Metadata-management services (xlyManage)": reference/maintainer/management-services.md
111 - "Activiti integration": reference/maintainer/activiti.md 112 - "Activiti integration": reference/maintainer/activiti.md
112 - 5. API Reference: 113 - 5. API Reference:
113 - api-reference/index.md 114 - api-reference/index.md