Commit bbcde3e6069b61413751a843e8b2616ed31bda07
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.
Showing
19 changed files
with
554 additions
and
231 deletions
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's fixed URL spec. | @@ -49,7 +57,7 @@ they have to match a partner'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't call `RequestAddParamUtil` is a | @@ -95,12 +120,21 @@ A new controller method that doesn'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.** |