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 82 that method also bypasses the universal tenant injection in
83 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 104 ## What this API is *not*
86 105  
87 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 82 domain-specific base-data merge or fan-out work item, not cache
83 83 invalidation.
84 84  
85   -## The cross-node coherence question (open)
  85 +## Cross-node cache coherence — Redis-backed, confirmed
86 86  
87 87 `@EnableCaching` is on `EntryApplicationBoot.java:22` and
88 88 `ApiApplicationBoot.java:24`. **No custom `CacheManager` bean is
89 89 declared anywhere in the in-scope source** (no `RedisCacheManager`,
90 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 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 53 The next time anyone in that tenant loads the form, they see the extra
54 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 63 ## How the runtime merges
62 64  
... ... @@ -157,3 +159,4 @@ forms never use.
157 159 itself? Likely the former, but worth confirming.
158 160 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave`
159 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 106 for Slice-5 SQL overrides only when the metadata model genuinely cannot
107 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 146 The companion view `viw_salsaleschecking_pro.sql` exists for the same
129 147 reason — when the override needs a join shape the standard doesn't
... ...
en/mkdocs.yml
... ... @@ -108,6 +108,7 @@ nav:
108 108 - "Cache invalidation on metadata change": reference/maintainer/cache-invalidation.md
109 109 - "SQL templates (xlyEntry/templesql/)": reference/maintainer/sql-templates.md
110 110 - "Multi-service deployment": reference/maintainer/deployment.md
  111 + - "Metadata-management services (xlyManage)": reference/maintainer/management-services.md
111 112 - "Activiti integration": reference/maintainer/activiti.md
112 113 - 5. API Reference:
113 114 - api-reference/index.md
... ...