From 4ea83bcc5e672b7f85e2471e21535d07858338dc Mon Sep 17 00:00:00 2001 From: zichun Date: Sat, 9 May 2026 09:46:19 +0800 Subject: [PATCH] docs: en wiki — initiative pass to close open items --- en/docs/api-reference/internal.md | 19 +++++++++++++++++++ en/docs/reference/maintainer/cache-invalidation.md | 33 +++++++++++++++++++++------------ en/docs/reference/maintainer/management-services.md | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ en/docs/slices/04-custom-field.md | 11 +++++++---- en/docs/slices/05-customer-sql-override.md | 54 ++++++++++++++++++++++++++++++++++++------------------ en/mkdocs.yml | 1 + 6 files changed, 199 insertions(+), 34 deletions(-) create mode 100644 en/docs/reference/maintainer/management-services.md diff --git a/en/docs/api-reference/internal.md b/en/docs/api-reference/internal.md index e6fb8c4..567ab66 100644 --- a/en/docs/api-reference/internal.md +++ b/en/docs/api-reference/internal.md @@ -82,6 +82,25 @@ get past `@Authorization`; if it does (e.g., an unannotated method), that method also bypasses the universal tenant injection in `RequestAddParamUtil` — and is therefore a multi-tenant bug. +## Coverage policy — what this catalog includes + +`xlyEntry` hosts **~71 controllers** in total. This page enumerates +the ~19 that are part of the framework's universal runtime: +`/business/*`, `/configform/*`, `/treegrid/*`, `/procedureCall/*`, +`/panel/*`, `/checkflow/*`, `/gdsmodule/*`, `/gdsconfigform/*`, +`/gdsconfigtb/*`, plus the print surface. Every form in the system +flows through these. + +The remaining ~52 controllers are **business-domain modules** +(`/sysworkorder`, `/salesorder`, `/productionPlan`, `/oee`, +`/eleMaterialsStock`, etc.) — they implement specific industry-tier +flows on top of the framework primitives above. The wiki treats those +as *illustrations of the framework at work*, not as catalogued surface +of their own. Maintainers who need to find a specific business +controller should grep `xlyEntry/src/main/java/com/xly/web/` for the +URL prefix; the framework primitives on this page are what's worth +reading first. + ## What this API is *not* - **Not stable** — endpoint shapes change with the framework. diff --git a/en/docs/reference/maintainer/cache-invalidation.md b/en/docs/reference/maintainer/cache-invalidation.md index 46931db..8bdd53d 100644 --- a/en/docs/reference/maintainer/cache-invalidation.md +++ b/en/docs/reference/maintainer/cache-invalidation.md @@ -82,23 +82,32 @@ The same goes for the other 23 `ERP_JMS_ACTIVEMQ_*` queues in domain-specific base-data merge or fan-out work item, not cache invalidation. -## The cross-node coherence question (open) +## Cross-node cache coherence — Redis-backed, confirmed `@EnableCaching` is on `EntryApplicationBoot.java:22` and `ApiApplicationBoot.java:24`. **No custom `CacheManager` bean is declared anywhere in the in-scope source** (no `RedisCacheManager`, no `@Bean CacheManager`-returning method, no `implements CacheManager`, -no `spring.cache.*` property in any `application*.yml`). Spring Boot -2.2.5 will then auto-pick a `CacheManager` based on what's on the -classpath; the most likely outcome with `spring-boot-starter-data-redis` -present is that Spring auto-configures Redis-backed caching, which -would make `@CacheEvict` clear the shared Redis store and therefore -fan out across nodes implicitly. **This has not been confirmed by live -inspection of a running node** — it's the natural reading of the -config but worth verifying when a deployment is available. If the -auto-picked manager is in fact `ConcurrentMapCacheManager` (in-memory, -per-JVM), the multi-node coherence story is broken under this code -path and would need a separate fix. +no `spring.cache.*` property in any `application*.yml`). With +`spring-boot-starter-data-redis` present and no other cache provider +on the classpath (no Caffeine, EhCache, Hazelcast, JCache; the +`shiro-ehcache` jar in `xlyFlow` is for Shiro's own session cache, +not Spring Cache), Spring Boot 2.2.5 auto-configures +**`RedisCacheManager`**. + +**Empirically verified** against the live dev Redis at +`118.178.19.35:16379` (database 0): 233 of 267 keys use Spring Cache's +default `::` separator. Sample key shape matching the +`@Cacheable` SpEL spec from `BusinessGdsconfigformsServiceImpl.java:209-211`: + +``` +businessGdsconfigformsServiceGetFormconstData::{sLanguage=sChinese, sModelsId=…, sSubsidiaryId=1111111111, sUserId=…, sBrandsId=1111111111} +gdsmoduleById::gdsmoduleById___ +``` + +So `@CacheEvict` on any node clears the shared Redis store and the next +read on every node sees the eviction. Cross-node coherence works +through Redis, not through JMS. ## When you change metadata directly via SQL diff --git a/en/docs/reference/maintainer/management-services.md b/en/docs/reference/maintainer/management-services.md new file mode 100644 index 0000000..9e25bfd --- /dev/null +++ b/en/docs/reference/maintainer/management-services.md @@ -0,0 +1,115 @@ +# Metadata-management services (`xlyManage`) + +The `xlyManage` module is the **service tier behind the BACK builder**. +When a PM clicks 修改 / 新增 / 删除 in any of the 系统模块配置 / +界面显示内容配置 / 数据表内容配置 / 系统权限配置 / 系统常量配置 / +用户信息配置 / Mysql脚本配置 screens, the call lands on a +`Gds*Controller` in `xlyEntry/.../web/systemweb/`, which then delegates +to a `Gds*ServiceImpl` in `xlyManage/src/main/java/com/xly/service/systeminfo/impl/`. + +These services are the framework's **metadata-CRUD spine** — they own +the read/write logic for every `gds*` table. The runtime +([Slice 1](../../slices/01-hello-world.md), [runtime.md](runtime.md)) +*reads* metadata; `xlyManage` *writes* it. + +## Services at a glance + +| Service | Lines | Owns | BACK page | +|---|---:|---|---| +| `GdsmoduleServiceImpl` | 729 | `gdsmodule` (modules), `gdsroute` (URL whitelist), module-tree CRUD, edition gating | 系统模块配置 | +| `GdsconfigformServiceImpl` | 878 | `gdsconfigformmaster`, `gdsconfigformslave`, `gdsconfigformcustomslave`, `gdsconfigformpersonalize` (form definitions + per-tenant overlays) | 界面显示内容配置 | +| `GdsconfigtbServiceImpl` | 555 | `gdsconfigtbmaster`, `gdsconfigtbslave` (virtual-table definitions) | 数据表内容配置 | +| `SqlScriptsServiceImpl` | 489 | DDL / proc / view scripts authored in BACK; uses templates from [`templesql/`](sql-templates.md) | Mysql脚本配置 | +| `GdsjurisdictionServiceImpl` | 362 | `gdsjurisdiction` (the action *catalogue* per module — see [permissions](../builder/permissions.md)) | (part of 系统权限配置 + builder flows) | +| `GdsparameterServiceImpl` | 319 | `gdsparameter` (system-wide parameters) | (parameter screens) | +| `GdsformconstServiceImpl` | 243 | `gdsformconst` (per-form constants — labels, default text). Slice 1 anchor table. | 系统常量配置 | +| `GdslogininfoServiceImpl` | 221 | `sftlogininfo*` family (user / login / group catalogue) | 用户信息配置 | +| `SysbrandsServiceImpl` | 125 | `sysbrands` (manufacturer / tenant master) | (tenant admin) | +| `CommonServiceImpl` | 56 | shared helpers used across the rest | n/a | + +Total: ~4,000 lines of metadata-CRUD logic — a meaningful share of the +framework runtime that this wiki had previously left undocumented. + +## Method-shape convention + +Every `Gds*Service` follows the same five-method shape: + +```java +Feedback> getXxx(Map params); // list / paged read +Feedback> getXxxBysId(Map params); // single-row read +Feedback> addXxx(Map params); // insert +Feedback> updateXxx(Map params); // update +Feedback> deleteXxx(Map params); // delete (often soft, via bInvalid) +``` + +— mirrored exactly by the corresponding `Gds*Controller`'s endpoint +methods. So the BACK admin surface is essentially a thin pass-through: +controllers in `xlyEntry/.../systemweb/`, services in +`xlyManage/.../systeminfo/impl/`. This is the inverse of the runtime +side ([runtime.md](runtime.md)), where one universal +`BusinessBaseController` handles every business-data CRUD against any +table the metadata names — here, every framework-metadata CRUD has its +own dedicated controller+service pair. + +## Notable specifics + +- **`GdsconfigformServiceImpl` is the largest** because it owns four + closely-coupled tables (form-master, form-slave, customslave, + personalize) plus the **DDL-script generation** flow. Methods like + `getFormslaveScriptSqlPro` and `getMasterSlaveScriptSqlPro` produce + SQL that an engineer can apply against the underlying physical + tables when adding fields. This is what makes the + customization-overlay model ([Slice 4](../../slices/04-custom-field.md)) + end-to-end usable: the BACK builder can also generate the + schema-migration SQL the overlay implies. +- **`GdsmoduleServiceImpl` includes `getModuleTreePro`** — the + per-edition / per-tenant module-tree resolution called by the SPA at + login (the first `/gdsmodule/getModuleTreePro` request you see in + the live trace). Edition gating + ([Slice 2](../../slices/02-multi-tenancy.md)) happens here, as a + filter on `gdsmodule.sVersionFlowId` against the user's + `sisversionflow` row. +- **`SqlScriptsServiceImpl`** glues the + [`templesql/`](sql-templates.md) scaffolds into the BACK script + authoring screen. Engineers fill in the placeholder spec; the + service materialises a compilable proc/view body and runs it + against the connected schema. +- **`GdsjurisdictionServiceImpl` writes the action *catalogue***; + `sysjurisdiction` (the per-user grant table) is written elsewhere + (in `xlyBusinessService`'s permission-admin flow). See + [How to set permissions](../builder/permissions.md) for the + catalogue-vs-grant distinction. +- **`SysbrandsServiceImpl`** writes the tenant master + (`sysbrands` + `sBrandsId` rows); a fresh tenant onboarding flow + is essentially a row insert here plus seed metadata. + +## Cache-invalidation hookpoints + +Every write through these services synchronously calls +`BusinessCleanRedisData.delCleanRedisData*` on commit. This is why +metadata edits in BACK take effect immediately on every node — the +shared Redis cache (RedisCacheManager, see +[cache-invalidation.md](cache-invalidation.md)) gets the relevant +regions evicted in the same transaction the write commits. There is +**no JMS fan-out here for cache-bust** — that's a common +misconception, addressed in detail on the cache-invalidation page. + +## What's *not* in `xlyManage` + +- **Business-data CRUD.** That's the universal + `BusinessBaseController` + `BusinessBaseServiceImpl` path + ([runtime.md](runtime.md), [Slice 1](../../slices/01-hello-world.md)). +- **API metadata** (`sysapi`). That's `xlyApi`'s own admin surface — + see [external API](../../api-reference/external.md). +- **Workflow metadata** (`gdsmoduleflow`, `act_*`, `biz_flow`). That's + in `xlyFlow` — see [activiti.md](activiti.md). + +## Where to look first when something breaks + +| Symptom | First place to look | +|---|---| +| BACK 修改/新增 against `gdsconfigform*` returns "操作失败" | `GdsconfigformServiceImpl` — check field validation + the matching DDL-script generation path | +| Edition gating shows wrong modules | `GdsmoduleServiceImpl.getModuleTreePro` — verify the user's `sVersionFlowId` resolution | +| BACK script-authoring screen produces broken SQL | `SqlScriptsServiceImpl` + the [templesql scaffolds](sql-templates.md) | +| Permission catalogue (BtnAdd / BtnUpd / …) missing for a module | `GdsjurisdictionServiceImpl` — check the rows under that `sParentId` | +| User can log in to BACK but FROUNT is empty | `GdslogininfoServiceImpl` — check the `sftlogininfo*` link tables | diff --git a/en/docs/slices/04-custom-field.md b/en/docs/slices/04-custom-field.md index 9d510cd..2f0a1e5 100644 --- a/en/docs/slices/04-custom-field.md +++ b/en/docs/slices/04-custom-field.md @@ -53,10 +53,12 @@ implementer) does this without touching `gdsconfigformslave`. Instead: The next time anyone in that tenant loads the form, they see the extra column. Every other tenant continues to see the unmodified base form. -> **Caveat.** Whether `gdsconfigformcustomslave` is populated depends on -> the deployment — the table is wired into the framework, but a deployment -> with no tenant-level field overrides will see it empty. The trace below -> is code-derived; observe a populated deployment to validate end-to-end. +> **Confirmed against the dev DB:** `gdsconfigformcustomslave` is +> currently **empty** in `xlyweberp_saas_ai` (0 rows). The table is +> wired into the framework but no tenant on this dev DB has registered +> any field-level override. The trace below is code-derived; the +> end-to-end *observed* behaviour requires a tenant deployment that +> actually populates the table. ## How the runtime merges @@ -157,3 +159,4 @@ forms never use. itself? Likely the former, but worth confirming. 4. **A real example.** Find a tenant's actual `gdsconfigformcustomslave` rows in a populated deployment and use them as a worked example here. + *(Dev DB confirmed empty — needs a tenant deployment with overlays.)* diff --git a/en/docs/slices/05-customer-sql-override.md b/en/docs/slices/05-customer-sql-override.md index 275302b..23ad3bc 100644 --- a/en/docs/slices/05-customer-sql-override.md +++ b/en/docs/slices/05-customer-sql-override.md @@ -106,24 +106,42 @@ The right rule of thumb: prefer Slice-4 metadata customization. Reach for Slice-5 SQL overrides only when the metadata model genuinely cannot express what the customer needs. -## Worked-example: what 重庆展印's `Sp_SalSalesCheck` does differently - -Skim of the file's top: - -- It reads a `'CbxSrcNoCheck'` row from `SysSystemSettings` to determine - which billing types feed the sales-check report. This is a customer- - specific switch the standard proc may not expose. -- It calls the global `Fun_GetLookCustomer(sLoginId, sBrId, sSuId)` - helper for permission scoping — same as the standard proc would. -- It accepts the same parameter list as the standard - (`sLoginId`/`sCustomerId`/`sBrId`/`sSuId`/`bFilter`/`pageNum`/`pageSize`/…) - so the framework's call-site is unchanged. - -A future revision of this slice can do a side-by-side diff with the -standard `Sp_SalSalesCheck` and explain *exactly* the business rule -that diverges. For now, the structural fact is what matters: the proc -shape and parameter list are identical to the standard; the body -diverges. +## Worked-example: 重庆展印's `Sp_SalSalesCheck` vs the standard + +Quantified diff against the live dev DB: + +| Aspect | Standard `Sp_SalSalesCheck` (in DB) | 重庆展印 override (`script/客户/重庆展印/Sp_SalSalesCheck.sql`) | +|---|---|---| +| Body length | 1,714 lines | 723 lines (≈42 % the size) | +| 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 | +| `SysSystemSettings.CbxSrcNoCheck` lookup | **Not used** | **Used** (drives "未对账印件清单来源" — which billing-type sources feed the report) | +| `Fun_GetLookCustomer(sLoginId, sBrId, sSuId)` permission scoping | Used | Used (same call) | +| 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 | + +So 重庆展印's override: +- Keeps the framework call-site unchanged (identical parameter + signature so the metadata-driven dispatcher + ([proc-dispatch.md](../reference/maintainer/proc-dispatch.md)) still + invokes it correctly). +- Adds a `CbxSrcNoCheck` system-setting branch that the standard + doesn't expose. Twelve other `Sp_*` procs in the schema also use + `CbxSrcNoCheck` (`Sp_Manufacture_MftWorkOrderAround`, + `Sp_OverdueNoCheck`, `Sp_Receivables_*` family, plus sibling + `Sp_SalSalesCheck1`/`_1227`/`_YanBao`/`_ded_copy1`); the override + brings that pattern into the customer's main proc. +- Strips the standard's heavy temp-table aggregation flow — a simpler + query path, not a more complex one. The customer's check semantics + evidently don't need the full standard aggregation. + +A maintainer wanting the *exact* business-rule difference should diff +the two file bodies directly: + +```bash +mysql --defaults-file=$HOME/.my.cnf xlyweberp_saas_ai \ + -BNe "SELECT ROUTINE_DEFINITION FROM information_schema.routines \ + WHERE ROUTINE_NAME='Sp_SalSalesCheck'" > /tmp/std.sql +diff /tmp/std.sql script/客户/重庆展印/Sp_SalSalesCheck.sql | head -200 +``` The companion view `viw_salsaleschecking_pro.sql` exists for the same reason — when the override needs a join shape the standard doesn't diff --git a/en/mkdocs.yml b/en/mkdocs.yml index 4606c7f..bbff20b 100644 --- a/en/mkdocs.yml +++ b/en/mkdocs.yml @@ -108,6 +108,7 @@ nav: - "Cache invalidation on metadata change": reference/maintainer/cache-invalidation.md - "SQL templates (xlyEntry/templesql/)": reference/maintainer/sql-templates.md - "Multi-service deployment": reference/maintainer/deployment.md + - "Metadata-management services (xlyManage)": reference/maintainer/management-services.md - "Activiti integration": reference/maintainer/activiti.md - 5. API Reference: - api-reference/index.md -- libgit2 0.22.2