kcode-pi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -0
- package/dist/cli/kcode.d.ts +15 -0
- package/dist/cli/kcode.js +153 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +7 -0
- package/docs/KCODE_DISTRIBUTION.md +91 -0
- package/extensions/kingdee-harness.ts +180 -0
- package/extensions/kingdee-header.ts +122 -0
- package/extensions/kingdee-tools.ts +379 -0
- package/knowledge/.backup/v1.0.0/version.json +10 -0
- package/knowledge/cangqiong/product-notes.md +15 -0
- package/knowledge/common/business-flows.md +115 -0
- package/knowledge/common/config-guides.md +110 -0
- package/knowledge/common/error-patterns.md +170 -0
- package/knowledge/common/implementation.md +144 -0
- package/knowledge/cosmic/hard-constraints.md +38 -0
- package/knowledge/cosmic/ksql-datafix.md +34 -0
- package/knowledge/cosmic/platform-baseline.md +32 -0
- package/knowledge/cosmic/plugin-decision-matrix.md +40 -0
- package/knowledge/cosmic/review-checklist.md +40 -0
- package/knowledge/cosmic/unittest.md +35 -0
- package/knowledge/enterprise/api-reference.md +186 -0
- package/knowledge/enterprise/code-patterns.md +217 -0
- package/knowledge/enterprise/plugin-lifecycle.md +188 -0
- package/knowledge/enterprise/tables.json +159 -0
- package/knowledge/flagship/api-reference.md +237 -0
- package/knowledge/flagship/code-patterns.md +246 -0
- package/knowledge/flagship/cosmic-platform-note.md +15 -0
- package/knowledge/flagship/plugin-lifecycle.md +248 -0
- package/knowledge/flagship/tables.json +159 -0
- package/knowledge/version.json +10 -0
- package/knowledge/xinghan/product-notes.md +15 -0
- package/package.json +71 -0
- package/prompts/kd-discuss.md +11 -0
- package/prompts/kd-execute.md +12 -0
- package/prompts/kd-plan.md +12 -0
- package/prompts/kd-ship.md +12 -0
- package/prompts/kd-spec.md +12 -0
- package/prompts/kd-verify.md +12 -0
- package/skills/kd-check/SKILL.md +26 -0
- package/skills/kd-cosmic-dev/SKILL.md +82 -0
- package/skills/kd-cosmic-review/SKILL.md +90 -0
- package/skills/kd-cosmic-unittest/SKILL.md +92 -0
- package/skills/kd-debug/SKILL.md +30 -0
- package/skills/kd-discuss/SKILL.md +24 -0
- package/skills/kd-execute/SKILL.md +22 -0
- package/skills/kd-gen/SKILL.md +34 -0
- package/skills/kd-ksql/SKILL.md +86 -0
- package/skills/kd-plan/SKILL.md +24 -0
- package/skills/kd-ship/SKILL.md +22 -0
- package/skills/kd-spec/SKILL.md +24 -0
- package/skills/kd-verify/SKILL.md +22 -0
- package/themes/kcode-dark.json +81 -0
- package/vendor/kingdee-skills/cosmic-unittest/SKILL.md +788 -0
- package/vendor/kingdee-skills/cosmic-unittest/author-cache.json +5 -0
- package/vendor/kingdee-skills/cosmic-unittest/cosmic-unittest-skill-overview.html +746 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/business-test.md +205 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/common-test.md +257 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/formplugin-test.md +560 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/op-plugin-test.md +231 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/validator-test.md +232 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/business-helper.md +184 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/common-module.md +355 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/convert-plugin.md +130 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/formplugin.md +235 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/op-plugin.md +226 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/validator.md +206 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +674 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/advanced-scenario-checklist.md +307 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/algox-performance-checklist.md +129 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/coding-standard-checklist.md +491 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/cosmic-api-checklist.md +285 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-access-checklist.md +261 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-transaction-checklist.md +390 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/domain-logic-checklist.md +295 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/form-plugin-checklist.md +508 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/infra-checklist.md +254 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ksql-checklist.md +305 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/lifecycle-checklist.md +298 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/operation-plugin-checklist.md +442 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/test-mock-checklist.md +120 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ui-performance-checklist.md +320 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +336 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +121 -0
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +295 -0
- package/vendor/kingdee-skills/ok-cosmic/README.md +460 -0
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +287 -0
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +17 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BatchImportPluginTemplate.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BillPlugInTemplate.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ConvertPlugInTemplate.java +255 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/FormPluginTemplate.java +597 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/IWorkflowPluginTemplate.java +91 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ListPluginTemplate.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpPluginTemplate.java +201 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpenApiControllerTemplate.java +103 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/PrintPluginTemplate.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportFormPluginTemplate.java +257 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportListDataPluginTemplate.java +70 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/StandardTreeListPluginTemplate.java +130 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TaskTemplate.java +80 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TreeListPluginTemplate.java +152 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/WriteBackPlugInTemplate.java +286 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/attachment/AttachmentUploadBindSample.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/BotpTracePushSample.java +168 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/SampleConvertPlugin.java +223 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/cache/SampleCacheUsage.java +218 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/concurrent/SampleThreadPoolBatch.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectCrudSample.java +205 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectOpsSample.java +100 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/BeforeOperationConfirmSample.java +217 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ConfirmDialogSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/EntryRowCalculateSample.java +116 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/F7FilterSample.java +134 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/GetAndSetValueSample.java +176 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/HyperlinkJumpSample.java +124 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/OpenBillModalSample.java +253 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ReturnParentDataSample.java +295 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/TreeControlSample.java +140 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ViewControlOpsSample.java +132 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPluginBasicSample.java +170 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPreOpenFilterSample.java +68 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/message/MessageNotifySample.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/SampleMQConsumer.java +198 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/sample_mq.xml +15 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OpAddValidatorsSample.java +137 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OperationOptionBridgeSample.java +228 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/package-info.java +19 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BaseDataQuerySample.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java +368 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/DataSetQueryStatSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportFormPlugin.java +179 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportListDataPlugin.java +616 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/snippets-guide.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/task/ScheduleTaskSample.java +160 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/workflow/SampleWorkflowPlugin.java +302 -0
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +78 -0
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +903 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/attachment-api.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/botp-convert.md +98 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/dynamic-object.md +113 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/entity-metadata.md +123 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/event-lifecycle.md +184 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/flex-prop.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/form-utils.md +133 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/operate-chain.md +159 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/plugin-base.md +218 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/query-dataset.md +149 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/request-context.md +88 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/view-handler.md +157 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-bill.md +76 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-botp.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-form.md +165 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-import.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-list.md +227 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-openapi.md +112 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-operation.md +135 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-print.md +65 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-data.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-form.md +90 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-task.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-tree-list.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-workflow.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-writeback.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-algo.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-cache.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-model-svc.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-object.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-entity-model.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-exception.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-file.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-id.md +47 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-lock.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-log.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-network-control.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-orm-access.md +78 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-request-context.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-threadpool.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-tx.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-utils.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +2 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +24 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +48 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/cheat-sheet.md +256 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +140 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +222 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +94 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/platform-baseline.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +109 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +204 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +910 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +359 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +181 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +389 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +856 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +262 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +293 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +2 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +393 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +176 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +375 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +434 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +36 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +186 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +40 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +142 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic-docs.db +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic.json +13 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +18 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +53 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +81 -0
- package/vendor/kingdee-skills/ok-ksql/agents/openai.yaml +7 -0
- package/vendor/kingdee-skills/ok-ksql/manifest.json +14 -0
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +452 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +363 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
package kd.cd.common.snippets;
|
|
2
|
+
|
|
3
|
+
import kd.bos.dataentity.entity.DynamicObject;
|
|
4
|
+
import kd.bos.dataentity.metadata.dynamicobject.DynamicProperty;
|
|
5
|
+
import kd.bos.entity.ExtendedDataEntity;
|
|
6
|
+
import kd.bos.entity.botp.plugin.AbstractConvertPlugIn;
|
|
7
|
+
import kd.bos.entity.botp.plugin.args.AfterConvertEventArgs;
|
|
8
|
+
import kd.bos.entity.botp.plugin.args.AfterCreateLinkEventArgs;
|
|
9
|
+
import kd.bos.entity.botp.plugin.args.AfterCreateTargetEventArgs;
|
|
10
|
+
import kd.bos.entity.botp.plugin.args.AfterFieldMappingEventArgs;
|
|
11
|
+
import kd.bos.entity.botp.plugin.args.AfterGetSourceDataEventArgs;
|
|
12
|
+
import kd.bos.entity.botp.runtime.ConvertConst;
|
|
13
|
+
import kd.bos.logging.Log;
|
|
14
|
+
import kd.bos.logging.LogFactory;
|
|
15
|
+
import kd.cd.common.util.DynamicObjectUtils;
|
|
16
|
+
import kd.cd.core.util.CollectionUtils;
|
|
17
|
+
import kd.cd.core.util.ArrayUtils;
|
|
18
|
+
|
|
19
|
+
import java.util.List;
|
|
20
|
+
import java.util.Map;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 转换插件示例 —— AbstractConvertPlugIn 内部生命周期全场景。
|
|
24
|
+
* <p>
|
|
25
|
+
* 适用插件:转换插件(下推/选单场景)
|
|
26
|
+
* 原生兜底:AbstractConvertPlugIn、ExtendedDataEntity、ConvertConst
|
|
27
|
+
* 相关 lint 规则:STYLE-003、STYLE-004、STYLE-015
|
|
28
|
+
* <p>
|
|
29
|
+
* 生命周期方法执行顺序:
|
|
30
|
+
* 1. afterGetSourceData —— 转换执行前触发,拿到扁平化源数据行,适合源数据预校验/拦截;
|
|
31
|
+
* 2. afterCreateTarget —— 目标单创建完成后触发(字段映射前),适合自动填充分录行;
|
|
32
|
+
* 3. afterFieldMapping —— 每对"源→目标"映射完成后逐一触发,适合逐单补值/动态新增分录行;
|
|
33
|
+
* 4. afterCreateLink —— 建立单据关联后触发,适合防止重复下推/关联数据清理;
|
|
34
|
+
* 5. afterConvert —— 整批转换完成后触发,可拿到全部目标单 + 源单行,适合跨单汇总/补字段/校验。
|
|
35
|
+
* <p>
|
|
36
|
+
* <b>注意:本文件覆盖转换插件"内部"生命周期;外部编程式下推/链路追踪请看 BotpTracePushSample。</b>
|
|
37
|
+
*/
|
|
38
|
+
public class SampleConvertPlugin extends AbstractConvertPlugIn {
|
|
39
|
+
private static final Log log = LogFactory.getLog(SampleConvertPlugin.class);
|
|
40
|
+
|
|
41
|
+
// ===================================================================
|
|
42
|
+
// 一、afterGetSourceData —— 转换执行前触发,源数据预校验
|
|
43
|
+
// ===================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 特点:在字段映射和目标单创建之前触发,只有「扁平化源数据行」可用。
|
|
47
|
+
* <p>
|
|
48
|
+
* 可用数据:
|
|
49
|
+
* - e.getSourceRows() → 扁平化后的源数据行(分录字段以 entry.xxx 形式平铺)
|
|
50
|
+
* - e.getFldProperties() → 字段属性 Map,用于从扁平行安全取值
|
|
51
|
+
* <p>
|
|
52
|
+
* 适合操作:源数据预校验/拦截(throw KDBizException 可直接阻断转换流程)
|
|
53
|
+
* 不适合操作:修改目标单(此时目标单尚未创建)
|
|
54
|
+
* <p>
|
|
55
|
+
* 实战场景:支付安全校验、金额合规检查、源单状态前置校验
|
|
56
|
+
*/
|
|
57
|
+
@Override
|
|
58
|
+
public void afterGetSourceData(AfterGetSourceDataEventArgs e) {
|
|
59
|
+
super.afterGetSourceData(e);
|
|
60
|
+
|
|
61
|
+
// ── 扁平化源数据行(不是完整 DynamicObject,需要完整数据要用 id 重新 load)
|
|
62
|
+
List<DynamicObject> sourceRows = e.getSourceRows();
|
|
63
|
+
Map<String, DynamicProperty> fldProperties = e.getFldProperties();
|
|
64
|
+
|
|
65
|
+
// ── 通过 fldProperties 从扁平行安全取值
|
|
66
|
+
for (DynamicObject row : sourceRows) {
|
|
67
|
+
Long srcId = (Long) fldProperties.get("id").getValue(row);
|
|
68
|
+
// TODO 校验源单数据,不满足条件 throw new KDBizException("...") 拦截下推
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ===================================================================
|
|
73
|
+
// 二、afterCreateTarget —— 目标单创建后、字段映射前触发
|
|
74
|
+
// ===================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 特点:目标单 DynamicObject 已创建但字段映射尚未执行,映射字段此时为空。
|
|
78
|
+
* <p>
|
|
79
|
+
* 可用数据:
|
|
80
|
+
* - e.getTargetExtDataEntitySet().getExtDataEntityMap().get(entityName) → 目标单列表
|
|
81
|
+
* - ExtendedDataEntity.getDataEntity() → 目标单(字段值大多为空/默认值)
|
|
82
|
+
* <p>
|
|
83
|
+
* 适合操作:向目标单预填充分录行(查基础资料 → addNew)、设置系统字段(创建人等)
|
|
84
|
+
* 不适合操作:读取映射字段值(此时尚未映射)
|
|
85
|
+
* <p>
|
|
86
|
+
* 实战场景:自动填充扣款项/费用项分录、预设创建人/修改人
|
|
87
|
+
*/
|
|
88
|
+
@Override
|
|
89
|
+
public void afterCreateTarget(AfterCreateTargetEventArgs e) {
|
|
90
|
+
super.afterCreateTarget(e);
|
|
91
|
+
|
|
92
|
+
String tgtEntityName = this.getTgtMainType().getName();
|
|
93
|
+
List<ExtendedDataEntity> targetBills = e.getTargetExtDataEntitySet()
|
|
94
|
+
.getExtDataEntityMap().get(tgtEntityName);
|
|
95
|
+
if (CollectionUtils.isEmpty(targetBills)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (ExtendedDataEntity targetExt : targetBills) {
|
|
100
|
+
DynamicObject targetBill = targetExt.getDataEntity();
|
|
101
|
+
// TODO 查询基础资料 → targetBill.getDynamicObjectCollection("entryKey").addNew()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ===================================================================
|
|
106
|
+
// 三、afterFieldMapping —— 每对映射逐一触发,映射后补值
|
|
107
|
+
// ===================================================================
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 特点:每对「源→目标」映射完成后逐一触发,目标单已有映射值,适合逐单补值。
|
|
111
|
+
* <p>
|
|
112
|
+
* 可用数据:
|
|
113
|
+
* - e.getTargetExtDataEntitySet().FindByEntityKey(entityName) → 目标单数组
|
|
114
|
+
* - e.getFldProperties() → 源单字段属性 Map
|
|
115
|
+
* - 目标单字段已完成映射赋值
|
|
116
|
+
* <p>
|
|
117
|
+
* 与 afterConvert 的区别:
|
|
118
|
+
* - afterFieldMapping 逐对映射触发 → 逐单补值/动态新增分录行
|
|
119
|
+
* - afterConvert 整批触发 → 跨单汇总/拆分/合并
|
|
120
|
+
* <p>
|
|
121
|
+
* 适合操作:简单字段补值、清空并重建分录行、源单行程展开为目标多行
|
|
122
|
+
* <p>
|
|
123
|
+
* 实战场景:字段复制补值、委托报销自动带出收款人、出差申请行程展开为多行
|
|
124
|
+
*/
|
|
125
|
+
@Override
|
|
126
|
+
public void afterFieldMapping(AfterFieldMappingEventArgs e) {
|
|
127
|
+
super.afterFieldMapping(e);
|
|
128
|
+
String tgtEntityName = this.getTgtMainType().getName();
|
|
129
|
+
ExtendedDataEntity[] targetBills = e.getTargetExtDataEntitySet().FindByEntityKey(tgtEntityName);
|
|
130
|
+
|
|
131
|
+
for (ExtendedDataEntity targetExt : targetBills) {
|
|
132
|
+
DynamicObject targetBill = targetExt.getDataEntity();
|
|
133
|
+
|
|
134
|
+
// TODO 补值:targetBill.set("fieldA", targetBill.get("fieldB"));
|
|
135
|
+
// TODO 动态新增分录:targetBill.getDynamicObjectCollection("entry").addNew().set(...);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ===================================================================
|
|
140
|
+
// 四、afterCreateLink —— 建立单据关联后触发
|
|
141
|
+
// ===================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 特点:源单→目标单关联关系(lk 表)已写入,可据此做去重/清理。
|
|
145
|
+
* <p>
|
|
146
|
+
* 可用数据:
|
|
147
|
+
* - e.getTargetExtDataEntitySet().getExtDataEntityMap().get(entityName) → 目标单列表
|
|
148
|
+
* - 关联关系已建立,可查询 lk 表判断是否重复下推
|
|
149
|
+
* <p>
|
|
150
|
+
* 适合操作:防止重复下推(查已下推记录 → removeIf 移除重复行)
|
|
151
|
+
* <p>
|
|
152
|
+
* 实战场景:暂估应付单多次下推时扣款项只需首次携带
|
|
153
|
+
*/
|
|
154
|
+
@Override
|
|
155
|
+
public void afterCreateLink(AfterCreateLinkEventArgs e) {
|
|
156
|
+
super.afterCreateLink(e);
|
|
157
|
+
|
|
158
|
+
String tgtEntityName = this.getTgtMainType().getName();
|
|
159
|
+
List<ExtendedDataEntity> targetBills = e.getTargetExtDataEntitySet()
|
|
160
|
+
.getExtDataEntityMap().get(tgtEntityName);
|
|
161
|
+
if (CollectionUtils.isEmpty(targetBills)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (ExtendedDataEntity targetExt : targetBills) {
|
|
166
|
+
DynamicObject targetBill = targetExt.getDataEntity();
|
|
167
|
+
// TODO 查询已下推记录 → entries.removeIf(...) 移除重复行
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ===================================================================
|
|
172
|
+
// 五、afterConvert —— 整批转换完成后统一触发(最常用)
|
|
173
|
+
// ===================================================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 特点:整批转换全部完成后触发,可同时拿到全部目标单 + 对应源单行,功能最强。
|
|
177
|
+
* <p>
|
|
178
|
+
* 可用数据:
|
|
179
|
+
* - e.getTargetExtDataEntitySet().FindByEntityKey(entityName) → 目标单数组
|
|
180
|
+
* - ExtendedDataEntity.getDataEntity() → 目标单 DynamicObject
|
|
181
|
+
* - ExtendedDataEntity.getValue(ConvertConst.ConvExtDataKey_SourceRows) → 对应源单行列表
|
|
182
|
+
* - e.getFldProperties() → 源单字段属性 Map
|
|
183
|
+
* - this.getSrcMainType().getName() / this.getTgtMainType().getName() → 源/目标单标识
|
|
184
|
+
* <p>
|
|
185
|
+
* 适合操作:
|
|
186
|
+
* - 基础补值:从源单行取值补充目标单字段
|
|
187
|
+
* - 金额校验:校验后 throw KDBizException 拦截
|
|
188
|
+
* - 分录拆分:按维度将一行拆为多行(clone → clear → addAll)
|
|
189
|
+
* - 分录合并:按 groupKey 分组 + 金额汇总
|
|
190
|
+
* - 外部查询填值:查映射配置/基础资料 → 批量 set
|
|
191
|
+
* <p>
|
|
192
|
+
* 实战场景:付款金额校验、差旅明细按人拆分、账单分录合并汇总、映射资金用途
|
|
193
|
+
*/
|
|
194
|
+
@Override
|
|
195
|
+
public void afterConvert(AfterConvertEventArgs e) {
|
|
196
|
+
super.afterConvert(e);
|
|
197
|
+
|
|
198
|
+
String tgtEntityName = this.getTgtMainType().getName();
|
|
199
|
+
ExtendedDataEntity[] targetBills = e.getTargetExtDataEntitySet().FindByEntityKey(tgtEntityName);
|
|
200
|
+
if (ArrayUtils.isEmpty(targetBills)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (ExtendedDataEntity targetExt : targetBills) {
|
|
205
|
+
DynamicObject targetBill = targetExt.getDataEntity();
|
|
206
|
+
|
|
207
|
+
// ── 获取当前目标单对应的源单行
|
|
208
|
+
@SuppressWarnings("unchecked")
|
|
209
|
+
List<DynamicObject> sourceRows = (List<DynamicObject>) targetExt.getValue(
|
|
210
|
+
ConvertConst.ConvExtDataKey_SourceRows);
|
|
211
|
+
if (CollectionUtils.isEmpty(sourceRows)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 从源单行安全取值
|
|
216
|
+
Long srcBillId = DynamicObjectUtils.nullSafeGet(sourceRows.get(0), "id");
|
|
217
|
+
|
|
218
|
+
// TODO 补值/校验/拆分/合并/映射填值
|
|
219
|
+
targetBill.set("sourcebilltype", this.getSrcMainType().getName());
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
package kd.cd.common.snippets;
|
|
2
|
+
|
|
3
|
+
import kd.bos.cache.CacheFactory;
|
|
4
|
+
import kd.bos.cache.DistributeSessionlessCache;
|
|
5
|
+
import kd.bos.context.RequestContext;
|
|
6
|
+
import kd.bos.dataentity.entity.DynamicObject;
|
|
7
|
+
import kd.bos.dataentity.entity.DynamicObjectCollection;
|
|
8
|
+
import kd.bos.entity.cache.AppCache;
|
|
9
|
+
import kd.bos.entity.cache.IAppCache;
|
|
10
|
+
import kd.bos.logging.Log;
|
|
11
|
+
import kd.bos.logging.LogFactory;
|
|
12
|
+
import kd.bos.orm.query.QCP;
|
|
13
|
+
import kd.bos.orm.query.QFilter;
|
|
14
|
+
import kd.bos.servicehelper.BusinessDataServiceHelper;
|
|
15
|
+
import kd.bos.servicehelper.QueryServiceHelper;
|
|
16
|
+
|
|
17
|
+
import java.util.HashMap;
|
|
18
|
+
import java.util.Map;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 缓存使用示例 —— AppCache / DistributeSessionlessCache / loadFromCache。
|
|
22
|
+
* <p>
|
|
23
|
+
* 适用插件:操作插件、OpenAPI 控制器、表单插件、后台任务
|
|
24
|
+
* 优先封装:(暂无 commons 封装)
|
|
25
|
+
* 原生兜底:AppCache、DistributeSessionlessCache、CacheFactory、BusinessDataServiceHelper
|
|
26
|
+
* 相关 lint 规则:(暂无)
|
|
27
|
+
* <p>
|
|
28
|
+
* 使用场景:
|
|
29
|
+
* 1. AppCache:应用级别缓存,适合存储配置、票据、状态等轻量数据;
|
|
30
|
+
* 2. DistributeSessionlessCache:分布式无会话缓存,支持 TTL 自动过期,适合跨节点共享;
|
|
31
|
+
* 3. loadFromCache:基础资料查询走缓存通道,减少数据库访问。
|
|
32
|
+
* <p>
|
|
33
|
+
* <b>注意:缓存 key 应包含足够的业务区分度(如表单标识 + 业务编码),
|
|
34
|
+
* 避免不同业务场景互相覆盖。</b>
|
|
35
|
+
*/
|
|
36
|
+
public class SampleCacheUsage {
|
|
37
|
+
private static final Log log = LogFactory.getLog(SampleCacheUsage.class);
|
|
38
|
+
|
|
39
|
+
// ===================================================================
|
|
40
|
+
// 一、AppCache —— 应用级别缓存
|
|
41
|
+
// ===================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* AppCache 是基于命名空间的应用级缓存。
|
|
45
|
+
* 参数为命名空间名称,同一命名空间内共享 key 空间。
|
|
46
|
+
*/
|
|
47
|
+
private static final IAppCache APP_CACHE = AppCache.get("DEV");
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 场景:单点登录票据缓存。
|
|
51
|
+
* 票据只能使用一次,首次解析后缓存结果,后续直接取缓存。
|
|
52
|
+
*/
|
|
53
|
+
public String getOrCacheTicket(String ticket) {
|
|
54
|
+
// 先查缓存
|
|
55
|
+
String cachedValue = APP_CACHE.get(ticket, String.class);
|
|
56
|
+
if (cachedValue != null) {
|
|
57
|
+
log.info("缓存命中:{}", ticket);
|
|
58
|
+
return cachedValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 缓存未命中,执行实际解析
|
|
62
|
+
String resolvedValue = resolveTicket(ticket);
|
|
63
|
+
|
|
64
|
+
// 写入缓存(无 TTL,缓存随应用生命周期)
|
|
65
|
+
APP_CACHE.put(ticket, resolvedValue);
|
|
66
|
+
log.info("缓存写入:{}", ticket);
|
|
67
|
+
|
|
68
|
+
return resolvedValue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 场景:存储临时状态(如异步操作的中间状态)。
|
|
73
|
+
*/
|
|
74
|
+
public void cacheOperationStatus(String bizKey, String status) {
|
|
75
|
+
APP_CACHE.put(bizKey, status);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public String getOperationStatus(String bizKey) {
|
|
79
|
+
return APP_CACHE.get(bizKey, String.class);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ===================================================================
|
|
83
|
+
// 二、DistributeSessionlessCache —— 分布式无会话缓存(支持 TTL)
|
|
84
|
+
// ===================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 分布式缓存,跨节点共享,支持自动过期。
|
|
88
|
+
* 参数为缓存区域名,同一区域内共享 key 空间。
|
|
89
|
+
*/
|
|
90
|
+
private final DistributeSessionlessCache distCache =
|
|
91
|
+
CacheFactory.getCommonCacheFactory().getDistributeSessionlessCache("kdcd_biz_cache");
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 场景:缓存基础资料映射表(如维度成员数据)。
|
|
95
|
+
* 查询时先查缓存,未命中则从 DB 查询后回填。
|
|
96
|
+
*
|
|
97
|
+
* @param formId 表单标识(如 "bcm_model"、"bos_org")
|
|
98
|
+
* @param selectFields 需要缓存的字段
|
|
99
|
+
* @param cacheKey 缓存 key(建议 formId + 业务标识)
|
|
100
|
+
* @param ttlSeconds 过期时间(秒),一般 180~600
|
|
101
|
+
* @return key=id, value=字段 JSON 串
|
|
102
|
+
*/
|
|
103
|
+
public Map<String, String> getCachedModelData(
|
|
104
|
+
String formId, String selectFields, String cacheKey, int ttlSeconds) {
|
|
105
|
+
|
|
106
|
+
// 1. 先查缓存
|
|
107
|
+
Map<String, String> cached = distCache.getAll(cacheKey);
|
|
108
|
+
if (!cached.isEmpty()) {
|
|
109
|
+
return cached;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. 缓存未命中,查询数据库
|
|
113
|
+
QFilter filter = new QFilter("status", QCP.equals, "C")
|
|
114
|
+
.and(new QFilter("enable", QCP.equals, "1"));
|
|
115
|
+
|
|
116
|
+
DynamicObjectCollection dataCollection = QueryServiceHelper.query(
|
|
117
|
+
formId, "id," + selectFields, new QFilter[]{filter});
|
|
118
|
+
|
|
119
|
+
// 3. 构建缓存 Map
|
|
120
|
+
Map<String, String> dataMap = new HashMap<>();
|
|
121
|
+
for (DynamicObject item : dataCollection) {
|
|
122
|
+
String id = item.getString("id");
|
|
123
|
+
// 可以存 JSON 或简单值
|
|
124
|
+
dataMap.put(id, item.getString(selectFields));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 4. 写入缓存(带 TTL)
|
|
128
|
+
distCache.put(cacheKey, dataMap, ttlSeconds);
|
|
129
|
+
log.info("分布式缓存写入:key={}, size={}, ttl={}s", cacheKey, dataMap.size(), ttlSeconds);
|
|
130
|
+
|
|
131
|
+
return distCache.getAll(cacheKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 场景:缓存单个值(如用户 ID 反查)。
|
|
136
|
+
*
|
|
137
|
+
* @param userNumber 用户编码
|
|
138
|
+
* @return 用户 ID
|
|
139
|
+
*/
|
|
140
|
+
public Long getCachedUserId(String userNumber) {
|
|
141
|
+
String cacheKey = "bos_user_" + userNumber;
|
|
142
|
+
|
|
143
|
+
// 先查缓存
|
|
144
|
+
String userId = (String) distCache.get(cacheKey);
|
|
145
|
+
if (userId != null) {
|
|
146
|
+
return Long.valueOf(userId);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 查询数据库
|
|
150
|
+
DynamicObject userObj = QueryServiceHelper.queryOne(
|
|
151
|
+
"bos_user", "id", new QFilter[]{new QFilter("number", QCP.equals, userNumber)});
|
|
152
|
+
|
|
153
|
+
if (userObj == null) {
|
|
154
|
+
// 兜底:返回当前用户 ID
|
|
155
|
+
userId = String.valueOf(RequestContext.get().getCurrUserId());
|
|
156
|
+
} else {
|
|
157
|
+
userId = userObj.getString("id");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 写入缓存,180 秒过期
|
|
161
|
+
distCache.put(cacheKey, userId, 180);
|
|
162
|
+
|
|
163
|
+
return Long.valueOf(userId);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ===================================================================
|
|
167
|
+
// 三、loadFromCache —— 基础资料缓存查询
|
|
168
|
+
// ===================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 场景:查询基础资料时走缓存通道,减少数据库压力。
|
|
172
|
+
* 适用于基础资料、辅助资料等不经常变更的实体。
|
|
173
|
+
*
|
|
174
|
+
* @param pk 主键
|
|
175
|
+
* @param entityId 实体标识(如 "bos_org"、"bd_currency")
|
|
176
|
+
* @return 缓存中的 DynamicObject(如果缓存中没有会自动从 DB 加载并缓存)
|
|
177
|
+
*/
|
|
178
|
+
public DynamicObject loadBaseDataFromCache(Object pk, String entityId) {
|
|
179
|
+
// loadSingleFromCache:平台内置缓存通道(单条查询)
|
|
180
|
+
// 首次调用从 DB 加载,后续直接从缓存返回
|
|
181
|
+
return BusinessDataServiceHelper.loadSingleFromCache(pk, entityId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 场景:批量加载基础资料走缓存。
|
|
186
|
+
* 返回 Map<主键, DynamicObject>。
|
|
187
|
+
*/
|
|
188
|
+
public Map<Object, DynamicObject> loadBaseDataBatchFromCache(Object[] pks, String entityId) {
|
|
189
|
+
return BusinessDataServiceHelper.loadFromCache(pks, entityId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ===================================================================
|
|
193
|
+
// 缓存选型速查
|
|
194
|
+
// ===================================================================
|
|
195
|
+
//
|
|
196
|
+
// AppCache:
|
|
197
|
+
// - 应用级缓存,同一 JVM 实例内共享
|
|
198
|
+
// - 无 TTL(生命周期随应用)
|
|
199
|
+
// - 适合:配置项、票据、临时状态
|
|
200
|
+
// - 用法:AppCache.get("命名空间") → put/get
|
|
201
|
+
//
|
|
202
|
+
// DistributeSessionlessCache:
|
|
203
|
+
// - 分布式缓存,跨节点共享(底层 Redis)
|
|
204
|
+
// - 支持 TTL 自动过期
|
|
205
|
+
// - 适合:频繁查询的映射表、用户信息、维度成员
|
|
206
|
+
// - 用法:CacheFactory...getDistributeSessionlessCache("区域") → put/get/getAll
|
|
207
|
+
//
|
|
208
|
+
// loadFromCache:
|
|
209
|
+
// - 平台内置基础资料缓存通道
|
|
210
|
+
// - 自动管理缓存生命周期
|
|
211
|
+
// - 适合:基础资料、辅助资料等标准实体
|
|
212
|
+
// - 用法:BusinessDataServiceHelper.loadFromCache(pk, entityId)
|
|
213
|
+
|
|
214
|
+
private String resolveTicket(String ticket) {
|
|
215
|
+
// 模拟票据解析
|
|
216
|
+
return "resolved_" + ticket;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
package kd.cd.common.snippets;
|
|
2
|
+
|
|
3
|
+
import kd.bos.dataentity.entity.DynamicObject;
|
|
4
|
+
import kd.bos.dataentity.entity.DynamicObjectCollection;
|
|
5
|
+
import kd.bos.logging.Log;
|
|
6
|
+
import kd.bos.logging.LogFactory;
|
|
7
|
+
import kd.bos.orm.query.QCP;
|
|
8
|
+
import kd.bos.orm.query.QFilter;
|
|
9
|
+
import kd.bos.servicehelper.QueryServiceHelper;
|
|
10
|
+
import kd.bos.threads.ThreadPools;
|
|
11
|
+
import kd.cd.common.concurrent.ExecutorServiceUtils;
|
|
12
|
+
|
|
13
|
+
import java.util.List;
|
|
14
|
+
import java.util.concurrent.ExecutorService;
|
|
15
|
+
import java.util.stream.Collectors;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 线程池批量处理示例 —— 大批量数据并发处理的标准模式。
|
|
19
|
+
* <p>
|
|
20
|
+
* 适用插件:操作插件、后台调度任务、数据初始化
|
|
21
|
+
* 优先封装:ExecutorServiceUtils.shutdownAndAwaitTermination
|
|
22
|
+
* 原生兜底:ThreadPools、ExecutorService
|
|
23
|
+
* 相关 lint 规则:STYLE-019(禁 new Thread)、STYLE-020(禁 JDK Executors)
|
|
24
|
+
* <p>
|
|
25
|
+
* 使用场景:
|
|
26
|
+
* 1. 批量刷数据:查询大量单据后多线程并发修改;
|
|
27
|
+
* 2. 一次性任务:ThreadPools.executeOnce 执行一次后销毁;
|
|
28
|
+
* 3. 优雅关闭:shutdownAndAwaitTermination 等待所有任务完成再继续。
|
|
29
|
+
* <p>
|
|
30
|
+
*/
|
|
31
|
+
public class SampleThreadPoolBatch {
|
|
32
|
+
private static final Log log = LogFactory.getLog(SampleThreadPoolBatch.class);
|
|
33
|
+
|
|
34
|
+
// ===================================================================
|
|
35
|
+
// 一、标准批量处理模式(最常用)
|
|
36
|
+
// ===================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 批量并发修改单据。
|
|
40
|
+
* 步骤:查询 → 创建线程池 → 循环提交任务 → 优雅关闭等待完成。
|
|
41
|
+
*
|
|
42
|
+
* @param formId 单据标识
|
|
43
|
+
* @param conditions 过滤条件
|
|
44
|
+
*/
|
|
45
|
+
public void batchProcess(String formId, QFilter[] conditions) {
|
|
46
|
+
// ---------- Step 1: 查询待处理数据 ----------
|
|
47
|
+
DynamicObjectCollection coll = QueryServiceHelper.query(formId, "id", conditions);
|
|
48
|
+
List<Object> pks = coll.stream()
|
|
49
|
+
.map(o -> o.get("id"))
|
|
50
|
+
.distinct()
|
|
51
|
+
.collect(Collectors.toList());
|
|
52
|
+
|
|
53
|
+
if (pks.isEmpty()) {
|
|
54
|
+
log.info("无需处理的数据");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------- Step 2: 创建线程池 ----------
|
|
59
|
+
// 命名规则:业务标识 + 时间戳,方便运维排查
|
|
60
|
+
// 线程数:一般 50~100,根据单条任务耗时和总量调整
|
|
61
|
+
ExecutorService pool = ThreadPools.newExecutorService(
|
|
62
|
+
"kdcd_batch_" + System.currentTimeMillis(), 50);
|
|
63
|
+
|
|
64
|
+
// ---------- Step 3: 循环提交任务 ----------
|
|
65
|
+
for (Object pk : pks) {
|
|
66
|
+
pool.execute(() -> processSingleItem(formId, pk));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------- Step 4: 优雅关闭并等待所有任务完成 ----------
|
|
70
|
+
// 参数:线程池、超时时间(秒);一般 1800(30分钟)到 2400(40分钟)
|
|
71
|
+
ExecutorServiceUtils.shutdownAndAwaitTermination(pool, 1800);
|
|
72
|
+
|
|
73
|
+
log.info("批量处理完成,共 {} 条", pks.size());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 单条数据处理逻辑(每个线程执行一条)。
|
|
78
|
+
* 要点:捕获异常,避免单条失败导致整个线程池崩溃。
|
|
79
|
+
*/
|
|
80
|
+
private void processSingleItem(String formId, Object pk) {
|
|
81
|
+
try {
|
|
82
|
+
// 此处编写具体业务逻辑(如修改单据、调接口等)
|
|
83
|
+
log.info("正在处理:{}", pk);
|
|
84
|
+
} catch (Exception e) {
|
|
85
|
+
log.error("处理失败:pk={}", pk, e);
|
|
86
|
+
// 根据业务需要决定:记录失败 or 更新状态字段 or 重试
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ===================================================================
|
|
91
|
+
// 二、ThreadPools.executeOnce —— 一次性异步任务
|
|
92
|
+
// ===================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 场景:只执行一次的后台任务(如异步生成中间表、触发通知等)。
|
|
96
|
+
* ThreadPools.executeOnce 会创建单线程池执行后自动销毁。
|
|
97
|
+
*
|
|
98
|
+
* @param taskName 任务标识名(用于日志追踪)
|
|
99
|
+
*/
|
|
100
|
+
public void executeOnceExample(String taskName) {
|
|
101
|
+
ThreadPools.executeOnce(taskName, () -> {
|
|
102
|
+
try {
|
|
103
|
+
// 执行一次性任务
|
|
104
|
+
log.info("一次性任务开始:{}", taskName);
|
|
105
|
+
doHeavyWork();
|
|
106
|
+
log.info("一次性任务完成:{}", taskName);
|
|
107
|
+
} catch (Exception e) {
|
|
108
|
+
log.error("一次性任务异常:{}", taskName, e);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ===================================================================
|
|
114
|
+
// 三、带分片的批量处理(超大数据量场景)
|
|
115
|
+
// ===================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 超大批量场景:先分片再并发。
|
|
119
|
+
* 适用于数十万条以上的数据处理,避免一次性查出所有 PK 占用过多内存。
|
|
120
|
+
*
|
|
121
|
+
* @param formId 单据标识
|
|
122
|
+
* @param pageSize 每页大小
|
|
123
|
+
*/
|
|
124
|
+
public void batchProcessByPage(String formId, int pageSize) {
|
|
125
|
+
ExecutorService pool = ThreadPools.newExecutorService(
|
|
126
|
+
"kdcd_paged_" + System.currentTimeMillis(), 50);
|
|
127
|
+
|
|
128
|
+
while (true) {
|
|
129
|
+
// 分页查询
|
|
130
|
+
DynamicObjectCollection page = QueryServiceHelper.query(
|
|
131
|
+
formId, "id",
|
|
132
|
+
new QFilter[]{new QFilter("kdcd_processed", QCP.equals, false)},
|
|
133
|
+
"id asc", pageSize);
|
|
134
|
+
|
|
135
|
+
if (page.isEmpty()) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (DynamicObject row : page) {
|
|
140
|
+
Object pk = row.get("id");
|
|
141
|
+
pool.execute(() -> processSingleItem(formId, pk));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (page.size() < pageSize) {
|
|
145
|
+
break; // 最后一页
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
ExecutorServiceUtils.shutdownAndAwaitTermination(pool, 2400);
|
|
150
|
+
log.info("分页批量处理完成");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private void doHeavyWork() {
|
|
154
|
+
// 模拟耗时操作
|
|
155
|
+
}
|
|
156
|
+
}
|