From c0dc0823b621d5c6548457fc34308bb51eb4ccc5 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 15 Oct 2025 08:59:57 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=A7=84=E8=8C=83=E5=A2=9E=E9=87=8F=20SQL=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=91=BD=E5=90=8D=202.=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=80=BB=E7=BA=BF=E6=A8=A1=E5=9D=97=EF=BC=88?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E6=88=90=EF=BC=89=203.=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=A8=A1=E5=9D=97=EF=BC=88=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90=EF=BC=89=204.=E6=96=B0=E5=A2=9E=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E4=B8=8E=E5=A4=96=E9=83=A8=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=BB=84=E7=BB=87=E7=BC=96=E7=A0=81=E6=98=A0=E5=B0=84=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=E8=A1=A8=205.=E8=A1=A5=E5=85=A8=20e=20=E5=8A=9E?= =?UTF-8?q?=E5=8D=95=E7=82=B9=E7=99=BB=E5=BD=95=E5=9B=9E=E8=B0=83=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...结构.sql => e 办同步信息日志表结构_20250917.sql} | 0 ...管理菜单.sql => 在线文档管理菜单_20250901.sql} | 0 ...表结构.sql => 在线文档管理表结构_20250901.sql} | 0 ....sql => 扩充用户表部门表名称字段,匹配e办数据_20250918.sql} | 0 ...表状态.sql => 新增业务附件表状态_20250925.sql} | 0 ...移.sql => 新增用户、组织机构源表结构并迁移_20250917.sql} | 0 ...迁移.sql => 新增用户与组织关系表并迁移_20250717.sql} | 0 ...型字段.sql => 新增组织机构类型字段_20250703.sql} | 0 sql/dm/统一外部网关菜单_20251010.sql | 31 ++ sql/dm/统一对外网关_20251010.sql | 241 ++++++++++ sql/dm/规则引擎核心表结构_20251014.sql | 228 +++++++++ sql/dm/规则引擎菜单初始化_20251014.sql | 27 ++ ...限剔除表.sql => 角色权限剔除表_20250623.sql} | 0 sql/dm/部门外部组织编码映射初始化_DM8.sql | 60 +++ sql/dm/部门外部组织编码映射菜单权限_DM8.sql | 35 ++ ...称字段.sql => 部门添加编码、简称字段_20250918.sql} | 0 sql/mysql/databus_sample_data.sql | 65 +++ sql/tools/convertor.py | 2 +- .../src/main/resources/application.yaml | 20 + .../zt-module-databus-server/pom.xml | 57 +++ .../admin/databus/DatabusController.java | 29 -- .../gateway/ApiDefinitionController.java | 79 ++++ .../admin/gateway/ApiGatewayController.java | 107 +++++ .../gateway/ApiPolicyAuthController.java | 84 ++++ .../gateway/ApiPolicyRateLimitController.java | 84 ++++ .../gateway/convert/ApiDefinitionConvert.java | 104 +++++ .../gateway/convert/ApiPolicyAuthConvert.java | 25 + .../convert/ApiPolicyRateLimitConvert.java | 25 + .../gateway/vo/ApiGatewayInvokeReqVO.java | 27 ++ .../definition/ApiDefinitionDetailRespVO.java | 74 +++ .../vo/definition/ApiDefinitionPageReqVO.java | 25 + .../ApiDefinitionPublicationRespVO.java | 28 ++ .../vo/definition/ApiDefinitionSaveReqVO.java | 67 +++ .../definition/ApiDefinitionStepRespVO.java | 55 +++ .../ApiDefinitionStepSaveReqVO.java | 58 +++ .../ApiDefinitionSummaryRespVO.java | 48 ++ .../ApiDefinitionTransformRespVO.java | 31 ++ .../ApiDefinitionTransformSaveReqVO.java | 29 ++ .../gateway/vo/policy/ApiPolicyBaseVO.java | 27 ++ .../gateway/vo/policy/ApiPolicyPageReqVO.java | 22 + .../gateway/vo/policy/ApiPolicyRespVO.java | 32 ++ .../gateway/vo/policy/ApiPolicySaveReqVO.java | 18 + .../vo/policy/ApiPolicySimpleRespVO.java | 25 + .../dataobject/gateway/ApiDefinitionDO.java | 52 +++ .../dataobject/gateway/ApiFlowPublishDO.java | 35 ++ .../dataobject/gateway/ApiPolicyAuthDO.java | 31 ++ .../gateway/ApiPolicyRateLimitDO.java | 31 ++ .../dal/dataobject/gateway/ApiStepDO.java | 49 ++ .../dataobject/gateway/ApiTransformDO.java | 35 ++ .../mysql/gateway/ApiDefinitionMapper.java | 63 +++ .../mysql/gateway/ApiFlowPublishMapper.java | 19 + .../mysql/gateway/ApiPolicyAuthMapper.java | 36 ++ .../gateway/ApiPolicyRateLimitMapper.java | 36 ++ .../dal/mysql/gateway/ApiStepMapper.java | 25 + .../dal/mysql/gateway/ApiTransformMapper.java | 39 ++ .../databus/enums/gateway/ApiStatusEnum.java | 28 ++ .../enums/gateway/ApiStepTypeEnum.java | 18 + .../enums/gateway/ExpressionTypeEnum.java | 32 ++ .../enums/gateway/TransformPhaseEnum.java | 28 ++ .../config/ApiGatewayProperties.java | 36 ++ .../config/ExpressionConfiguration.java | 25 + .../GatewayIntegrationConfiguration.java | 147 ++++++ .../gateway/core/ApiFlowAssembler.java | 270 +++++++++++ .../gateway/core/ApiFlowDispatcher.java | 38 ++ .../gateway/core/ApiFlowRegistration.java | 20 + .../gateway/core/ApiGatewayRequestMapper.java | 82 ++++ .../gateway/core/ErrorHandlingStrategy.java | 73 +++ .../gateway/core/IntegrationFlowManager.java | 96 ++++ .../gateway/core/MonitoringInterceptor.java | 54 +++ .../gateway/core/PolicyAdvisorFactory.java | 166 +++++++ .../domain/ApiDefinitionAggregate.java | 40 ++ .../gateway/domain/ApiFlowPublication.java | 25 + .../gateway/domain/ApiStepDefinition.java | 32 ++ .../domain/ApiTransformDefinition.java | 23 + .../ExpressionEvaluationContext.java | 24 + .../expression/ExpressionEvaluator.java | 10 + .../ExpressionEvaluatorRegistry.java | 28 ++ .../expression/ExpressionExecutor.java | 51 +++ .../gateway/expression/ExpressionSpec.java | 21 + .../expression/ExpressionSpecParser.java | 32 ++ .../JsScriptExpressionEvaluator.java | 13 + .../JsonataExpressionEvaluator.java | 50 ++ .../expression/MvelExpressionEvaluator.java | 13 + .../expression/SpelExpressionEvaluator.java | 13 + .../gateway/init/GatewayPolicyMigration.java | 19 + .../gateway/model/ApiGatewayResponse.java | 21 + .../gateway/model/ApiInvocationContext.java | 115 +++++ .../gateway/model/ApiStepResult.java | 35 ++ .../gateway/policy/AuthPolicyEvaluator.java | 13 + .../policy/DefaultAuthPolicyEvaluator.java | 56 +++ .../DefaultRateLimitPolicyEvaluator.java | 61 +++ .../policy/RateLimitPolicyEvaluator.java | 13 + .../security/GatewaySecurityFilter.java | 75 +++ .../gateway/step/ApiStepHandler.java | 17 + .../gateway/step/StepHandlerFactory.java | 34 ++ .../gateway/step/impl/HttpStepHandler.java | 395 ++++++++++++++++ .../gateway/step/impl/RpcStepHandler.java | 154 +++++++ .../gateway/step/impl/ScriptStepHandler.java | 74 +++ .../service/gateway/ApiDefinitionService.java | 57 +++ .../service/gateway/ApiPolicyAuthService.java | 46 ++ .../gateway/ApiPolicyRateLimitService.java | 46 ++ .../impl/ApiDefinitionServiceImpl.java | 431 ++++++++++++++++++ .../impl/ApiPolicyAuthServiceImpl.java | 98 ++++ .../impl/ApiPolicyRateLimitServiceImpl.java | 98 ++++ .../GatewayServiceErrorCodeConstants.java | 39 ++ .../step/impl/HttpStepHandlerTest.java | 105 +++++ .../gateway/ApiDefinitionServiceImplTest.java | 276 +++++++++++ .../test/resources/application-unit-test.yaml | 42 ++ .../src/test/resources/application.yaml | 5 + .../src/test/resources/sql/clean.sql | 7 + .../src/test/resources/sql/create_tables.sql | 118 +++++ zt-module-rule/pom.xml | 24 - zt-module-rule/zt-module-rule-api/pom.xml | 46 -- .../plat/module/rule/api/RuleEngineApi.java | 42 ++ .../api/dto/RuleChainExecuteMetaRespDTO.java | 26 ++ .../rule/api/dto/RuleChainLoadReqDTO.java | 19 + .../rule/api/dto/RuleChainSimpleRespDTO.java | 20 + .../rule/api/dto/RuleChainVersionRespDTO.java | 31 ++ .../plat/module/rule/enums/ApiConstants.java | 24 + .../module/rule/enums/ErrorCodeConstants.java | 17 - .../rule/enums/OverrideStrategyEnum.java | 32 ++ .../module/rule/enums/RuleNodeTypeEnum.java | 44 ++ .../rule/enums/RulePublishStatusEnum.java | 32 ++ .../module/rule/enums/RuleStatusEnum.java | 32 ++ .../plat/module/rule/enums/RuleTypeEnum.java | 32 ++ zt-module-rule/zt-module-rule-server/pom.xml | 158 ------- .../module/rule/RuleServerApplication.java | 6 - .../business/RuleBusinessController.java | 104 +++++ .../vo/RuleBusinessBindPageReqVO.java | 24 + .../business/vo/RuleBusinessBindRespVO.java | 34 ++ .../vo/RuleBusinessBindSaveReqVO.java | 27 ++ .../vo/RuleBusinessChainViewRespVO.java | 23 + .../vo/RuleBusinessRelationSaveReqVO.java | 20 + .../vo/RuleBusinessTreeNodeRespVO.java | 23 + .../admin/chain/RuleChainController.java | 90 ++++ .../admin/chain/vo/RuleChainBaseVO.java | 34 ++ .../admin/chain/vo/RuleChainCreateReqVO.java | 13 + .../admin/chain/vo/RuleChainLinkVO.java | 20 + .../admin/chain/vo/RuleChainNodeVO.java | 43 ++ .../admin/chain/vo/RuleChainPageReqVO.java | 25 + .../admin/chain/vo/RuleChainRespVO.java | 25 + .../admin/chain/vo/RuleChainStructureVO.java | 20 + .../admin/chain/vo/RuleChainUpdateReqVO.java | 18 + .../definition/RuleDefinitionController.java | 96 ++++ .../definition/vo/RuleDefinitionBaseVO.java | 43 ++ .../vo/RuleDefinitionCreateReqVO.java | 16 + .../vo/RuleDefinitionPageReqVO.java | 31 ++ .../definition/vo/RuleDefinitionRespVO.java | 27 ++ .../vo/RuleDefinitionUpdateReqVO.java | 21 + .../admin/publish/RulePublishController.java | 74 +++ .../publish/vo/RuleReleaseCreateReqVO.java | 19 + .../publish/vo/RuleReleasePageReqVO.java | 24 + .../admin/publish/vo/RuleReleaseRespVO.java | 30 ++ .../publish/vo/RuleReleaseRollbackReqVO.java | 17 + .../controller/admin/rule/RuleController.java | 136 ------ .../controller/admin/rule/vo/RuleBaseVO.java | 44 -- .../admin/rule/vo/RuleCreateReqVO.java | 14 - .../admin/rule/vo/RuleExecuteReqVO.java | 116 ----- .../admin/rule/vo/RuleExecuteRespVO.java | 59 --- .../admin/rule/vo/RulePageReqVO.java | 32 -- .../controller/admin/rule/vo/RuleRespVO.java | 25 - .../admin/rule/vo/RuleUpdateReqVO.java | 20 - .../simulation/RuleSimulationController.java | 35 ++ .../vo/RuleSimulationExecuteReqVO.java | 24 + .../vo/RuleSimulationExecuteRespVO.java | 25 + .../convert/business/RuleBusinessConvert.java | 24 + .../rule/convert/chain/RuleChainConvert.java | 81 ++++ .../definition/RuleDefinitionConvert.java | 27 ++ .../convert/publish/RuleReleaseConvert.java | 21 + .../module/rule/convert/rule/RuleConvert.java | 31 -- .../business/RuleBusinessBindingDO.java | 47 ++ .../business/RuleBusinessRelationDO.java | 31 ++ .../dal/dataobject/chain/RuleChainDO.java | 44 ++ .../chain/RuleChainDependencyDO.java | 49 ++ .../definition/RuleDefinitionDO.java | 72 +++ .../release/RuleReleaseRecordDO.java | 51 +++ .../rule/dal/dataobject/rule/RuleDO.java | 70 --- .../business/RuleBusinessBindingMapper.java | 36 ++ .../business/RuleBusinessRelationMapper.java | 26 ++ .../chain/RuleChainDependencyMapper.java | 24 + .../rule/dal/mysql/chain/RuleChainMapper.java | 31 ++ .../definition/RuleDefinitionMapper.java | 40 ++ .../release/RuleReleaseRecordMapper.java | 37 ++ .../rule/dal/mysql/rule/RuleMapper.java | 28 -- .../module/rule/enums/ErrorCodeConstants.java | 31 +- .../config/RuleModuleConfiguration.java | 12 + .../config/RulePublishProperties.java | 22 + .../config/SecurityConfiguration.java | 26 +- .../liteflow/RuleChainAssembler.java | 110 +++++ .../liteflow/RuleLiteflowManager.java | 45 ++ .../AbstractJsonConfigNodeComponent.java | 151 ++++++ .../component/action/DataSetComponent.java | 177 ++++--- .../action/MathCalculateComponent.java | 139 +++--- .../component/base/BaseRuleComponent.java | 149 ------ .../common/NumberCompareComponent.java | 96 ++-- .../common/StringConditionComponent.java | 138 +++--- .../config/LiteFlowConfiguration.java | 24 - .../liteflow/service/LiteFlowService.java | 212 --------- .../framework/nacos/RuleNacosPublisher.java | 57 +++ .../service/business/RuleBusinessService.java | 33 ++ .../business/RuleBusinessServiceImpl.java | 331 ++++++++++++++ .../rule/service/chain/RuleChainService.java | 23 + .../service/chain/RuleChainServiceImpl.java | 188 ++++++++ .../definition/RuleDefinitionService.java | 25 + .../definition/RuleDefinitionServiceImpl.java | 86 ++++ .../dto/BusinessChainAssembleResultDTO.java | 24 + .../rule/service/dto/RuleChainLinkDTO.java | 21 + .../rule/service/dto/RuleChainNodeDTO.java | 37 ++ .../service/dto/RuleChainStructureDTO.java | 22 + .../service/publish/RulePublishService.java | 18 + .../publish/RulePublishServiceImpl.java | 141 ++++++ .../module/rule/service/rule/RuleService.java | 84 ---- .../rule/service/rule/RuleServiceImpl.java | 252 ---------- .../simulation/RuleSimulationService.java | 9 + .../simulation/RuleSimulationServiceImpl.java | 94 ++++ .../src/main/resources/application-dev.yml | 107 ----- .../src/main/resources/application-local.yml | 97 ---- .../src/main/resources/application.yml | 4 +- .../resources/liteflow/default-rules.json | 58 +-- .../src/main/resources/sql/rule_rule.sql | 31 -- .../rule/example/RuleEngineExample.java | 229 +--------- .../service/rule/RuleServiceImplTest.java | 200 +------- .../system/api/dept/DeptExternalCodeApi.java | 38 ++ .../api/dept/dto/DeptExternalCodeRespDTO.java | 45 ++ .../system/enums/ErrorCodeConstants.java | 5 + .../api/dept/DeptExternalCodeApiImpl.java | 79 ++++ .../controller/admin/auth/AuthController.java | 7 + .../auth/vo/AuthVerifyPasswordReqVO.java | 21 + .../dept/DeptExternalCodeController.java | 116 +++++ .../DeptExternalCodeBaseVO.java | 41 ++ .../DeptExternalCodePageReqVO.java | 25 + .../DeptExternalCodeRespVO.java | 29 ++ .../DeptExternalCodeSaveReqVO.java | 15 + .../dataobject/dept/DeptExternalCodeDO.java | 61 +++ .../mysql/dept/DeptExternalCodeMapper.java | 44 ++ .../mysql/oauth2/OAuth2AccessTokenMapper.java | 18 + .../system/service/auth/AdminAuthService.java | 8 + .../service/auth/AdminAuthServiceImpl.java | 14 + .../service/dept/DeptExternalCodeService.java | 62 +++ .../dept/DeptExternalCodeServiceImpl.java | 151 ++++++ .../service/oauth2/EbanOAuth2Service.java | 8 + .../service/oauth2/EbanOAuth2ServiceImpl.java | 326 +++++++------ .../service/oauth2/EbanTokenService.java | 4 +- .../service/oauth2/EbanTokenServiceImpl.java | 159 +++++-- .../src/main/resources/application.yaml | 2 +- zt-server/src/main/resources/application.yaml | 2 +- 246 files changed, 11118 insertions(+), 2749 deletions(-) rename sql/dm/{e 办同步信息日志表结构.sql => e 办同步信息日志表结构_20250917.sql} (100%) rename sql/dm/{在线文档管理菜单.sql => 在线文档管理菜单_20250901.sql} (100%) rename sql/dm/{在线文档管理表结构.sql => 在线文档管理表结构_20250901.sql} (100%) rename sql/dm/{扩充用户表部门表名称字段,匹配e办数据.sql => 扩充用户表部门表名称字段,匹配e办数据_20250918.sql} (100%) rename sql/dm/{新增业务附件表状态.sql => 新增业务附件表状态_20250925.sql} (100%) rename sql/dm/{新增用户、组织机构源表结构并迁移.sql => 新增用户、组织机构源表结构并迁移_20250917.sql} (100%) rename sql/dm/{新增用户与组织关系表并迁移.sql => 新增用户与组织关系表并迁移_20250717.sql} (100%) rename sql/dm/{新增组织机构类型字段.sql => 新增组织机构类型字段_20250703.sql} (100%) create mode 100644 sql/dm/统一外部网关菜单_20251010.sql create mode 100644 sql/dm/统一对外网关_20251010.sql create mode 100644 sql/dm/规则引擎核心表结构_20251014.sql create mode 100644 sql/dm/规则引擎菜单初始化_20251014.sql rename sql/dm/{角色权限剔除表.sql => 角色权限剔除表_20250623.sql} (100%) create mode 100644 sql/dm/部门外部组织编码映射初始化_DM8.sql create mode 100644 sql/dm/部门外部组织编码映射菜单权限_DM8.sql rename sql/dm/{部门添加编码、简称字段.sql => 部门添加编码、简称字段_20250918.sql} (100%) create mode 100644 sql/mysql/databus_sample_data.sql delete mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/databus/DatabusController.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiDefinitionController.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyRateLimitController.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyRateLimitConvert.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/ApiGatewayInvokeReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPublicationRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformSaveReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyBaseVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyPageReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySaveReqVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySimpleRespVO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiFlowPublishDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyRateLimitDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiTransformDO.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiFlowPublishMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyRateLimitMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiStepMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiTransformMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ExpressionTypeEnum.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/TransformPhaseEnum.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ExpressionConfiguration.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ErrorHandlingStrategy.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/IntegrationFlowManager.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/MonitoringInterceptor.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/PolicyAdvisorFactory.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiFlowPublication.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiStepDefinition.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiTransformDefinition.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluationContext.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluatorRegistry.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionExecutor.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpec.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpecParser.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsScriptExpressionEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsonataExpressionEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/MvelExpressionEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/SpelExpressionEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/init/GatewayPolicyMigration.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiGatewayResponse.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiStepResult.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/AuthPolicyEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultAuthPolicyEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultRateLimitPolicyEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/RateLimitPolicyEvaluator.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/ApiStepHandler.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/StepHandlerFactory.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/RpcStepHandler.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/ScriptStepHandler.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionService.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyAuthService.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyRateLimitService.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiDefinitionServiceImpl.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyAuthServiceImpl.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyRateLimitServiceImpl.java create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/GatewayServiceErrorCodeConstants.java create mode 100644 zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerTest.java create mode 100644 zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java create mode 100644 zt-module-databus/zt-module-databus-server/src/test/resources/application-unit-test.yaml create mode 100644 zt-module-databus/zt-module-databus-server/src/test/resources/application.yaml create mode 100644 zt-module-databus/zt-module-databus-server/src/test/resources/sql/clean.sql create mode 100644 zt-module-databus/zt-module-databus-server/src/test/resources/sql/create_tables.sql delete mode 100644 zt-module-rule/pom.xml delete mode 100644 zt-module-rule/zt-module-rule-api/pom.xml create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/RuleEngineApi.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainExecuteMetaRespDTO.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainLoadReqDTO.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainSimpleRespDTO.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainVersionRespDTO.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ApiConstants.java delete mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/OverrideStrategyEnum.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleNodeTypeEnum.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RulePublishStatusEnum.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleStatusEnum.java create mode 100644 zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleTypeEnum.java delete mode 100644 zt-module-rule/zt-module-rule-server/pom.xml create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/RuleBusinessController.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindPageReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindSaveReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessChainViewRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessRelationSaveReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessTreeNodeRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/RuleChainController.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainBaseVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainCreateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainLinkVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainNodeVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainPageReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainStructureVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainUpdateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/RuleDefinitionController.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionBaseVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionCreateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionPageReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionUpdateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/RulePublishController.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseCreateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleasePageReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRollbackReqVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/RuleController.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleBaseVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleCreateReqVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteReqVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteRespVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RulePageReqVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleRespVO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleUpdateReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/RuleSimulationController.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteReqVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteRespVO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/business/RuleBusinessConvert.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/chain/RuleChainConvert.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/definition/RuleDefinitionConvert.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/publish/RuleReleaseConvert.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/rule/RuleConvert.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessBindingDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessRelationDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDependencyDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/definition/RuleDefinitionDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/release/RuleReleaseRecordDO.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/rule/RuleDO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessBindingMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessRelationMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainDependencyMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/definition/RuleDefinitionMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/release/RuleReleaseRecordMapper.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/rule/RuleMapper.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RuleModuleConfiguration.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RulePublishProperties.java rename zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/{security => }/config/SecurityConfiguration.java (51%) create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleChainAssembler.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleLiteflowManager.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/AbstractJsonConfigNodeComponent.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/base/BaseRuleComponent.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/config/LiteFlowConfiguration.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/service/LiteFlowService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/nacos/RuleNacosPublisher.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessServiceImpl.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainServiceImpl.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionServiceImpl.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/BusinessChainAssembleResultDTO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainLinkDTO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainNodeDTO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainStructureDTO.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishServiceImpl.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleService.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleServiceImpl.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationService.java create mode 100644 zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationServiceImpl.java delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/resources/application-dev.yml delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/resources/application-local.yml delete mode 100644 zt-module-rule/zt-module-rule-server/src/main/resources/sql/rule_rule.sql create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApi.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptExternalCodeRespDTO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApiImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthVerifyPasswordReqVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptExternalCodeController.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeBaseVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodePageReqVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeRespVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeSaveReqVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptExternalCodeDO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java diff --git a/sql/dm/e 办同步信息日志表结构.sql b/sql/dm/e 办同步信息日志表结构_20250917.sql similarity index 100% rename from sql/dm/e 办同步信息日志表结构.sql rename to sql/dm/e 办同步信息日志表结构_20250917.sql diff --git a/sql/dm/在线文档管理菜单.sql b/sql/dm/在线文档管理菜单_20250901.sql similarity index 100% rename from sql/dm/在线文档管理菜单.sql rename to sql/dm/在线文档管理菜单_20250901.sql diff --git a/sql/dm/在线文档管理表结构.sql b/sql/dm/在线文档管理表结构_20250901.sql similarity index 100% rename from sql/dm/在线文档管理表结构.sql rename to sql/dm/在线文档管理表结构_20250901.sql diff --git a/sql/dm/扩充用户表部门表名称字段,匹配e办数据.sql b/sql/dm/扩充用户表部门表名称字段,匹配e办数据_20250918.sql similarity index 100% rename from sql/dm/扩充用户表部门表名称字段,匹配e办数据.sql rename to sql/dm/扩充用户表部门表名称字段,匹配e办数据_20250918.sql diff --git a/sql/dm/新增业务附件表状态.sql b/sql/dm/新增业务附件表状态_20250925.sql similarity index 100% rename from sql/dm/新增业务附件表状态.sql rename to sql/dm/新增业务附件表状态_20250925.sql diff --git a/sql/dm/新增用户、组织机构源表结构并迁移.sql b/sql/dm/新增用户、组织机构源表结构并迁移_20250917.sql similarity index 100% rename from sql/dm/新增用户、组织机构源表结构并迁移.sql rename to sql/dm/新增用户、组织机构源表结构并迁移_20250917.sql diff --git a/sql/dm/新增用户与组织关系表并迁移.sql b/sql/dm/新增用户与组织关系表并迁移_20250717.sql similarity index 100% rename from sql/dm/新增用户与组织关系表并迁移.sql rename to sql/dm/新增用户与组织关系表并迁移_20250717.sql diff --git a/sql/dm/新增组织机构类型字段.sql b/sql/dm/新增组织机构类型字段_20250703.sql similarity index 100% rename from sql/dm/新增组织机构类型字段.sql rename to sql/dm/新增组织机构类型字段_20250703.sql diff --git a/sql/dm/统一外部网关菜单_20251010.sql b/sql/dm/统一外部网关菜单_20251010.sql new file mode 100644 index 00000000..e7c36fe3 --- /dev/null +++ b/sql/dm/统一外部网关菜单_20251010.sql @@ -0,0 +1,31 @@ + +-- 清理旧数据,确保脚本可重复执行 +DELETE FROM system_menu WHERE id IN (6500,6501,650101,650102,650103); + +-- 顶级目录(父级假定为 id=2 的系统管理目录) +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status, component_name +) VALUES ( + 6500, '统一外部网关', '', 1, 20, 1, + 'databus', 'ep:data-line', '', 0, NULL +); + +-- API 门户页面 +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status, component_name +) VALUES ( + 6501, 'API 门户', 'databus:gateway:query', 2, 1, 6500, + 'databus-gateway', 'ep:cpu', 'databus/gateway/index', 0, 'DatabusGateway' +); + +-- 页面内操作按钮权限 +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status +) VALUES + (650101, 'API 列表', 'databus:gateway:query', 3, 1, 6501, '', '', '', 0), + (650102, 'API 调试', 'databus:gateway:invoke', 3, 2, 6501, '', '', '', 0), + (650103, '刷新定义', 'databus:gateway:refresh', 3, 3, 6501, '', '', '', 0); +d \ No newline at end of file diff --git a/sql/dm/统一对外网关_20251010.sql b/sql/dm/统一对外网关_20251010.sql new file mode 100644 index 00000000..b4c39768 --- /dev/null +++ b/sql/dm/统一对外网关_20251010.sql @@ -0,0 +1,241 @@ +/* + * Databus API portal schema for DM8 + * Generated on 2025-10-10 + */ + +-- ---------------------------- +-- Table structure for databus_api_definition +-- ---------------------------- +CREATE TABLE databus_api_definition ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + api_code VARCHAR(128) NOT NULL, + uri_pattern VARCHAR(256) NOT NULL, + http_method VARCHAR(16) NOT NULL, + version VARCHAR(32) NOT NULL, + status SMALLINT DEFAULT 0 NOT NULL, + description VARCHAR(512), + auth_policy_id BIGINT, + rate_limit_id BIGINT, + response_template CLOB, + cache_strategy VARCHAR(128), + updated_at DATETIME, + grey_released BIT DEFAULT '0' NOT NULL, + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE UNIQUE INDEX uk_databus_api_definition_code_ver ON databus_api_definition (tenant_id, api_code, version); +CREATE INDEX idx_databus_api_definition_status ON databus_api_definition (tenant_id, status); +CREATE INDEX idx_databus_api_definition_policy ON databus_api_definition (tenant_id, auth_policy_id, rate_limit_id); + +COMMENT ON TABLE databus_api_definition IS '统一外部 API 门户 - API 定义表'; +COMMENT ON COLUMN databus_api_definition.id IS '主键 ID'; +COMMENT ON COLUMN databus_api_definition.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_api_definition.api_code IS 'API 编码'; +COMMENT ON COLUMN databus_api_definition.uri_pattern IS '匹配路径模板'; +COMMENT ON COLUMN databus_api_definition.http_method IS 'HTTP 方法'; +COMMENT ON COLUMN databus_api_definition.version IS '版本号'; +COMMENT ON COLUMN databus_api_definition.status IS '发布状态'; +COMMENT ON COLUMN databus_api_definition.description IS '描述信息'; +COMMENT ON COLUMN databus_api_definition.auth_policy_id IS '认证策略 ID'; +COMMENT ON COLUMN databus_api_definition.rate_limit_id IS '限流策略 ID'; +COMMENT ON COLUMN databus_api_definition.response_template IS '响应模板 JSON'; +COMMENT ON COLUMN databus_api_definition.cache_strategy IS '缓存策略配置'; +COMMENT ON COLUMN databus_api_definition.updated_at IS '业务更新时间'; +COMMENT ON COLUMN databus_api_definition.grey_released IS '灰度发布标记'; +COMMENT ON COLUMN databus_api_definition.creator IS '创建者'; +COMMENT ON COLUMN databus_api_definition.create_time IS '创建时间'; +COMMENT ON COLUMN databus_api_definition.updater IS '更新者'; +COMMENT ON COLUMN databus_api_definition.update_time IS '更新时间'; +COMMENT ON COLUMN databus_api_definition.deleted IS '逻辑删除标记'; + +-- ---------------------------- +-- Table structure for databus_api_flow_publish +-- ---------------------------- +CREATE TABLE databus_api_flow_publish ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + api_id BIGINT NOT NULL, + release_tag VARCHAR(64), + snapshot CLOB, + status VARCHAR(32), + active BIT DEFAULT '0' NOT NULL, + description VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE INDEX idx_databus_api_flow_publish_api ON databus_api_flow_publish (tenant_id, api_id, active); + +COMMENT ON TABLE databus_api_flow_publish IS '统一外部 API 门户 - 发布记录表'; +COMMENT ON COLUMN databus_api_flow_publish.id IS '主键 ID'; +COMMENT ON COLUMN databus_api_flow_publish.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_api_flow_publish.api_id IS '关联的 API ID'; +COMMENT ON COLUMN databus_api_flow_publish.release_tag IS '发布批次标识'; +COMMENT ON COLUMN databus_api_flow_publish.snapshot IS '配置快照 JSON'; +COMMENT ON COLUMN databus_api_flow_publish.status IS '发布状态'; +COMMENT ON COLUMN databus_api_flow_publish.active IS '是否当前生效'; +COMMENT ON COLUMN databus_api_flow_publish.description IS '备注信息'; +COMMENT ON COLUMN databus_api_flow_publish.creator IS '创建者'; +COMMENT ON COLUMN databus_api_flow_publish.create_time IS '创建时间'; +COMMENT ON COLUMN databus_api_flow_publish.updater IS '更新者'; +COMMENT ON COLUMN databus_api_flow_publish.update_time IS '更新时间'; +COMMENT ON COLUMN databus_api_flow_publish.deleted IS '逻辑删除标记'; + +-- ---------------------------- +-- Table structure for databus_policy_auth +-- ---------------------------- +CREATE TABLE databus_policy_auth ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(64) NOT NULL, + config CLOB, + description VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE UNIQUE INDEX uk_databus_policy_auth_name ON databus_policy_auth (tenant_id, name); + +COMMENT ON TABLE databus_policy_auth IS '统一外部 API 门户 - 认证策略表'; +COMMENT ON COLUMN databus_policy_auth.id IS '主键 ID'; +COMMENT ON COLUMN databus_policy_auth.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_policy_auth.name IS '策略名称'; +COMMENT ON COLUMN databus_policy_auth.type IS '策略类型'; +COMMENT ON COLUMN databus_policy_auth.config IS '策略配置 JSON'; +COMMENT ON COLUMN databus_policy_auth.description IS '描述信息'; +COMMENT ON COLUMN databus_policy_auth.creator IS '创建者'; +COMMENT ON COLUMN databus_policy_auth.create_time IS '创建时间'; +COMMENT ON COLUMN databus_policy_auth.updater IS '更新者'; +COMMENT ON COLUMN databus_policy_auth.update_time IS '更新时间'; +COMMENT ON COLUMN databus_policy_auth.deleted IS '逻辑删除标记'; + +-- ---------------------------- +-- Table structure for databus_policy_rate_limit +-- ---------------------------- +CREATE TABLE databus_policy_rate_limit ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(64) NOT NULL, + config CLOB, + description VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE UNIQUE INDEX uk_databus_policy_rate_limit_name ON databus_policy_rate_limit (tenant_id, name); + +COMMENT ON TABLE databus_policy_rate_limit IS '统一外部 API 门户 - 限流策略表'; +COMMENT ON COLUMN databus_policy_rate_limit.id IS '主键 ID'; +COMMENT ON COLUMN databus_policy_rate_limit.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_policy_rate_limit.name IS '策略名称'; +COMMENT ON COLUMN databus_policy_rate_limit.type IS '策略类型'; +COMMENT ON COLUMN databus_policy_rate_limit.config IS '策略配置 JSON'; +COMMENT ON COLUMN databus_policy_rate_limit.description IS '描述信息'; +COMMENT ON COLUMN databus_policy_rate_limit.creator IS '创建者'; +COMMENT ON COLUMN databus_policy_rate_limit.create_time IS '创建时间'; +COMMENT ON COLUMN databus_policy_rate_limit.updater IS '更新者'; +COMMENT ON COLUMN databus_policy_rate_limit.update_time IS '更新时间'; +COMMENT ON COLUMN databus_policy_rate_limit.deleted IS '逻辑删除标记'; + +-- ---------------------------- +-- Table structure for databus_api_step +-- ---------------------------- +CREATE TABLE databus_api_step ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + api_id BIGINT NOT NULL, + step_order INT DEFAULT 0 NOT NULL, + parallel_group VARCHAR(64), + type VARCHAR(32) NOT NULL, + target_endpoint VARCHAR(512), + request_mapping_expr CLOB, + response_mapping_expr CLOB, + transform_id BIGINT, + timeout BIGINT, + retry_strategy CLOB, + fallback_strategy CLOB, + condition_expr CLOB, + stop_on_error BIT DEFAULT '0' NOT NULL, + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE INDEX idx_databus_api_step_api_order ON databus_api_step (tenant_id, api_id, parallel_group, step_order); + +COMMENT ON TABLE databus_api_step IS '统一外部 API 门户 - 编排步骤表'; +COMMENT ON COLUMN databus_api_step.id IS '主键 ID'; +COMMENT ON COLUMN databus_api_step.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_api_step.api_id IS '关联的 API ID'; +COMMENT ON COLUMN databus_api_step.step_order IS '执行顺序'; +COMMENT ON COLUMN databus_api_step.parallel_group IS '并行分组标识'; +COMMENT ON COLUMN databus_api_step.type IS '步骤类型'; +COMMENT ON COLUMN databus_api_step.target_endpoint IS '目标端点'; +COMMENT ON COLUMN databus_api_step.request_mapping_expr IS '请求映射表达式'; +COMMENT ON COLUMN databus_api_step.response_mapping_expr IS '响应映射表达式'; +COMMENT ON COLUMN databus_api_step.transform_id IS '默认变换 ID'; +COMMENT ON COLUMN databus_api_step.timeout IS '超时时间(毫秒)'; +COMMENT ON COLUMN databus_api_step.retry_strategy IS '重试策略 JSON'; +COMMENT ON COLUMN databus_api_step.fallback_strategy IS '降级策略 JSON'; +COMMENT ON COLUMN databus_api_step.condition_expr IS '执行条件表达式'; +COMMENT ON COLUMN databus_api_step.stop_on_error IS '出错是否终止'; +COMMENT ON COLUMN databus_api_step.creator IS '创建者'; +COMMENT ON COLUMN databus_api_step.create_time IS '创建时间'; +COMMENT ON COLUMN databus_api_step.updater IS '更新者'; +COMMENT ON COLUMN databus_api_step.update_time IS '更新时间'; +COMMENT ON COLUMN databus_api_step.deleted IS '逻辑删除标记'; + +-- ---------------------------- +-- Table structure for databus_api_transform +-- ---------------------------- +CREATE TABLE databus_api_transform ( + id BIGINT NOT NULL PRIMARY KEY, + tenant_id BIGINT NOT NULL, + api_id BIGINT, + step_id BIGINT, + phase VARCHAR(32) NOT NULL, + expression_type VARCHAR(32) NOT NULL, + expression CLOB NOT NULL, + description VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BIT DEFAULT '0' NOT NULL +); + +CREATE INDEX idx_databus_api_transform_api ON databus_api_transform (tenant_id, api_id); +CREATE INDEX idx_databus_api_transform_step ON databus_api_transform (tenant_id, step_id); + +COMMENT ON TABLE databus_api_transform IS '统一外部 API 门户 - 变换配置表'; +COMMENT ON COLUMN databus_api_transform.id IS '主键 ID'; +COMMENT ON COLUMN databus_api_transform.tenant_id IS '租户编号'; +COMMENT ON COLUMN databus_api_transform.api_id IS '关联的 API ID'; +COMMENT ON COLUMN databus_api_transform.step_id IS '关联的步骤 ID'; +COMMENT ON COLUMN databus_api_transform.phase IS '执行阶段'; +COMMENT ON COLUMN databus_api_transform.expression_type IS '表达式类型'; +COMMENT ON COLUMN databus_api_transform.expression IS '表达式内容'; +COMMENT ON COLUMN databus_api_transform.description IS '描述信息'; +COMMENT ON COLUMN databus_api_transform.creator IS '创建者'; +COMMENT ON COLUMN databus_api_transform.create_time IS '创建时间'; +COMMENT ON COLUMN databus_api_transform.updater IS '更新者'; +COMMENT ON COLUMN databus_api_transform.update_time IS '更新时间'; +COMMENT ON COLUMN databus_api_transform.deleted IS '逻辑删除标记'; diff --git a/sql/dm/规则引擎核心表结构_20251014.sql b/sql/dm/规则引擎核心表结构_20251014.sql new file mode 100644 index 00000000..0e734790 --- /dev/null +++ b/sql/dm/规则引擎核心表结构_20251014.sql @@ -0,0 +1,228 @@ +-- 规则引擎模块核心表结构(DM8) +-- 如果需要重建表,请在执行前备份现有数据 + +DROP TABLE IF EXISTS rule_release_record; +DROP TABLE IF EXISTS rule_business_relation; +DROP TABLE IF EXISTS rule_business; +DROP TABLE IF EXISTS rule_chain_dependency; +DROP TABLE IF EXISTS rule_chain; +DROP TABLE IF EXISTS rule_definition; + +CREATE TABLE rule_definition ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + code VARCHAR(128) NOT NULL, + name VARCHAR(128) NOT NULL, + type SMALLINT NOT NULL, + dsl CLOB, + script_language VARCHAR(64), + bean_ref VARCHAR(128), + config_json CLOB, + status SMALLINT DEFAULT 0 NOT NULL, + version VARCHAR(64), + remark VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_definition IS '规则定义表'; +COMMENT ON COLUMN rule_definition.id IS '规则定义编号'; +COMMENT ON COLUMN rule_definition.code IS '规则编码'; +COMMENT ON COLUMN rule_definition.name IS '规则名称'; +COMMENT ON COLUMN rule_definition.type IS '规则类型'; +COMMENT ON COLUMN rule_definition.dsl IS 'LiteFlow DSL 脚本'; +COMMENT ON COLUMN rule_definition.script_language IS '脚本语言'; +COMMENT ON COLUMN rule_definition.bean_ref IS 'Spring Bean 引用'; +COMMENT ON COLUMN rule_definition.config_json IS '规则配置 JSON'; +COMMENT ON COLUMN rule_definition.status IS '规则状态'; +COMMENT ON COLUMN rule_definition.version IS '规则版本号'; +COMMENT ON COLUMN rule_definition.remark IS '备注'; +COMMENT ON COLUMN rule_definition.creator IS '创建者'; +COMMENT ON COLUMN rule_definition.create_time IS '创建时间'; +COMMENT ON COLUMN rule_definition.updater IS '更新者'; +COMMENT ON COLUMN rule_definition.update_time IS '更新时间'; +COMMENT ON COLUMN rule_definition.deleted IS '是否删除'; +COMMENT ON COLUMN rule_definition.tenant_id IS '租户编号'; + +CREATE UNIQUE INDEX uk_rule_definition_code_tenant ON rule_definition (code, tenant_id); + +CREATE TABLE rule_chain ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + code VARCHAR(128) NOT NULL, + name VARCHAR(128) NOT NULL, + description VARCHAR(512), + structure_json CLOB, + liteflow_dsl CLOB, + status SMALLINT DEFAULT 0 NOT NULL, + version VARCHAR(64), + remark VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_chain IS '规则链表'; +COMMENT ON COLUMN rule_chain.id IS '规则链编号'; +COMMENT ON COLUMN rule_chain.code IS '规则链编码'; +COMMENT ON COLUMN rule_chain.name IS '规则链名称'; +COMMENT ON COLUMN rule_chain.description IS '规则链描述'; +COMMENT ON COLUMN rule_chain.structure_json IS '链路结构 JSON'; +COMMENT ON COLUMN rule_chain.liteflow_dsl IS 'LiteFlow DSL 内容'; +COMMENT ON COLUMN rule_chain.status IS '规则链状态'; +COMMENT ON COLUMN rule_chain.version IS '版本号'; +COMMENT ON COLUMN rule_chain.remark IS '备注'; +COMMENT ON COLUMN rule_chain.creator IS '创建者'; +COMMENT ON COLUMN rule_chain.create_time IS '创建时间'; +COMMENT ON COLUMN rule_chain.updater IS '更新者'; +COMMENT ON COLUMN rule_chain.update_time IS '更新时间'; +COMMENT ON COLUMN rule_chain.deleted IS '是否删除'; +COMMENT ON COLUMN rule_chain.tenant_id IS '租户编号'; + +CREATE UNIQUE INDEX uk_rule_chain_code_tenant ON rule_chain (code, tenant_id); +CREATE INDEX idx_rule_chain_status ON rule_chain (status); + +CREATE TABLE rule_chain_dependency ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + parent_chain_id BIGINT NOT NULL, + child_rule_id BIGINT NOT NULL, + link_type SMALLINT NOT NULL, + order_index INTEGER, + parallel_group VARCHAR(64), + condition_expr VARCHAR(512), + config_json CLOB, + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_chain_dependency IS '规则链依赖表'; +COMMENT ON COLUMN rule_chain_dependency.id IS '依赖编号'; +COMMENT ON COLUMN rule_chain_dependency.parent_chain_id IS '父规则链编号'; +COMMENT ON COLUMN rule_chain_dependency.child_rule_id IS '引用的规则定义编号'; +COMMENT ON COLUMN rule_chain_dependency.link_type IS '节点类型'; +COMMENT ON COLUMN rule_chain_dependency.order_index IS '执行顺序'; +COMMENT ON COLUMN rule_chain_dependency.parallel_group IS '并行组标识'; +COMMENT ON COLUMN rule_chain_dependency.condition_expr IS '条件表达式'; +COMMENT ON COLUMN rule_chain_dependency.config_json IS '节点配置 JSON'; +COMMENT ON COLUMN rule_chain_dependency.creator IS '创建者'; +COMMENT ON COLUMN rule_chain_dependency.create_time IS '创建时间'; +COMMENT ON COLUMN rule_chain_dependency.updater IS '更新者'; +COMMENT ON COLUMN rule_chain_dependency.update_time IS '更新时间'; +COMMENT ON COLUMN rule_chain_dependency.deleted IS '是否删除'; +COMMENT ON COLUMN rule_chain_dependency.tenant_id IS '租户编号'; + +CREATE INDEX idx_rule_chain_dependency_parent ON rule_chain_dependency (parent_chain_id); +CREATE INDEX idx_rule_chain_dependency_child ON rule_chain_dependency (child_rule_id); + +CREATE TABLE rule_business ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + business VARCHAR(128) NOT NULL, + rule_chain_id BIGINT, + override_strategy SMALLINT DEFAULT 0 NOT NULL, + locked TINYINT DEFAULT 0 NOT NULL, + effective_version VARCHAR(64), + config_json CLOB, + remark VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_business IS '业务规则绑定表'; +COMMENT ON COLUMN rule_business.id IS '业务绑定编号'; +COMMENT ON COLUMN rule_business.business IS '业务标识'; +COMMENT ON COLUMN rule_business.rule_chain_id IS '绑定的规则链编号'; +COMMENT ON COLUMN rule_business.override_strategy IS '覆盖策略'; +COMMENT ON COLUMN rule_business.locked IS '是否锁定'; +COMMENT ON COLUMN rule_business.effective_version IS '生效版本'; +COMMENT ON COLUMN rule_business.config_json IS '业务配置 JSON'; +COMMENT ON COLUMN rule_business.remark IS '备注'; +COMMENT ON COLUMN rule_business.creator IS '创建者'; +COMMENT ON COLUMN rule_business.create_time IS '创建时间'; +COMMENT ON COLUMN rule_business.updater IS '更新者'; +COMMENT ON COLUMN rule_business.update_time IS '更新时间'; +COMMENT ON COLUMN rule_business.deleted IS '是否删除'; +COMMENT ON COLUMN rule_business.tenant_id IS '租户编号'; + +CREATE UNIQUE INDEX uk_rule_business_tenant ON rule_business (business, tenant_id); +CREATE INDEX idx_rule_business_chain ON rule_business (rule_chain_id); + +CREATE TABLE rule_business_relation ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + parent_business VARCHAR(128) NOT NULL, + child_business VARCHAR(128) NOT NULL, + sort INTEGER DEFAULT 0 NOT NULL, + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_business_relation IS '业务继承关系表'; +COMMENT ON COLUMN rule_business_relation.id IS '继承关系编号'; +COMMENT ON COLUMN rule_business_relation.parent_business IS '父业务标识'; +COMMENT ON COLUMN rule_business_relation.child_business IS '子业务标识'; +COMMENT ON COLUMN rule_business_relation.sort IS '排序'; +COMMENT ON COLUMN rule_business_relation.creator IS '创建者'; +COMMENT ON COLUMN rule_business_relation.create_time IS '创建时间'; +COMMENT ON COLUMN rule_business_relation.updater IS '更新者'; +COMMENT ON COLUMN rule_business_relation.update_time IS '更新时间'; +COMMENT ON COLUMN rule_business_relation.deleted IS '是否删除'; +COMMENT ON COLUMN rule_business_relation.tenant_id IS '租户编号'; + +CREATE UNIQUE INDEX uk_rule_business_relation_child ON rule_business_relation (child_business, tenant_id); +CREATE INDEX idx_rule_business_relation_parent ON rule_business_relation (parent_business); + +CREATE TABLE rule_release_record ( + id BIGINT IDENTITY(1,1) PRIMARY KEY, + business VARCHAR(128) NOT NULL, + chain_id VARCHAR(255) NOT NULL, + chain_code VARCHAR(128), + version VARCHAR(64) NOT NULL, + status SMALLINT DEFAULT 0 NOT NULL, + release_user_id BIGINT, + release_user_name VARCHAR(128), + release_time TIMESTAMP, + remark VARCHAR(512), + creator VARCHAR(64) DEFAULT '' NOT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '' NOT NULL, + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted TINYINT DEFAULT 0 NOT NULL, + tenant_id BIGINT DEFAULT 0 NOT NULL +); + +COMMENT ON TABLE rule_release_record IS '规则发布记录表'; +COMMENT ON COLUMN rule_release_record.id IS '发布记录编号'; +COMMENT ON COLUMN rule_release_record.business IS '业务标识'; +COMMENT ON COLUMN rule_release_record.chain_id IS '生成链路标识'; +COMMENT ON COLUMN rule_release_record.chain_code IS '规则链编码'; +COMMENT ON COLUMN rule_release_record.version IS '发布版本'; +COMMENT ON COLUMN rule_release_record.status IS '发布状态'; +COMMENT ON COLUMN rule_release_record.release_user_id IS '发布人编号'; +COMMENT ON COLUMN rule_release_record.release_user_name IS '发布人名称'; +COMMENT ON COLUMN rule_release_record.release_time IS '发布时间'; +COMMENT ON COLUMN rule_release_record.remark IS '备注'; +COMMENT ON COLUMN rule_release_record.creator IS '创建者'; +COMMENT ON COLUMN rule_release_record.create_time IS '创建时间'; +COMMENT ON COLUMN rule_release_record.updater IS '更新者'; +COMMENT ON COLUMN rule_release_record.update_time IS '更新时间'; +COMMENT ON COLUMN rule_release_record.deleted IS '是否删除'; +COMMENT ON COLUMN rule_release_record.tenant_id IS '租户编号'; + +CREATE INDEX idx_rule_release_business_time ON rule_release_record (business, release_time); +CREATE UNIQUE INDEX uk_rule_release_business_version ON rule_release_record (business, version, tenant_id); diff --git a/sql/dm/规则引擎菜单初始化_20251014.sql b/sql/dm/规则引擎菜单初始化_20251014.sql new file mode 100644 index 00000000..095f64d2 --- /dev/null +++ b/sql/dm/规则引擎菜单初始化_20251014.sql @@ -0,0 +1,27 @@ +-- 规则引擎模块菜单与权限初始化(DM8) +-- 顶级目录放置在系统管理(2)下,如需调整请修改 parent_id + +DELETE FROM system_menu WHERE id IN (6100,6101,610101,610102,610103,610104,610111,610112,610113,610114,610121,610122,610123,610124,610131,610132,610133,610141); + +INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES +(6100, '规则引擎', '', 1, 20, 2, 'rule', 'ep:s-operation', '', 'RuleModule', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(6101, '规则设计器', '', 2, 1, 6100, 'designer', 'ep:s-operation', 'rule/designer/index', 'RuleDesigner', 0, '1', '0', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'); + +INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES +(610101, '规则定义查询', 'rule:definition:query', 3, 1, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610102, '规则定义创建', 'rule:definition:create', 3, 2, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610103, '规则定义更新', 'rule:definition:update', 3, 3, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610104, '规则定义删除', 'rule:definition:delete', 3, 4, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610111, '规则链查询', 'rule:chain:query', 3, 5, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610112, '规则链创建', 'rule:chain:create', 3, 6, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610113, '规则链更新', 'rule:chain:update', 3, 7, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610114, '规则链删除', 'rule:chain:delete', 3, 8, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610121, '业务链查询', 'rule:business:query', 3, 9, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610122, '业务链维护', 'rule:business:update', 3, 10, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610123, '业务链删除', 'rule:business:delete', 3, 11, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610124, '业务链预览', 'rule:business:preview', 3, 12, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610131, '发布记录查询', 'rule:publish:query', 3, 13, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610132, '规则链发布', 'rule:publish:publish', 3, 14, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610133, '规则链回滚', 'rule:publish:rollback', 3, 15, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'), +(610141, '规则模拟执行', 'rule:simulation:execute', 3, 16, 6101, '', '', '', '', 0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'); diff --git a/sql/dm/角色权限剔除表.sql b/sql/dm/角色权限剔除表_20250623.sql similarity index 100% rename from sql/dm/角色权限剔除表.sql rename to sql/dm/角色权限剔除表_20250623.sql diff --git a/sql/dm/部门外部组织编码映射初始化_DM8.sql b/sql/dm/部门外部组织编码映射初始化_DM8.sql new file mode 100644 index 00000000..a1a2ae86 --- /dev/null +++ b/sql/dm/部门外部组织编码映射初始化_DM8.sql @@ -0,0 +1,60 @@ +-- DM8 部门外部组织编码映射初始化脚本 +-- 包含表结构、字段注释及基础字典数据 + +-- 重复执行时请先备份数据 +DROP TABLE IF EXISTS system_dept_external_code; + +CREATE TABLE system_dept_external_code ( + id BIGINT NOT NULL, + dept_id BIGINT NOT NULL, + system_code VARCHAR(64) NOT NULL, + external_dept_code VARCHAR(128) NOT NULL, + external_dept_name VARCHAR(255), + status TINYINT DEFAULT 0 NOT NULL, + remark VARCHAR(512), + tenant_id BIGINT DEFAULT 0, + creator VARCHAR(64), + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64), + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + deleted TINYINT DEFAULT 0 NOT NULL, + CONSTRAINT pk_system_dept_external_code PRIMARY KEY (id) +); + +-- 唯一索引与辅助索引 +CREATE UNIQUE INDEX uk_system_dept_external_code_ext + ON system_dept_external_code (tenant_id, system_code, external_dept_code); +CREATE UNIQUE INDEX uk_system_dept_external_code_dept + ON system_dept_external_code (tenant_id, system_code, dept_id); +CREATE INDEX idx_system_dept_external_code_dept + ON system_dept_external_code (tenant_id, dept_id); + +COMMENT ON TABLE system_dept_external_code IS '部门外部组织编码映射'; +COMMENT ON COLUMN system_dept_external_code.id IS '主键编号'; +COMMENT ON COLUMN system_dept_external_code.dept_id IS '本系统部门编号'; +COMMENT ON COLUMN system_dept_external_code.system_code IS '外部系统标识'; +COMMENT ON COLUMN system_dept_external_code.external_dept_code IS '外部组织编码'; +COMMENT ON COLUMN system_dept_external_code.external_dept_name IS '外部组织名称'; +COMMENT ON COLUMN system_dept_external_code.status IS '状态(0开启 1关闭)'; +COMMENT ON COLUMN system_dept_external_code.remark IS '备注'; +COMMENT ON COLUMN system_dept_external_code.tenant_id IS '租户编号'; +COMMENT ON COLUMN system_dept_external_code.creator IS '创建者'; +COMMENT ON COLUMN system_dept_external_code.create_time IS '创建时间'; +COMMENT ON COLUMN system_dept_external_code.updater IS '更新者'; +COMMENT ON COLUMN system_dept_external_code.update_time IS '更新时间'; +COMMENT ON COLUMN system_dept_external_code.deleted IS '删除标记'; + +-- 初始化外部系统标识字典 +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted) +SELECT 20050, '外部系统标识', 'system_dept_external_system', 0, '部门外部组织编码中的外部系统标识', 'admin', SYSDATE, 'admin', SYSDATE, 0 +FROM dual +WHERE NOT EXISTS ( + SELECT 1 FROM system_dict_type WHERE type = 'system_dept_external_system' +); + +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) +SELECT 2005001, 1, 'ERP 系统', 'ERP', 'system_dept_external_system', 0, '', '', '企业资源计划系统', 'admin', SYSDATE, 'admin', SYSDATE, 0 +FROM dual +WHERE NOT EXISTS ( + SELECT 1 FROM system_dict_data WHERE dict_type = 'system_dept_external_system' AND value = 'ERP' +); diff --git a/sql/dm/部门外部组织编码映射菜单权限_DM8.sql b/sql/dm/部门外部组织编码映射菜单权限_DM8.sql new file mode 100644 index 00000000..4c726aff --- /dev/null +++ b/sql/dm/部门外部组织编码映射菜单权限_DM8.sql @@ -0,0 +1,35 @@ +-- DM8 部门外部组织编码映射菜单与权限脚本 +-- 清理旧数据并重新创建目录、页面及操作按钮 + +-- 保持脚本幂等性,先清理旧数据 +DELETE FROM system_role_menu WHERE menu_id IN (6200, 6201, 620101, 620102, 620103, 620104); +DELETE FROM system_menu WHERE id IN (6200, 6201, 620101, 620102, 620103, 620104); + +-- 在系统管理(ID=2)下创建目录与页面 +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status, component_name +) VALUES ( + 6200, '组织编码映射', '', 1, 25, 2, + 'dept-external', 'ep:connection', '', 0, 'DeptExternalCodeRoot' +); + +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status, component_name +) VALUES ( + 6201, '外部组织编码', '', 2, 1, 6200, + 'dept-external-code', 'ep:connection', 'system/deptExternalCode/index', 0, 'SystemDeptExternalCode' +); + +-- 创建操作按钮权限 +INSERT INTO system_menu ( + id, name, permission, type, sort, parent_id, + path, icon, component, status +) VALUES + (620101, '查询部门外部编码', 'system:dept-external-code:query', 3, 1, 6201, '', '', '', 0), + (620102, '新增部门外部编码', 'system:dept-external-code:create', 3, 2, 6201, '', '', '', 0), + (620103, '修改部门外部编码', 'system:dept-external-code:update', 3, 3, 6201, '', '', '', 0), + (620104, '删除部门外部编码', 'system:dept-external-code:delete', 3, 4, 6201, '', '', '', 0); + +-- 如需分配给角色,请按本地序列策略写入 system_role_menu diff --git a/sql/dm/部门添加编码、简称字段.sql b/sql/dm/部门添加编码、简称字段_20250918.sql similarity index 100% rename from sql/dm/部门添加编码、简称字段.sql rename to sql/dm/部门添加编码、简称字段_20250918.sql diff --git a/sql/mysql/databus_sample_data.sql b/sql/mysql/databus_sample_data.sql new file mode 100644 index 00000000..260b66ee --- /dev/null +++ b/sql/mysql/databus_sample_data.sql @@ -0,0 +1,65 @@ +START TRANSACTION; + +-- Cleanup previous sample records by identifier range +DELETE FROM databus_api_transform WHERE id BETWEEN 520100000000000000 AND 520100000000000999; +DELETE FROM databus_api_step WHERE id BETWEEN 610100000000000000 AND 610100000000000999; +DELETE FROM databus_api_definition WHERE id BETWEEN 410100000000000000 AND 410100000000000999; +DELETE FROM databus_policy_auth WHERE id BETWEEN 110100000000000000 AND 110100000000000999; +DELETE FROM databus_policy_rate_limit WHERE id BETWEEN 210100000000000000 AND 210100000000000999; +DELETE FROM databus_api_flow_publish WHERE id BETWEEN 710100000000000000 AND 710100000000000999; + +-- Authentication policies aligned with DefaultAuthPolicyEvaluator header strategy +INSERT INTO databus_policy_auth + (id, tenant_id, name, type, config, description, creator, create_time, updater, update_time, deleted) +VALUES + (110100000000000001, 1, '统一 Token 校验', 'HEADER_TOKEN', '{"allowedTokens":[]}', '通过 ZT-Auth-Token 传递访问凭证', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (110100000000000002, 1, '内部服务白名单', 'INTERNAL_TRUSTED', '{"allowedTokens":["system-server","databus-server"]}', '内部系统间调用的白名单策略', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (110100000000000003, 1, '调试临时凭证', 'HEADER_TOKEN', '{"allowedTokens":["debug-token"]}', '用于灰度测试的临时凭证', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0); + +-- Rate limit policies compatible with DefaultRateLimitPolicyEvaluator +INSERT INTO databus_policy_rate_limit + (id, tenant_id, name, type, config, description, creator, create_time, updater, update_time, deleted) +VALUES + (210100000000000001, 1, '公共查询 120 RPM', 'FIXED_WINDOW', '{"limit":120,"windowSeconds":60}', '公共查询接口的分钟级限流', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (210100000000000002, 1, '用户画像 30 RPM', 'FIXED_WINDOW', '{"limit":30,"windowSeconds":60}', '用户画像聚合接口的限流策略', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (210100000000000003, 1, '登录试用 60 RPM', 'FIXED_WINDOW', '{"limit":60,"windowSeconds":60}', '测试登录能力的限流', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0); + +-- API definitions referencing real system modules +INSERT INTO databus_api_definition + (id, tenant_id, api_code, uri_pattern, http_method, version, status, description, auth_policy_id, rate_limit_id, response_template, cache_strategy, updated_at, grey_released, creator, create_time, updater, update_time, deleted) +VALUES + (410100000000000001, 1, 'system.lookup.bundle', '/external/system/lookup-bundle', 'GET', 'v1', 1, '聚合系统用户、部门、字典精简列表的只读接口', 110100000000000001, 210100000000000001, '{"code":0,"message":"success","data":{}}', '{"provider":"redis","ttlSeconds":120,"cacheKey":"system:lookup:bundle"}', CURRENT_TIMESTAMP, 0, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (410100000000000002, 1, 'system.user.profile.aggregate', '/external/system/user/profile', 'POST', 'v1', 1, '根据 userId 聚合后台用户、角色及部门信息', 110100000000000001, 210100000000000002, '{"code":0,"message":"success","data":{}}', NULL, CURRENT_TIMESTAMP, 0, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (410100000000000003, 1, 'system.auth.quick-login', '/external/system/auth/quick-login', 'POST', 'v1', 1, '调用测试登录接口并返回用户基础画像', 110100000000000001, 210100000000000003, '{"code":0,"message":"success","data":{}}', NULL, CURRENT_TIMESTAMP, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0); + +-- API level transforms +INSERT INTO databus_api_transform + (id, tenant_id, api_id, step_id, phase, expression_type, expression, description, creator, create_time, updater, update_time, deleted) +VALUES + (520100000000000101, 1, 410100000000000001, NULL, 'REQUEST_PRE', 'JSON', '($trace := $ctx.requestHeaders."X-Trace-Id"; {"requestHeaders": {"X-Trace-Id": $trace ? $trace : $uuid()}})', '自动补全链路追踪 ID', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (520100000000000102, 1, 410100000000000001, NULL, 'RESPONSE_PRE', 'JSON', '{"responseBody": {"users": $vars.users ? $vars.users : [], "departments": $vars.departments ? $vars.departments : [], "dicts": $vars.dicts ? $vars.dicts : []}}', '组装统一响应结构', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (520100000000000103, 1, 410100000000000002, NULL, 'RESPONSE_PRE', 'JSON', '{"responseBody": {"user": $vars.user, "roleIds": $vars.roleIds ? $vars.roleIds : [], "roles": $vars.roles ? $vars.roles : [], "departments": $vars.departments ? $vars.departments : []}}', '聚合用户详情返回体', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (520100000000000104, 1, 410100000000000003, NULL, 'RESPONSE_PRE', 'JSON', '{"responseBody": {"tokens": $vars.tokens, "loginUser": $vars.loginUser, "companyDept": $vars.companyDept ? $vars.companyDept : []}}', '组合测试登录返回体', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0); + +-- API orchestration steps referencing real HTTP endpoints and Spring beans +INSERT INTO databus_api_step + (id, tenant_id, api_id, step_order, parallel_group, type, target_endpoint, request_mapping_expr, response_mapping_expr, transform_id, timeout, retry_strategy, fallback_strategy, condition_expr, stop_on_error, creator, create_time, updater, update_time, deleted) +VALUES + (610100000000000201, 1, 410100000000000001, 1, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/user/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/user/list-all-simple 失败: " & $.msg) : {"users": $.data ? $.data : []})', NULL, 5000, '{"maxAttempts":2,"delayMs":200}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000202, 1, 410100000000000001, 2, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/dept/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/dept/list-all-simple 失败: " & $.msg) : {"departments": $.data ? $.data : []})', NULL, 5000, '{"maxAttempts":2,"delayMs":200}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000203, 1, 410100000000000001, 3, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/dict-data/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/dict-data/list-all-simple 失败: " & $.msg) : {"dicts": $.data ? $.data : []})', NULL, 5000, '{"maxAttempts":2,"delayMs":200}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000204, 1, 410100000000000002, 1, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/user/get', 'JSON::{"id": $.userId}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/user/get 失败: " & $.msg) : {"user": $.data})', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000205, 1, 410100000000000002, 2, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/permission/list-user-roles', 'JSON::($user := $vars.user; {"userId": $user ? $user.id : null})', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/permission/list-user-roles 失败: " & $.msg) : ($data := $.data ? $.data : []; {"roleIds": $data.($string($))}))', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000206, 1, 410100000000000002, 3, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/role/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/role/list-all-simple 失败: " & $.msg) : ($data := $.data ? $.data : []; {"roles": $data[$contains($vars.roleIds, $string(id))]}))', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000207, 1, 410100000000000002, 4, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/dept/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/dept/list-all-simple 失败: " & $.msg) : ($data := $.data ? $.data : []; $deptIds := $vars.user.deptIds ? $vars.user.deptIds.($string($)) : []; {"departments": $deptIds ? $data[$contains($deptIds, $string(id))] : []}))', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000208, 1, 410100000000000003, 1, NULL, 'HTTP', 'POST http://127.0.0.1:48080/admin-api/system/auth/test-login', 'JSON::{"username": $.username, "password": $.password}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/auth/test-login 失败: " & $.msg) : {"tokens": $.data})', NULL, 5000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000209, 1, 410100000000000003, 2, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/user/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/user/list-all-simple 失败: " & $.msg) : ($data := $.data ? $.data : []; {"loginUser": $data[$string(id) = $string($vars.tokens.userId)][0]}))', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + (610100000000000210, 1, 410100000000000003, 3, NULL, 'HTTP', 'GET http://127.0.0.1:48080/admin-api/system/dept/list-all-simple', 'JSON::{}', 'JSON::($.code != 0 ? $error("调用 /admin-api/system/dept/list-all-simple 失败: " & $.msg) : ($data := $.data ? $.data : []; {"companyDept": $vars.loginUser and $vars.loginUser.deptId ? $data[$string(id) = $string($vars.loginUser.deptId)] : []}))', NULL, 3000, '{"maxAttempts":1}', NULL, NULL, 1, 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0), + +-- Optional: publish record to illustrate version management +INSERT INTO databus_api_flow_publish + (id, tenant_id, api_id, release_tag, snapshot, status, active, description, creator, create_time, updater, update_time, deleted) +VALUES + (710100000000000001, 1, 410100000000000001, '2025.10.01-INIT', '{"definitionId":410100000000000001,"version":"v1","steps":3}', 'SUCCESS', 1, '初始发布 system.lookup.bundle 接口', 'sample_loader', CURRENT_TIMESTAMP, 'sample_loader', CURRENT_TIMESTAMP, 0); + +COMMIT; diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py index 1ea0f629..5b5be6dd 100644 --- a/sql/tools/convertor.py +++ b/sql/tools/convertor.py @@ -837,7 +837,7 @@ def main(): ) args = parser.parse_args() - sql_file = pathlib.Path("../mysql/在线文档管理表结构.sql").resolve().as_posix() + sql_file = pathlib.Path("../mysql/在线文档管理表结构_20250901.sql").resolve().as_posix() convertor = None if args.type == "postgres": convertor = PostgreSQLConvertor(sql_file) diff --git a/zt-gateway/src/main/resources/application.yaml b/zt-gateway/src/main/resources/application.yaml index 8414b41c..2d837261 100644 --- a/zt-gateway/src/main/resources/application.yaml +++ b/zt-gateway/src/main/resources/application.yaml @@ -186,6 +186,13 @@ spring: - Path=/admin-api/crm/** filters: - RewritePath=/admin-api/crm/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs + ## rule-server 服务 + - id: rule-admin-api # 路由的编号 + uri: grayLb://rule-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/rule/** + filters: + - RewritePath=/admin-api/rule/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs ## ai-server 服务 - id: ai-admin-api # 路由的编号 uri: grayLb://ai-server @@ -207,6 +214,13 @@ spring: - Path=/admin-api/template/** filters: - RewritePath=/admin-api/template/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs + ## databus-server 服务 + - id: databus-admin-api # 路由的编号 + uri: grayLb://databus-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/databus/** + filters: + - RewritePath=/admin-api/databus/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs x-forwarded: prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 @@ -258,6 +272,9 @@ knife4j: - name: crm-server service-name: crm-server url: /admin-api/crm/v3/api-docs + - name: rule-server + service-name: rule-server + url: /admin-api/rule/v3/api-docs - name: ai-server service-name: ai-server url: /admin-api/ai/v3/api-docs @@ -267,6 +284,9 @@ knife4j: - name: template-server service-name: template-server url: /admin-api/template/v3/api-docs + - name: databus-server + service-name: databus-server + url: /admin-api/databus/v3/api-docs --- #################### 芋道相关配置 #################### diff --git a/zt-module-databus/zt-module-databus-server/pom.xml b/zt-module-databus/zt-module-databus-server/pom.xml index 79a93f1f..0d51180c 100644 --- a/zt-module-databus/zt-module-databus-server/pom.xml +++ b/zt-module-databus/zt-module-databus-server/pom.xml @@ -126,6 +126,63 @@ zt-spring-boot-starter-biz-business ${revision} + + + + org.springframework.integration + spring-integration-http + + + org.springframework.integration + spring-integration-core + + + org.springframework.integration + spring-integration-scripting + + + org.springframework.retry + spring-retry + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + com.ibm.jsonata4java + JSONata4Java + 2.5.5 + + + com.github.ben-manes.caffeine + caffeine + + + org.mvel + mvel2 + 2.5.2.Final + + + commons-codec + commons-codec + + + + + org.springframework.integration + spring-integration-test + test + + + com.squareup.okhttp3 + mockwebserver + 4.12.0 + test + diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/databus/DatabusController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/databus/DatabusController.java deleted file mode 100644 index 742245d0..00000000 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/databus/DatabusController.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.zt.plat.module.databus.controller.admin.databus; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.zt.plat.framework.common.pojo.CommonResult; - -import static com.zt.plat.framework.common.pojo.CommonResult.success; - -/** - * Databus 控制器 - * - * @author ZT - */ -@Tag(name = "管理后台 - Databus") -@RestController -@RequestMapping("/admin/databus/databus") -public class DatabusController { - - @GetMapping("/hello") - @Operation(summary = "Hello Databus") - public CommonResult hello() { - return success("Hello, Databus!"); - } - -} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiDefinitionController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiDefinitionController.java new file mode 100644 index 00000000..b8c0d28c --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiDefinitionController.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.databus.controller.admin.gateway; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSummaryRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.framework.integration.gateway.core.IntegrationFlowManager; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.service.gateway.ApiDefinitionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_DEFINITION_NOT_FOUND; + +@Tag(name = "管理后台 - API 定义管理") +@RestController +@RequestMapping("/databus/gateway/definition") +@RequiredArgsConstructor +@Validated +public class ApiDefinitionController { + + private final ApiDefinitionService apiDefinitionService; + private final IntegrationFlowManager integrationFlowManager; + + @GetMapping("/page") + @Operation(summary = "分页查询 API 定义") + public CommonResult> getDefinitionPage(@Valid ApiDefinitionPageReqVO reqVO) { + PageResult pageResult = apiDefinitionService.getPage(reqVO); + return success(ApiDefinitionConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/{id}") + @Operation(summary = "获取 API 定义详情") + public CommonResult getDefinition(@PathVariable("id") Long id) { + ApiDefinitionAggregate aggregate = apiDefinitionService.findById(id) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND)); + return success(ApiDefinitionConvert.INSTANCE.convert(aggregate)); + } + + @PostMapping + @Operation(summary = "创建 API 定义") + public CommonResult createDefinition(@Valid @RequestBody ApiDefinitionSaveReqVO reqVO) { + Long id = apiDefinitionService.create(reqVO); + integrationFlowManager.refresh(reqVO.getApiCode(), reqVO.getVersion()); + return success(id); + } + + @PutMapping + @Operation(summary = "更新 API 定义") + public CommonResult updateDefinition(@Valid @RequestBody ApiDefinitionSaveReqVO reqVO) { + ApiDefinitionAggregate before = apiDefinitionService.findById(reqVO.getId()) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND)); + apiDefinitionService.update(reqVO); + integrationFlowManager.refresh(before.getDefinition().getApiCode(), before.getDefinition().getVersion()); + integrationFlowManager.refresh(reqVO.getApiCode(), reqVO.getVersion()); + return success(Boolean.TRUE); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除 API 定义") + public CommonResult deleteDefinition(@PathVariable("id") Long id) { + ApiDefinitionAggregate aggregate = apiDefinitionService.findById(id) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND)); + apiDefinitionService.delete(id); + integrationFlowManager.refresh(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion()); + return success(Boolean.TRUE); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java new file mode 100644 index 00000000..e7a44307 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiGatewayController.java @@ -0,0 +1,107 @@ +package com.zt.plat.module.databus.controller.admin.gateway; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert; +import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO; +import com.zt.plat.module.databus.framework.integration.gateway.core.ApiFlowDispatcher; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.service.gateway.ApiDefinitionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - API 门户") +@RestController +@RequestMapping("/databus/gateway") +@RequiredArgsConstructor +public class ApiGatewayController { + + private final ApiFlowDispatcher apiFlowDispatcher; + private final ApiDefinitionService apiDefinitionService; + + @PostMapping(value = "/invoke", consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "测试调用 API 编排") + public CommonResult invoke(@RequestBody ApiGatewayInvokeReqVO reqVO) { + ApiInvocationContext context = ApiInvocationContext.create(); + context.setApiCode(reqVO.getApiCode()); + context.setApiVersion(reqVO.getVersion()); + context.setRequestBody(reqVO.getPayload()); + if (reqVO.getHeaders() != null) { + context.getRequestHeaders().putAll(reqVO.getHeaders()); + } + if (reqVO.getQueryParams() != null) { + context.getRequestQueryParams().putAll(reqVO.getQueryParams()); + } + + ApiInvocationContext responseContext = context; + try { + responseContext = apiFlowDispatcher.dispatch(reqVO.getApiCode(), reqVO.getVersion(), context); + } catch (ServiceException ex) { + handleServiceException(responseContext, ex); + } catch (Exception ex) { + handleUnexpectedException(responseContext, ex); + } + + int status = responseContext.getResponseStatus() != null ? responseContext.getResponseStatus() : HttpStatus.OK.value(); + String message = StringUtils.hasText(responseContext.getResponseMessage()) + ? responseContext.getResponseMessage() + : HttpStatus.valueOf(status).getReasonPhrase(); + + ApiGatewayResponse envelope = ApiGatewayResponse.builder() + .code(status >= 200 && status < 400 ? "SUCCESS" : "ERROR") + .message(message) + .data(responseContext.getResponseBody()) + .traceId(responseContext.getRequestId()) + .build(); + return success(envelope); + } + + @GetMapping("/definitions") + @Operation(summary = "获取当前已发布 API 配置") + public CommonResult> listDefinitions() { + List definitions = apiDefinitionService.loadActiveDefinitions().stream() + .map(ApiDefinitionConvert.INSTANCE::convert) + .collect(Collectors.toList()); + return success(definitions); + } + + private void handleServiceException(ApiInvocationContext context, ServiceException ex) { + String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API 调用失败"; + context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + context.setResponseMessage(message); + Map body = new HashMap<>(); + if (ex.getCode() != null) { + body.put("errorCode", ex.getCode()); + } + body.put("errorMessage", message); + context.setResponseBody(body); + } + + private void handleUnexpectedException(ApiInvocationContext context, Exception ex) { + String message = StringUtils.hasText(ex.getMessage()) + ? ex.getMessage() + : ex.getCause() != null && StringUtils.hasText(ex.getCause().getMessage()) + ? ex.getCause().getMessage() + : "API invocation encountered an unexpected error"; + context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + context.setResponseMessage(message); + Map body = new HashMap<>(); + body.put("errorMessage", message); + body.put("exception", ex.getClass().getSimpleName()); + context.setResponseBody(body); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java new file mode 100644 index 00000000..9547c46b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyAuthController.java @@ -0,0 +1,84 @@ +package com.zt.plat.module.databus.controller.admin.gateway; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiPolicyAuthConvert; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.service.gateway.ApiPolicyAuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND; + +@Tag(name = "管理后台 - 网关认证策略") +@RestController +@RequestMapping("/databus/gateway/policy/auth") +@RequiredArgsConstructor +@Validated +public class ApiPolicyAuthController { + + private final ApiPolicyAuthService authService; + + @GetMapping("/page") + @Operation(summary = "分页查询认证策略") + public CommonResult> getAuthPolicyPage(@Valid ApiPolicyPageReqVO reqVO) { + PageResult pageResult = authService.getPage(reqVO); + return success(ApiPolicyAuthConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/{id}") + @Operation(summary = "查询认证策略详情") + public CommonResult getAuthPolicy(@PathVariable("id") Long id) { + ApiPolicyAuthDO policy = authService.get(id) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND)); + return success(ApiPolicyAuthConvert.INSTANCE.convert(policy)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获取认证策略精简列表") + public CommonResult> getAuthPolicySimpleList() { + List list = authService.getSimpleList(); + return success(ApiPolicyAuthConvert.INSTANCE.convertSimpleList(list)); + } + + @PostMapping + @Operation(summary = "创建认证策略") + public CommonResult createAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) { + Long id = authService.create(reqVO); + return success(id); + } + + @PutMapping + @Operation(summary = "更新认证策略") + public CommonResult updateAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) { + authService.update(reqVO); + return success(Boolean.TRUE); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除认证策略") + public CommonResult deleteAuthPolicy(@PathVariable("id") Long id) { + authService.delete(id); + return success(Boolean.TRUE); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyRateLimitController.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyRateLimitController.java new file mode 100644 index 00000000..6c6e06a1 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/ApiPolicyRateLimitController.java @@ -0,0 +1,84 @@ +package com.zt.plat.module.databus.controller.admin.gateway; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiPolicyRateLimitConvert; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import com.zt.plat.module.databus.service.gateway.ApiPolicyRateLimitService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND; + +@Tag(name = "管理后台 - 网关限流策略") +@RestController +@RequestMapping("/databus/gateway/policy/rate-limit") +@RequiredArgsConstructor +@Validated +public class ApiPolicyRateLimitController { + + private final ApiPolicyRateLimitService rateLimitService; + + @GetMapping("/page") + @Operation(summary = "分页查询限流策略") + public CommonResult> getRateLimitPolicyPage(@Valid ApiPolicyPageReqVO reqVO) { + PageResult pageResult = rateLimitService.getPage(reqVO); + return success(ApiPolicyRateLimitConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/{id}") + @Operation(summary = "查询限流策略详情") + public CommonResult getRateLimitPolicy(@PathVariable("id") Long id) { + ApiPolicyRateLimitDO policy = rateLimitService.get(id) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND)); + return success(ApiPolicyRateLimitConvert.INSTANCE.convert(policy)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获取限流策略精简列表") + public CommonResult> getRateLimitPolicySimpleList() { + List list = rateLimitService.getSimpleList(); + return success(ApiPolicyRateLimitConvert.INSTANCE.convertSimpleList(list)); + } + + @PostMapping + @Operation(summary = "创建限流策略") + public CommonResult createRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) { + Long id = rateLimitService.create(reqVO); + return success(id); + } + + @PutMapping + @Operation(summary = "更新限流策略") + public CommonResult updateRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) { + rateLimitService.update(reqVO); + return success(Boolean.TRUE); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除限流策略") + public CommonResult deleteRateLimitPolicy(@PathVariable("id") Long id) { + rateLimitService.delete(id); + return success(Boolean.TRUE); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java new file mode 100644 index 00000000..8725b204 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiDefinitionConvert.java @@ -0,0 +1,104 @@ +package com.zt.plat.module.databus.controller.admin.gateway.convert; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionDetailRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPublicationRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSummaryRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransformDefinition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Mapper +public interface ApiDefinitionConvert { + + ApiDefinitionConvert INSTANCE = Mappers.getMapper(ApiDefinitionConvert.class); + + ApiDefinitionSummaryRespVO convert(ApiDefinitionDO bean); + + List convertList(List list); + + default PageResult convertPage(PageResult page) { + if (page == null) { + return PageResult.empty(); + } + PageResult result = new PageResult<>(); + List list = convertList(page.getList()); + result.setList(list == null ? new ArrayList<>() : list); + result.setTotal(page.getTotal()); + return result; + } + + default ApiDefinitionDetailRespVO convert(ApiDefinitionAggregate aggregate) { + if (aggregate == null) { + return null; + } + ApiDefinitionDetailRespVO detail = BeanUtils.toBean(aggregate.getDefinition(), ApiDefinitionDetailRespVO.class); + detail.setApiLevelTransforms(convertTransforms(aggregate.getDefinition().getId(), aggregate.getApiLevelTransforms().values())); + detail.setSteps(convertSteps(aggregate.getSteps())); + detail.setPublication(convert(aggregate.getPublication())); + return detail; + } + + default List convertSteps(List steps) { + if (CollUtil.isEmpty(steps)) { + return new ArrayList<>(); + } + return steps.stream() + .sorted(Comparator.comparing(step -> step.getStep().getStepOrder() == null ? Integer.MAX_VALUE : step.getStep().getStepOrder())) + .map(step -> { + ApiDefinitionStepRespVO resp = BeanUtils.toBean(step.getStep(), ApiDefinitionStepRespVO.class); + resp.setTransforms(convertStepTransforms(step.getStep().getApiId(), step.getStep().getId(), step.getTransforms())); + return resp; + }) + .collect(Collectors.toList()); + } + + default List convertTransforms(Long apiId, Collection transforms) { + if (CollUtil.isEmpty(transforms)) { + return new ArrayList<>(); + } + return transforms.stream() + .sorted(Comparator.comparing(ApiTransformDefinition::getPhase, Comparator.nullsLast(String::compareTo))) + .map(transform -> { + ApiDefinitionTransformRespVO resp = BeanUtils.toBean(transform, ApiDefinitionTransformRespVO.class); + resp.setApiId(apiId); + resp.setStepId(null); + return resp; + }) + .collect(Collectors.toList()); + } + + default List convertStepTransforms(Long apiId, Long stepId, List transforms) { + if (CollUtil.isEmpty(transforms)) { + return new ArrayList<>(); + } + return transforms.stream() + .sorted(Comparator.comparing(ApiTransformDefinition::getPhase, Comparator.nullsLast(String::compareTo))) + .map(transform -> { + ApiDefinitionTransformRespVO resp = BeanUtils.toBean(transform, ApiDefinitionTransformRespVO.class); + resp.setApiId(apiId); + resp.setStepId(stepId); + return resp; + }) + .collect(Collectors.toList()); + } + + default ApiDefinitionPublicationRespVO convert(ApiFlowPublication publication) { + return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java new file mode 100644 index 00000000..3220d221 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyAuthConvert.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.controller.admin.gateway.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ApiPolicyAuthConvert { + + ApiPolicyAuthConvert INSTANCE = Mappers.getMapper(ApiPolicyAuthConvert.class); + + ApiPolicyRespVO convert(ApiPolicyAuthDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertSimpleList(List list); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyRateLimitConvert.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyRateLimitConvert.java new file mode 100644 index 00000000..8149b1c5 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/convert/ApiPolicyRateLimitConvert.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.controller.admin.gateway.convert; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyRespVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySimpleRespVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ApiPolicyRateLimitConvert { + + ApiPolicyRateLimitConvert INSTANCE = Mappers.getMapper(ApiPolicyRateLimitConvert.class); + + ApiPolicyRespVO convert(ApiPolicyRateLimitDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertSimpleList(List list); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/ApiGatewayInvokeReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/ApiGatewayInvokeReqVO.java new file mode 100644 index 00000000..1b141db3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/ApiGatewayInvokeReqVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class ApiGatewayInvokeReqVO { + + @Schema(description = "API 编码", requiredMode = Schema.RequiredMode.REQUIRED) + private String apiCode; + + @Schema(description = "API 版本", requiredMode = Schema.RequiredMode.REQUIRED) + private String version; + + @Schema(description = "请求头,可选") + private Map headers = new HashMap<>(); + + @Schema(description = "请求参数,可选") + private Map queryParams = new HashMap<>(); + + @Schema(description = "请求体") + private Object payload; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java new file mode 100644 index 00000000..dda4cf3b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionDetailRespVO.java @@ -0,0 +1,74 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Data +@Schema(description = "管理后台 - API 定义详情 Response VO") +public class ApiDefinitionDetailRespVO { + + @Schema(description = "主键", example = "1024") + private Long id; + + @Schema(description = "租户标识", example = "1") + private String tenantId; + + @Schema(description = "API 编码", example = "order.create") + private String apiCode; + + @Schema(description = "API 版本", example = "v1") + private String version; + + @Schema(description = "HTTP 方法", example = "POST") + private String httpMethod; + + @Schema(description = "URI 模板", example = "/external/order/create") + private String uriPattern; + + @Schema(description = "状态", example = "1") + private Integer status; + + @Schema(description = "是否灰度") + private Boolean greyReleased; + + @Schema(description = "描述") + private String description; + + @Schema(description = "认证策略编号") + private Long authPolicyId; + + @Schema(description = "限流策略编号") + private Long rateLimitId; + + @Schema(description = "响应模板(JSON)") + private String responseTemplate; + + @Schema(description = "缓存策略(JSON)") + private String cacheStrategy; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建人") + private String creator; + + @Schema(description = "更新人") + private String updater; + + @Schema(description = "API 级别变换列表") + private List apiLevelTransforms = new ArrayList<>(); + + @Schema(description = "步骤列表") + private List steps = new ArrayList<>(); + + @Schema(description = "发布信息") + private ApiDefinitionPublicationRespVO publication; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java new file mode 100644 index 00000000..cf5d82f4 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPageReqVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - API 定义分页查询 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiDefinitionPageReqVO extends PageParam { + + @Schema(description = "关键字,匹配编码/描述/URI", example = "order") + private String keyword; + + @Schema(description = "API 状态", example = "1") + private Integer status; + + @Schema(description = "HTTP 方法", example = "POST") + private String httpMethod; + + @Schema(description = "是否灰度", example = "true") + private Boolean greyReleased; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPublicationRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPublicationRespVO.java new file mode 100644 index 00000000..b53b4c36 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionPublicationRespVO.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "管理后台 - API 发布信息 Response VO") +public class ApiDefinitionPublicationRespVO { + + @Schema(description = "发布记录主键", example = "4001") + private Long id; + + @Schema(description = "发布标签", example = "release-20231001") + private String releaseTag; + + @Schema(description = "快照内容(JSON)") + private String snapshot; + + @Schema(description = "状态", example = "RELEASED") + private String status; + + @Schema(description = "是否当前生效") + private Boolean active; + + @Schema(description = "描述") + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java new file mode 100644 index 00000000..f9239581 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSaveReqVO.java @@ -0,0 +1,67 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Schema(description = "管理后台 - API 定义保存 Request VO") +public class ApiDefinitionSaveReqVO { + + @Schema(description = "主键", example = "1001") + private Long id; + + @Schema(description = "API 编码", example = "order.create") + @NotBlank(message = "API 编码不能为空") + private String apiCode; + + @Schema(description = "API 版本", example = "v1") + @NotBlank(message = "API 版本不能为空") + private String version; + + @Schema(description = "HTTP 方法", example = "POST") + @NotBlank(message = "HTTP 方法不能为空") + private String httpMethod; + + @Schema(description = "URI 模板", example = "/external/order/create") + @NotBlank(message = "URI 模板不能为空") + private String uriPattern; + + @Schema(description = "API 状态", example = "1") + @NotNull(message = "API 状态不能为空") + private Integer status; + + @Schema(description = "描述") + private String description; + + @Schema(description = "认证策略编号") + private Long authPolicyId; + + @Schema(description = "限流策略编号") + private Long rateLimitId; + + @Schema(description = "响应模板(JSON)") + private String responseTemplate; + + @Schema(description = "缓存策略(JSON)") + private String cacheStrategy; + + @Schema(description = "是否开启灰度发布") + private Boolean greyReleased; + + @Schema(description = "API 级别变换列表") + @Valid + private List apiLevelTransforms = new ArrayList<>(); + + @Schema(description = "步骤列表") + @NotEmpty(message = "编排步骤不能为空") + @Valid + private List steps = new ArrayList<>(); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java new file mode 100644 index 00000000..e6f7b71d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepRespVO.java @@ -0,0 +1,55 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Schema(description = "管理后台 - API 编排步骤详情 Response VO") +public class ApiDefinitionStepRespVO { + + @Schema(description = "步骤主键", example = "21001") + private Long id; + + @Schema(description = "所属 API 主键", example = "1024") + private Long apiId; + + @Schema(description = "步骤序号", example = "1") + private Integer stepOrder; + + @Schema(description = "并行分组") + private String parallelGroup; + + @Schema(description = "步骤类型", example = "HTTP") + private String type; + + @Schema(description = "目标端点") + private String targetEndpoint; + + @Schema(description = "请求映射表达式(JSON)") + private String requestMappingExpr; + + @Schema(description = "响应映射表达式(JSON)") + private String responseMappingExpr; + + @Schema(description = "超时时间(毫秒)") + private Long timeout; + + @Schema(description = "重试策略(JSON)") + private String retryStrategy; + + @Schema(description = "降级策略(JSON)") + private String fallbackStrategy; + + @Schema(description = "条件表达式") + private String conditionExpr; + + @Schema(description = "是否出错终止") + private Boolean stopOnError; + + @Schema(description = "步骤级变换列表") + private List transforms = new ArrayList<>(); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java new file mode 100644 index 00000000..650430ac --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionStepSaveReqVO.java @@ -0,0 +1,58 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Schema(description = "管理后台 - API 编排步骤保存 Request VO") +public class ApiDefinitionStepSaveReqVO { + + @Schema(description = "步骤主键", example = "21001") + private Long id; + + @Schema(description = "步骤序号", example = "1") + @NotNull(message = "步骤序号不能为空") + private Integer stepOrder; + + @Schema(description = "并行分组") + private String parallelGroup; + + @Schema(description = "步骤类型", example = "HTTP") + @NotBlank(message = "步骤类型不能为空") + private String type; + + @Schema(description = "目标端点", example = "https://api.demo.com/order") + private String targetEndpoint; + + @Schema(description = "请求映射表达式(JSON)") + private String requestMappingExpr; + + @Schema(description = "响应映射表达式(JSON)") + private String responseMappingExpr; + + @Schema(description = "超时时间(毫秒)", example = "5000") + private Long timeout; + + @Schema(description = "重试策略(JSON)") + private String retryStrategy; + + @Schema(description = "降级策略(JSON)") + private String fallbackStrategy; + + @Schema(description = "条件表达式") + private String conditionExpr; + + @Schema(description = "是否出错终止") + private Boolean stopOnError; + + @Schema(description = "步骤级变换列表") + @Valid + private List transforms = new ArrayList<>(); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java new file mode 100644 index 00000000..75e528cb --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionSummaryRespVO.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Schema(description = "管理后台 - API 定义分页列表 Response VO") +public class ApiDefinitionSummaryRespVO { + + @Schema(description = "主键", example = "1024") + private Long id; + + @Schema(description = "API 编码", example = "order.create") + private String apiCode; + + @Schema(description = "API 版本", example = "v1") + private String version; + + @Schema(description = "HTTP 方法", example = "POST") + private String httpMethod; + + @Schema(description = "URI 模板", example = "/external/order/create") + private String uriPattern; + + @Schema(description = "状态", example = "1") + private Integer status; + + @Schema(description = "是否灰度", example = "true") + private Boolean greyReleased; + + @Schema(description = "描述") + private String description; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建人") + private String creator; + + @Schema(description = "更新人") + private String updater; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformRespVO.java new file mode 100644 index 00000000..83c27912 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformRespVO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "管理后台 - API 变换详情 Response VO") +public class ApiDefinitionTransformRespVO { + + @Schema(description = "变换主键", example = "31001") + private Long id; + + @Schema(description = "所属 API 主键", example = "1024") + private Long apiId; + + @Schema(description = "所属步骤主键", example = "21001") + private Long stepId; + + @Schema(description = "阶段", example = "REQUEST") + private String phase; + + @Schema(description = "表达式类型", example = "SPEL") + private String expressionType; + + @Schema(description = "表达式内容", example = "#{payload}") + private String expression; + + @Schema(description = "描述") + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformSaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformSaveReqVO.java new file mode 100644 index 00000000..bd33c184 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/definition/ApiDefinitionTransformSaveReqVO.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.definition; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "管理后台 - API 变换保存 Request VO") +public class ApiDefinitionTransformSaveReqVO { + + @Schema(description = "变换主键", example = "31001") + private Long id; + + @Schema(description = "阶段", example = "REQUEST") + @NotBlank(message = "变换阶段不能为空") + private String phase; + + @Schema(description = "表达式类型", example = "SPEL") + @NotBlank(message = "表达式类型不能为空") + private String expressionType; + + @Schema(description = "表达式内容", example = "#{payload}") + @NotBlank(message = "表达式内容不能为空") + private String expression; + + @Schema(description = "描述") + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyBaseVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyBaseVO.java new file mode 100644 index 00000000..e0c82b25 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyBaseVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.policy; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * Base VO for policy definitions shared by request/response objects. + */ +@Data +public class ApiPolicyBaseVO { + + @Schema(description = "策略名称", example = "JWT") + @NotBlank(message = "策略名称不能为空") + private String name; + + @Schema(description = "策略类型", example = "JWT") + @NotBlank(message = "策略类型不能为空") + private String type; + + @Schema(description = "策略配置(JSON)", example = "{\"issuer\":\"iam\"}") + private String config; + + @Schema(description = "策略描述", example = "JWT 认证策略") + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyPageReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyPageReqVO.java new file mode 100644 index 00000000..77ee7cfe --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyPageReqVO.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.policy; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Policy search conditions with pagination. + */ +@Schema(description = "管理后台 - 策略分页查询 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiPolicyPageReqVO extends PageParam { + + @Schema(description = "关键字(名称/描述)", example = "JWT") + private String keyword; + + @Schema(description = "策略类型", example = "JWT") + private String type; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyRespVO.java new file mode 100644 index 00000000..86ac74a5 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicyRespVO.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.policy; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * Policy detail response VO. + */ +@Schema(description = "管理后台 - 策略详情 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiPolicyRespVO extends ApiPolicyBaseVO { + + @Schema(description = "策略编号", example = "1024") + private Long id; + + @Schema(description = "创建人", example = "admin") + private String creator; + + @Schema(description = "修改人", example = "admin") + private String updater; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "最后更新时间") + private LocalDateTime updateTime; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySaveReqVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySaveReqVO.java new file mode 100644 index 00000000..f8fb32ea --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySaveReqVO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.policy; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Policy create/update request VO. + */ +@Schema(description = "管理后台 - 策略保存 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiPolicySaveReqVO extends ApiPolicyBaseVO { + + @Schema(description = "策略编号", example = "1024") + private Long id; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySimpleRespVO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySimpleRespVO.java new file mode 100644 index 00000000..783f878d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/controller/admin/gateway/vo/policy/ApiPolicySimpleRespVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.controller.admin.gateway.vo.policy; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Policy simple response VO used by dropdowns. + */ +@Schema(description = "管理后台 - 策略精简 Response VO") +@Data +public class ApiPolicySimpleRespVO { + + @Schema(description = "策略编号", example = "1024") + private Long id; + + @Schema(description = "策略名称", example = "JWT") + private String name; + + @Schema(description = "策略类型", example = "JWT") + private String type; + + @Schema(description = "策略描述") + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java new file mode 100644 index 00000000..596bb40d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiDefinitionDO.java @@ -0,0 +1,52 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * API definition data object describing external API metadata and policies. + */ +@TableName("databus_api_definition") +@KeySequence("databus_api_definition_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiDefinitionDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private String apiCode; + + private String uriPattern; + + private String httpMethod; + + private String version; + + /** + * API status, see {@code ApiPublishStatusEnum}. + */ + private Integer status; + + private String description; + + private Long authPolicyId; + + private Long rateLimitId; + + private String responseTemplate; + + private String cacheStrategy; + + private LocalDateTime updatedAt; + + private Boolean greyReleased; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiFlowPublishDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiFlowPublishDO.java new file mode 100644 index 00000000..f16f1530 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiFlowPublishDO.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Publication record for API flow snapshots and gray releases. + */ +@TableName("databus_api_flow_publish") +@KeySequence("databus_api_flow_publish_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiFlowPublishDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long apiId; + + private String releaseTag; + + private String snapshot; + + private String status; + + private Boolean active; + + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java new file mode 100644 index 00000000..90054af6 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyAuthDO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Authentication policy definition. + */ +@TableName("databus_policy_auth") +@KeySequence("databus_policy_auth_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiPolicyAuthDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private String name; + + private String type; + + private String config; + + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyRateLimitDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyRateLimitDO.java new file mode 100644 index 00000000..b14cdafa --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiPolicyRateLimitDO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Rate limit policy definition stored in database. + */ +@TableName("databus_policy_rate_limit") +@KeySequence("databus_policy_rate_limit_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiPolicyRateLimitDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private String name; + + private String type; + + private String config; + + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java new file mode 100644 index 00000000..b0f17529 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiStepDO.java @@ -0,0 +1,49 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * API orchestration step definition. + */ +@TableName("databus_api_step") +@KeySequence("databus_api_step_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiStepDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long apiId; + + private Integer stepOrder; + + private String parallelGroup; + + private String type; + + private String targetEndpoint; + + private String requestMappingExpr; + + private String responseMappingExpr; + + private Long transformId; + + private Long timeout; + + private String retryStrategy; + + private String fallbackStrategy; + + private String conditionExpr; + + private Boolean stopOnError; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiTransformDO.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiTransformDO.java new file mode 100644 index 00000000..e3ae2a2e --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/dataobject/gateway/ApiTransformDO.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.databus.dal.dataobject.gateway; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * API request/response transformation expressions. + */ +@TableName("databus_api_transform") +@KeySequence("databus_api_transform_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class ApiTransformDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private Long apiId; + + private Long stepId; + + private String phase; + + private String expressionType; + + private String expression; + + private String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java new file mode 100644 index 00000000..d4278f4a --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiDefinitionMapper.java @@ -0,0 +1,63 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Optional; + +@Mapper +public interface ApiDefinitionMapper extends BaseMapperX { + + default Optional selectByCodeAndVersion(String apiCode, String version) { + return Optional.ofNullable(selectOne(ApiDefinitionDO::getApiCode, apiCode, + ApiDefinitionDO::getVersion, version, + ApiDefinitionDO::getDeleted, false)); + } + + default List selectActiveDefinitions(List statusList) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(ApiDefinitionDO::getStatus, statusList) + .eq(ApiDefinitionDO::getDeleted, false)); + } + + default PageResult selectPage(ApiDefinitionPageReqVO reqVO) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + if (StrUtil.isNotBlank(reqVO.getKeyword())) { + String keyword = reqVO.getKeyword(); + query.and(wrapper -> wrapper.like(ApiDefinitionDO::getApiCode, keyword) + .or().like(ApiDefinitionDO::getDescription, keyword) + .or().like(ApiDefinitionDO::getUriPattern, keyword)); + } + query.eqIfPresent(ApiDefinitionDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ApiDefinitionDO::getHttpMethod, reqVO.getHttpMethod()) +// .eqIfPresent(ApiDefinitionDO::getGreyReleased, reqVO.getGreyReleased()) + .orderByDesc(ApiDefinitionDO::getUpdateTime) + .orderByDesc(ApiDefinitionDO::getId); + return selectPage(reqVO, query); + } + + default Long selectCountByAuthPolicyId(Long policyId) { + if (policyId == null) { + return 0L; + } + return selectCount(new LambdaQueryWrapperX() + .eq(ApiDefinitionDO::getAuthPolicyId, policyId) + .eq(ApiDefinitionDO::getDeleted, false)); + } + + default Long selectCountByRateLimitPolicyId(Long policyId) { + if (policyId == null) { + return 0L; + } + return selectCount(new LambdaQueryWrapperX() + .eq(ApiDefinitionDO::getRateLimitId, policyId) + .eq(ApiDefinitionDO::getDeleted, false)); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiFlowPublishMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiFlowPublishMapper.java new file mode 100644 index 00000000..02f47834 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiFlowPublishMapper.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiFlowPublishDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Optional; + +@Mapper +public interface ApiFlowPublishMapper extends BaseMapperX { + + default Optional selectActiveByApiId(Long apiId) { + return Optional.ofNullable(selectOne(new LambdaQueryWrapperX() + .eq(ApiFlowPublishDO::getApiId, apiId) + .eq(ApiFlowPublishDO::getActive, true))); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java new file mode 100644 index 00000000..b1bf14ba --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyAuthMapper.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ApiPolicyAuthMapper extends BaseMapperX { + + default PageResult selectPage(ApiPolicyPageReqVO reqVO) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + if (StrUtil.isNotBlank(reqVO.getKeyword())) { + String keyword = reqVO.getKeyword(); + query.and(wrapper -> wrapper.like(ApiPolicyAuthDO::getName, keyword) + .or().like(ApiPolicyAuthDO::getDescription, keyword)); + } + query.eqIfPresent(ApiPolicyAuthDO::getType, reqVO.getType()) + .eq(ApiPolicyAuthDO::getDeleted, false) + .orderByDesc(ApiPolicyAuthDO::getUpdateTime) + .orderByDesc(ApiPolicyAuthDO::getId); + return selectPage(reqVO, query); + } + + default List selectSimpleList() { + return selectList(new LambdaQueryWrapperX() + .eq(ApiPolicyAuthDO::getDeleted, false) + .orderByDesc(ApiPolicyAuthDO::getUpdateTime) + .orderByDesc(ApiPolicyAuthDO::getId)); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyRateLimitMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyRateLimitMapper.java new file mode 100644 index 00000000..7d8ab89d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiPolicyRateLimitMapper.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ApiPolicyRateLimitMapper extends BaseMapperX { + + default PageResult selectPage(ApiPolicyPageReqVO reqVO) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + if (StrUtil.isNotBlank(reqVO.getKeyword())) { + String keyword = reqVO.getKeyword(); + query.and(wrapper -> wrapper.like(ApiPolicyRateLimitDO::getName, keyword) + .or().like(ApiPolicyRateLimitDO::getDescription, keyword)); + } + query.eqIfPresent(ApiPolicyRateLimitDO::getType, reqVO.getType()) + .eq(ApiPolicyRateLimitDO::getDeleted, false) + .orderByDesc(ApiPolicyRateLimitDO::getUpdateTime) + .orderByDesc(ApiPolicyRateLimitDO::getId); + return selectPage(reqVO, query); + } + + default List selectSimpleList() { + return selectList(new LambdaQueryWrapperX() + .eq(ApiPolicyRateLimitDO::getDeleted, false) + .orderByDesc(ApiPolicyRateLimitDO::getUpdateTime) + .orderByDesc(ApiPolicyRateLimitDO::getId)); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiStepMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiStepMapper.java new file mode 100644 index 00000000..d6f79e64 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiStepMapper.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ApiStepMapper extends BaseMapperX { + + default List selectByApiId(Long apiId) { + return selectList(new LambdaQueryWrapperX() + .eq(ApiStepDO::getApiId, apiId) + .orderByAsc(ApiStepDO::getParallelGroup) + .orderByAsc(ApiStepDO::getStepOrder)); + } + + default void deleteByApiId(Long apiId) { + delete(new LambdaQueryWrapperX() + .eq(ApiStepDO::getApiId, apiId)); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiTransformMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiTransformMapper.java new file mode 100644 index 00000000..58a965d9 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/dal/mysql/gateway/ApiTransformMapper.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.databus.dal.mysql.gateway; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ApiTransformMapper extends BaseMapperX { + + default List selectByApiId(Long apiId) { + return selectList(new LambdaQueryWrapperX() + .eq(ApiTransformDO::getApiId, apiId)); + } + + default List selectByStepId(Long stepId) { + return selectList(new LambdaQueryWrapperX() + .eq(ApiTransformDO::getStepId, stepId)); + } + + default List selectApiLevelTransforms(Long apiId) { + return selectList(new LambdaQueryWrapperX() + .eq(ApiTransformDO::getApiId, apiId) + .isNull(ApiTransformDO::getStepId)); + } + + default void deleteByApiId(Long apiId) { + delete(new LambdaQueryWrapperX() + .eq(ApiTransformDO::getApiId, apiId)); + } + + default void deleteByStepId(Long stepId) { + delete(new LambdaQueryWrapperX() + .eq(ApiTransformDO::getStepId, stepId)); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java new file mode 100644 index 00000000..76ffda64 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStatusEnum.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.enums.gateway; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * External API publish status enumeration. + */ +@AllArgsConstructor +@Getter +public enum ApiStatusEnum { + + DRAFT(0), + ONLINE(1), + OFFLINE(2), + DEPRECATED(3); + + private final int status; + + public static boolean isOnline(Integer status) { + return status != null && status == ONLINE.status; + } + + public static boolean isDeprecated(Integer status) { + return status != null && status == DEPRECATED.status; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java new file mode 100644 index 00000000..589a49f1 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ApiStepTypeEnum.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.databus.enums.gateway; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Step types supported by the unified API portal. + */ +@AllArgsConstructor +@Getter +public enum ApiStepTypeEnum { + + HTTP, + RPC, + SCRIPT, + FLOW; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ExpressionTypeEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ExpressionTypeEnum.java new file mode 100644 index 00000000..7efe85da --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/ExpressionTypeEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.databus.enums.gateway; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Locale; + +/** + * Supported expression languages for request/response mapping. + */ +@AllArgsConstructor +@Getter +public enum ExpressionTypeEnum { + + JSON("json"); + + private final String code; + + public static ExpressionTypeEnum fromValue(String value) { + if (value == null) { + return null; + } + String normalized = value.trim().toLowerCase(Locale.ROOT); + for (ExpressionTypeEnum type : values()) { + if (type.code.equals(normalized) || type.name().equals(normalized.toUpperCase(Locale.ROOT))) { + return type; + } + } + return null; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/TransformPhaseEnum.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/TransformPhaseEnum.java new file mode 100644 index 00000000..6ea21521 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/enums/gateway/TransformPhaseEnum.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.enums.gateway; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Transformation phase enumeration. + */ +@AllArgsConstructor +@Getter +public enum TransformPhaseEnum { + + REQUEST_PRE, + REQUEST_POST, + RESPONSE_PRE, + RESPONSE_POST, + ERROR; + + public static TransformPhaseEnum fromCode(String code) { + for (TransformPhaseEnum phase : values()) { + if (phase.name().equalsIgnoreCase(code)) { + return phase; + } + } + return null; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java new file mode 100644 index 00000000..ac37137a --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ApiGatewayProperties.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.databus.framework.integration.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration properties for the unified API portal. + */ +@Data +@ConfigurationProperties(prefix = "databus.api-portal") +public class ApiGatewayProperties { + + private String basePath = "/api/portal"; + + private List allowedIps = new ArrayList<>(); + + private List deniedIps = new ArrayList<>(); + + private boolean enableSignature = false; + + private String signatureHeader = "X-Signature"; + + private String signatureSecret; + + private boolean enableTenantHeader = true; + + private String tenantHeader = "X-Tenant-Id"; + + private boolean enableAudit = true; + + private boolean enableRateLimit = true; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ExpressionConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ExpressionConfiguration.java new file mode 100644 index 00000000..0096592d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/ExpressionConfiguration.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.framework.integration.config; + +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionEvaluatorRegistry; +import com.zt.plat.module.databus.framework.integration.gateway.expression.JsonataExpressionEvaluator; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; + +/** + * Registers expression evaluators with the registry. + */ +@Configuration +@RequiredArgsConstructor +public class ExpressionConfiguration { + + private final ExpressionEvaluatorRegistry registry; + private final JsonataExpressionEvaluator jsonataExpressionEvaluator; + + @PostConstruct + public void registerEvaluators() { + registry.register(ExpressionTypeEnum.JSON, jsonataExpressionEvaluator); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java new file mode 100644 index 00000000..9d75d547 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/config/GatewayIntegrationConfiguration.java @@ -0,0 +1,147 @@ +package com.zt.plat.module.databus.framework.integration.config; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.module.databus.framework.integration.gateway.core.ApiFlowDispatcher; +import com.zt.plat.module.databus.framework.integration.gateway.core.ApiGatewayRequestMapper; +import com.zt.plat.module.databus.framework.integration.gateway.core.ErrorHandlingStrategy; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.http.dsl.Http; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Configures the unified API portal inbound gateway and supporting beans. + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(ApiGatewayProperties.class) +@RequiredArgsConstructor +public class GatewayIntegrationConfiguration { + + private final ApiGatewayProperties properties; + private final ApiGatewayRequestMapper requestMapper; + private final ObjectProvider apiFlowDispatcherProvider; + private final ErrorHandlingStrategy errorHandlingStrategy; + + @Bean(name = "apiPortalTaskExecutor") + public ThreadPoolTaskExecutor apiPortalTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); + executor.setMaxPoolSize(32); + executor.setQueueCapacity(256); + executor.setThreadNamePrefix("api-portal-"); + executor.initialize(); + return executor; + } + + @Bean + public MessagingTemplate apiPortalMessagingTemplate() { + return new MessagingTemplate(); + } + + @Bean + public FilterRegistrationBean gatewaySecurityFilterRegistration(GatewaySecurityFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 10); + return registration; + } + + @Bean + public IntegrationFlow apiGatewayInboundFlow() { + String pattern = properties.getBasePath() + "/{apiCode}/{version}"; + return IntegrationFlow.from(Http.inboundGateway(pattern) + .requestMapping(spec -> spec + .methods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.PATCH)) + .errorChannel(errorHandlingStrategy.getErrorChannel()) + .requestPayloadType(String.class) + .mappedRequestHeaders("*") + .mappedResponseHeaders("*")) + .handle(this, "mapRequest", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())) + .handle(this, "dispatch", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())) + .handle(this, "buildResponse", endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())) + .get(); + } + + public Message mapRequest(Message message) { + ApiInvocationContext context = requestMapper.map(message.getPayload(), message.getHeaders()); + return MessageBuilder.withPayload(context) + .copyHeaders(message.getHeaders()) + .setHeaderIfAbsent("apiCode", context.getApiCode()) + .setHeaderIfAbsent("version", context.getApiVersion()) + .build(); + } + + public ApiInvocationContext dispatch(Message message) { + ApiInvocationContext context = message.getPayload(); + try { + return apiFlowDispatcherProvider.getObject() + .dispatch(context.getApiCode(), context.getApiVersion(), context); + } catch (ServiceException ex) { + handleServiceException(context, ex); + log.warn("[API-PORTAL] ServiceException while dispatching apiCode={} version={}: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage()); + return context; + } catch (Exception ex) { + handleUnexpectedException(context, ex); + log.error("[API-PORTAL] Unexpected exception while dispatching apiCode={} version={}", context.getApiCode(), context.getApiVersion(), ex); + return context; + } + } + + public ResponseEntity buildResponse(ApiInvocationContext context) { + int status = context.getResponseStatus() != null ? context.getResponseStatus() : HttpStatus.OK.value(); + ApiGatewayResponse envelope = ApiGatewayResponse.builder() + .code(status >= 200 && status < 400 ? "SUCCESS" : "ERROR") + .message(StringUtils.hasText(context.getResponseMessage()) ? context.getResponseMessage() : HttpStatus.valueOf(status).getReasonPhrase()) + .data(context.getResponseBody()) + .traceId(context.getRequestId()) + .build(); + return ResponseEntity.status(status).body(envelope); + } + + private void handleServiceException(ApiInvocationContext context, ServiceException ex) { + String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation failed"; + context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + context.setResponseMessage(message); + if (context.getResponseBody() == null) { + Map body = new HashMap<>(); + if (ex.getCode() != null) { + body.put("errorCode", ex.getCode()); + } + body.put("errorMessage", message); + context.setResponseBody(body); + } + } + + private void handleUnexpectedException(ApiInvocationContext context, Exception ex) { + String message = StringUtils.hasText(ex.getMessage()) ? ex.getMessage() : "API invocation encountered an unexpected error"; + context.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + context.setResponseMessage(message); + if (context.getResponseBody() == null) { + Map body = new HashMap<>(); + body.put("errorMessage", message); + body.put("exception", ex.getClass().getSimpleName()); + context.setResponseBody(body); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java new file mode 100644 index 00000000..a1d8c005 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowAssembler.java @@ -0,0 +1,270 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.enums.gateway.TransformPhaseEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.step.StepHandlerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.aop.Advice; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.task.TaskExecutor; +import org.springframework.integration.core.GenericHandler; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlowBuilder; +import org.springframework.integration.dsl.MessageChannels; +import org.springframework.messaging.MessageHeaders; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_PARALLEL_FAILED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_PARALLEL_INTERRUPTED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_TRANSFORM_EVALUATION_FAILED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_TRANSFORM_RESPONSE_STATUS_INVALID; + +/** + * Assembles dynamic integration flows per API definition. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ApiFlowAssembler { + + private final StepHandlerFactory stepHandlerFactory; + private final PolicyAdvisorFactory policyAdvisorFactory; + private final ErrorHandlingStrategy errorHandlingStrategy; + private final MonitoringInterceptor monitoringInterceptor; + private final ExpressionExecutor expressionExecutor; + @Qualifier("apiPortalTaskExecutor") + private final TaskExecutor apiPortalTaskExecutor; + + public ApiFlowRegistration assemble(ApiDefinitionAggregate aggregate) { + String inputChannelName = channelName(aggregate); + String flowId = flowId(aggregate); + IntegrationFlowBuilder builder = IntegrationFlow.from(MessageChannels.direct(inputChannelName) + .datatype(ApiInvocationContext.class) + .interceptor(monitoringInterceptor)) + .log(message -> String.format("[API-PORTAL] entering flow %s", flowId)) + .handle(ApiInvocationContext.class, + applyTransforms(aggregate, TransformPhaseEnum.REQUEST_PRE), + endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())); + + List segments = segments(aggregate.getSteps()); + for (FlowSegment segment : segments) { + if (segment instanceof SequentialSegment sequentialSegment) { + builder = applySequential(builder, aggregate, sequentialSegment.getStep()); + } else if (segment instanceof ParallelSegment parallelSegment) { + builder = applyParallel(builder, aggregate, parallelSegment); + } + } + + builder = builder + .handle(ApiInvocationContext.class, + applyTransforms(aggregate, TransformPhaseEnum.RESPONSE_PRE), + endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())) + .handle(ApiInvocationContext.class, + (payload, headers) -> payload, + endpoint -> endpoint.advice(errorHandlingStrategy.errorForwardingAdvice())); + + return ApiFlowRegistration.builder() + .flowId(flowId) + .inputChannelName(inputChannelName) + .flow(builder.get()) + .build(); + } + + private GenericHandler applyTransforms(ApiDefinitionAggregate aggregate, TransformPhaseEnum phase) { + return (payload, headers) -> { + var transformDefinition = aggregate.getApiLevelTransforms().get(phase.name()); + if (transformDefinition != null && StringUtils.hasText(transformDefinition.getExpression())) { + String rawExpression = transformDefinition.getExpressionType() + "::" + transformDefinition.getExpression(); + ExpressionSpec spec = ExpressionSpecParser.parse(rawExpression, ExpressionTypeEnum.JSON); + try { + Object result = expressionExecutor.evaluate(spec, payload, payload.getRequestBody(), headers); + applyTransformResult(payload, result); + } catch (Exception ex) { + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_TRANSFORM_EVALUATION_FAILED, ex.getMessage()); + } + } + return payload; + }; + } + + private void applyTransformResult(ApiInvocationContext context, Object result) { + if (!(result instanceof Map map)) { + return; + } + Object headerUpdates = map.get("requestHeaders"); + if (headerUpdates instanceof Map headerMap) { + headerMap.forEach((key, value) -> context.getRequestHeaders().put(String.valueOf(key), value)); + } + Object variableUpdates = map.get("variables"); + if (variableUpdates instanceof Map variables) { + variables.forEach((key, value) -> context.getVariables().put(String.valueOf(key), value)); + } + Object attributeUpdates = map.get("attributes"); + if (attributeUpdates instanceof Map attributes) { + attributes.forEach((key, value) -> context.getAttributes().put(String.valueOf(key), value)); + } + if (map.containsKey("responseBody")) { + context.setResponseBody(map.get("responseBody")); + } + if (map.containsKey("responseStatus")) { + context.setResponseStatus(asInteger(map.get("responseStatus"))); + } + if (map.containsKey("responseMessage")) { + Object message = map.get("responseMessage"); + context.setResponseMessage(message == null ? null : String.valueOf(message)); + } + } + + private Integer asInteger(Object value) { + if (value == null) { + return null; + } + if (value instanceof Number number) { + return number.intValue(); + } + try { + return Integer.parseInt(String.valueOf(value)); + } catch (NumberFormatException ex) { + throw ServiceExceptionUtil.exception(API_TRANSFORM_RESPONSE_STATUS_INVALID, value); + } + } + + private IntegrationFlowBuilder applySequential(IntegrationFlowBuilder builder, ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + GenericHandler handler = stepHandlerFactory.build(aggregate, stepDefinition); + return builder.handle(ApiInvocationContext.class, handler, endpoint -> { + endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()); + Advice[] advices = policyAdvisorFactory.buildAdvices(aggregate, stepDefinition); + if (advices.length > 0) { + endpoint.advice(advices); + } + }); + } + + private IntegrationFlowBuilder applyParallel(IntegrationFlowBuilder builder, ApiDefinitionAggregate aggregate, ParallelSegment segment) { + return builder.handle(ApiInvocationContext.class, + (payload, headers) -> executeParallel(payload, headers, aggregate, segment), + endpoint -> { + endpoint.advice(errorHandlingStrategy.errorForwardingAdvice()); + Advice[] advices = policyAdvisorFactory.buildParallelAdvices(aggregate, segment); + if (advices.length > 0) { + endpoint.advice(advices); + } + }); + } + + private ApiInvocationContext executeParallel(ApiInvocationContext context, MessageHeaders headers, + ApiDefinitionAggregate aggregate, ParallelSegment segment) { + List> futures = new ArrayList<>(); + for (ApiStepDefinition step : segment.getSteps()) { + GenericHandler handler = stepHandlerFactory.build(aggregate, step); + ApiInvocationContext childContext = context.copy(); + futures.add(CompletableFuture.supplyAsync(() -> { + handler.handle(childContext, headers); + return childContext; + }, apiPortalTaskExecutor)); + } + for (CompletableFuture future : futures) { + try { + ApiInvocationContext child = future.get(); + context.merge(child); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw ServiceExceptionUtil.exception(API_PARALLEL_INTERRUPTED); + } catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_PARALLEL_FAILED, cause == null ? ex.getMessage() : cause.getMessage()); + } + } + return context; + } + + private List segments(List steps) { + return steps.stream() + .sorted(Comparator.comparingInt(step -> step.getStep().getStepOrder() == null ? Integer.MAX_VALUE : step.getStep().getStepOrder())) + .collect(ArrayList::new, this::consumeStep, this::combineSegments); + } + + private void consumeStep(List segments, ApiStepDefinition step) { + String parallelGroup = step.getStep().getParallelGroup(); + if (!StringUtils.hasText(parallelGroup)) { + segments.add(new SequentialSegment(step)); + return; + } + FlowSegment last = segments.isEmpty() ? null : segments.get(segments.size() - 1); + if (last instanceof ParallelSegment parallelSegment && parallelGroup.equals(parallelSegment.getGroup())) { + parallelSegment.getSteps().add(step); + } else { + ParallelSegment newSegment = new ParallelSegment(parallelGroup, new ArrayList<>()); + newSegment.getSteps().add(step); + segments.add(newSegment); + } + } + + private void combineSegments(List target, List source) { + target.addAll(source); + } + + private String channelName(ApiDefinitionAggregate aggregate) { + return "api.portal.flow." + aggregate.getDefinition().getApiCode().toLowerCase() + "." + aggregate.getDefinition().getVersion(); + } + + private String flowId(ApiDefinitionAggregate aggregate) { + return "apiPortalFlow:" + aggregate.getDefinition().getApiCode() + ":" + aggregate.getDefinition().getVersion(); + } + + private interface FlowSegment { + } + + private static final class SequentialSegment implements FlowSegment { + private final ApiStepDefinition step; + + private SequentialSegment(ApiStepDefinition step) { + this.step = step; + } + + public ApiStepDefinition getStep() { + return step; + } + } + + private static final class ParallelSegment implements FlowSegment { + private final String group; + private final List steps; + + private ParallelSegment(String group, List steps) { + this.group = group; + this.steps = steps; + } + + public String getGroup() { + return group; + } + + public List getSteps() { + return steps; + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java new file mode 100644 index 00000000..2484aaa2 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowDispatcher.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_FLOW_NOT_FOUND; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_FLOW_NO_REPLY; + +/** + * Dispatches API invocation contexts to the appropriate integration flow. + */ +@Component +@RequiredArgsConstructor +public class ApiFlowDispatcher { + + private final IntegrationFlowManager integrationFlowManager; + private final MessagingTemplate messagingTemplate; + + public ApiInvocationContext dispatch(String apiCode, String version, ApiInvocationContext context) { + MessageChannel channel = integrationFlowManager.locateInputChannel(apiCode, version) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_FLOW_NOT_FOUND, apiCode, version)); + Message message = MessageBuilder.withPayload(context) + .setHeader("apiCode", apiCode) + .setHeader("version", version) + .build(); + Message reply = messagingTemplate.sendAndReceive(channel, message); + if (reply == null) { + throw ServiceExceptionUtil.exception(API_FLOW_NO_REPLY, apiCode, version); + } + return (ApiInvocationContext) reply.getPayload(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java new file mode 100644 index 00000000..cdd57f1c --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiFlowRegistration.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import lombok.Builder; +import lombok.Value; +import org.springframework.integration.dsl.IntegrationFlow; + +/** + * Metadata returned by the assembler for flow registration. + */ +@Value +@Builder +public class ApiFlowRegistration { + + String flowId; + + String inputChannelName; + + IntegrationFlow flow; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java new file mode 100644 index 00000000..c050f260 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayRequestMapper.java @@ -0,0 +1,82 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerMapping; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; + +/** + * Maps inbound HTTP request metadata into {@link ApiInvocationContext} instances. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ApiGatewayRequestMapper { + + private final ObjectMapper objectMapper; + private final ApiGatewayProperties properties; + + private static final String HEADER_REQUEST_HEADERS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders"; + private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri"; + + @SuppressWarnings("unchecked") + public ApiInvocationContext map(Object payload, Map headers) { + ApiInvocationContext context = ApiInvocationContext.create(); + Map uriVariables = (Map) headers.get(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + if (uriVariables != null) { + context.setApiCode(String.valueOf(uriVariables.get("apiCode"))); + context.setApiVersion(String.valueOf(uriVariables.get("version"))); + } + Object methodHeader = headers.get(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD); + if (methodHeader != null) { + context.setHttpMethod(String.valueOf(methodHeader)); + } + Object requestPath = headers.get(HEADER_REQUEST_URI); + if (requestPath == null) { + requestPath = headers.get(org.springframework.integration.http.HttpHeaders.REQUEST_URL); + } + if (requestPath != null) { + context.setRequestPath(String.valueOf(requestPath)); + } + Map requestHeaders = (Map) headers.get(HEADER_REQUEST_HEADERS); + if (requestHeaders != null) { + requestHeaders.forEach((key, value) -> context.getRequestHeaders().put(key, String.valueOf(value))); + } + if (properties.isEnableTenantHeader()) { + Object tenantHeaderValue = context.getRequestHeaders().get(properties.getTenantHeader()); + if (tenantHeaderValue != null) { + context.setTenantId(String.valueOf(tenantHeaderValue)); + } + } + if (payload instanceof String body) { + if (StringUtils.hasText(body) && isJsonContent(context)) { + try { + context.setRequestBody(objectMapper.readValue(body, Object.class)); + } catch (IOException ex) { + log.warn("Failed to parse request body as JSON", ex); + context.setRequestBody(body); + } + } else { + context.setRequestBody(body); + } + } else { + context.setRequestBody(payload); + } + return context; + } + + private boolean isJsonContent(ApiInvocationContext context) { + String contentType = String.valueOf(context.getRequestHeaders().getOrDefault(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)).toLowerCase(Locale.ROOT); + return contentType.contains(MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ErrorHandlingStrategy.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ErrorHandlingStrategy.java new file mode 100644 index 00000000..e7307110 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ErrorHandlingStrategy.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.dsl.MessageChannels; +import org.springframework.integration.handler.advice.AbstractHandleMessageAdvice; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.ErrorMessage; +import org.springframework.stereotype.Component; + +/** + * Centralized error channel and handler for the API portal. + */ +@Slf4j +@Component +public class ErrorHandlingStrategy { + + @Getter + private final MessageChannel errorChannel; + private final Advice errorForwardingAdvice; + + public ErrorHandlingStrategy() { + DirectChannel channel = MessageChannels.direct("apiPortalErrorChannel").getObject(); + this.errorChannel = channel; + channel.subscribe(this::handleErrorMessage); + this.errorForwardingAdvice = new ErrorForwardingAdvice(); + } + + public Advice errorForwardingAdvice() { + return errorForwardingAdvice; + } + + private void handleErrorMessage(Message message) { + if (message instanceof ErrorMessage errorMessage) { + handleError(errorMessage); + } + } + + private void handleError(ErrorMessage errorMessage) { + Throwable throwable = errorMessage.getPayload(); + Message failedMessage = errorMessage.getOriginalMessage(); + if (failedMessage != null && failedMessage.getPayload() instanceof ApiInvocationContext context) { + context.setResponseStatus(500); + context.setResponseMessage(throwable.getMessage()); + } + log.error("[API-PORTAL] Integration flow error", throwable); + } + + private class ErrorForwardingAdvice extends AbstractHandleMessageAdvice { + + @Override + protected Object doInvoke(MethodInvocation invocation, Message message) throws Throwable { + try { + return invocation.proceed(); + } catch (Throwable ex) { + ErrorMessage errorMessage = new ErrorMessage(ex, message); + try { + if (!errorChannel.send(errorMessage)) { + log.warn("[API-PORTAL] Failed to forward error message to channel {}", errorChannel); + } + } catch (Exception sendEx) { + log.error("[API-PORTAL] Error while submitting message to error channel", sendEx); + } + throw ex; + } + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/IntegrationFlowManager.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/IntegrationFlowManager.java new file mode 100644 index 00000000..0867b24b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/IntegrationFlowManager.java @@ -0,0 +1,96 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.service.gateway.ApiDefinitionService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.integration.dsl.context.IntegrationFlowContext; +import org.springframework.messaging.MessageChannel; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages dynamic registration of API integration flows. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class IntegrationFlowManager { + + private final IntegrationFlowContext integrationFlowContext; + private final ApiDefinitionService apiDefinitionService; + private final ApiFlowAssembler apiFlowAssembler; + + private final Map activeRegistrations = new ConcurrentHashMap<>(); + + @PostConstruct + public void bootstrap() { + refreshAll(); + } + + public void refreshAll() { + List aggregates = apiDefinitionService.loadActiveDefinitions(); + Map desired = new ConcurrentHashMap<>(); + for (ApiDefinitionAggregate aggregate : aggregates) { + desired.put(key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion()), aggregate); + } + + // remove flows that are no longer active + activeRegistrations.keySet().stream() + .filter(existingKey -> !desired.containsKey(existingKey)) + .forEach(this::deregisterByKey); + + // register or refresh active flows + desired.values().forEach(this::registerFlow); + } + + public void refresh(String apiCode, String version) { + apiDefinitionService.refresh(apiCode, version) + .ifPresentOrElse(this::registerFlow, () -> deregister(apiCode, version)); + } + + public Optional locateInputChannel(String apiCode, String version) { + String key = key(apiCode, version); + IntegrationFlowContext.IntegrationFlowRegistration registration = activeRegistrations.get(key); + if (registration == null) { + return Optional.empty(); + } + return Optional.ofNullable(registration.getInputChannel()); + } + + private void registerFlow(ApiDefinitionAggregate aggregate) { + String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion()); + deregisterByKey(key); + ApiFlowRegistration apiFlowRegistration = apiFlowAssembler.assemble(aggregate); + IntegrationFlowContext.IntegrationFlowRegistration registration = integrationFlowContext.registration(apiFlowRegistration.getFlow()) + .id(apiFlowRegistration.getFlowId()) + .register(); + activeRegistrations.put(key, registration); + log.info("[API-PORTAL] registered flow {} for apiCode={} version={}", apiFlowRegistration.getFlowId(), aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion()); + } + + private void deregister(String apiCode, String version) { + deregisterByKey(key(apiCode, version)); + } + + private void deregisterByKey(String key) { + IntegrationFlowContext.IntegrationFlowRegistration existing = activeRegistrations.remove(key); + if (existing != null) { + try { + integrationFlowContext.remove(existing.getId()); + log.info("[API-PORTAL] deregistered flow {} for key {}", existing.getId(), key); + } catch (Exception ex) { + log.warn("Failed to remove integration flow {}", existing.getId(), ex); + } + } + } + + private String key(String apiCode, String version) { + return (apiCode + ":" + version).toLowerCase(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/MonitoringInterceptor.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/MonitoringInterceptor.java new file mode 100644 index 00000000..f2895a99 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/MonitoringInterceptor.java @@ -0,0 +1,54 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.Instant; + +/** + * Channel interceptor capturing timing metrics and enriched logging. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MonitoringInterceptor implements ChannelInterceptor { + + private static final String HEADER_START_TIME = "ApiPortalStartTime"; + + private final MeterRegistry meterRegistry; + + @Override + public Message preSend(Message message, MessageChannel channel) { + return MessageBuilder.fromMessage(message) + .setHeader(HEADER_START_TIME, Instant.now()) + .build(); + } + + @Override + public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, Exception ex) { + Instant start = message.getHeaders().get(HEADER_START_TIME, Instant.class); + if (start != null) { + Duration duration = Duration.between(start, Instant.now()); + Object payload = message.getPayload(); + if (payload instanceof ApiInvocationContext context) { + Timer.builder("api.portal.latency") + .tag("api", context.getApiCode()) + .tag("version", context.getApiVersion()) + .register(meterRegistry) + .record(duration); + } + } + if (ex != null) { + log.error("[API-PORTAL] Channel send failed", ex); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/PolicyAdvisorFactory.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/PolicyAdvisorFactory.java new file mode 100644 index 00000000..37360507 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/PolicyAdvisorFactory.java @@ -0,0 +1,166 @@ +package com.zt.plat.module.databus.framework.integration.gateway.core; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.policy.AuthPolicyEvaluator; +import com.zt.plat.module.databus.framework.integration.gateway.policy.RateLimitPolicyEvaluator; +import lombok.RequiredArgsConstructor; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_EXECUTION_ERROR; + +/** + * Builds advice chains for steps based on configured policies. + */ +@Component +@RequiredArgsConstructor +public class PolicyAdvisorFactory { + + private final AuthPolicyEvaluator authPolicyEvaluator; + private final RateLimitPolicyEvaluator rateLimitPolicyEvaluator; + + public org.aopalliance.aop.Advice[] buildAdvices(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + List advices = new ArrayList<>(); + advices.add(new AuthPolicyAdvice(aggregate)); + advices.add(new RateLimitPolicyAdvice(aggregate)); + advices.add(createRetryAdvice(stepDefinition)); + return advices.stream().filter(advice -> advice != null).toArray(org.aopalliance.aop.Advice[]::new); + } + + public org.aopalliance.aop.Advice[] buildParallelAdvices(ApiDefinitionAggregate aggregate, Object segment) { + // For parallel segments we reuse the same advice chain (auth + rateLimit once at entry) + return buildAdvices(aggregate, null); + } + + private RequestHandlerRetryAdvice createRetryAdvice(ApiStepDefinition stepDefinition) { + if (stepDefinition == null) { + return null; + } + Object strategyConfig = stepDefinition.getMetadata().get("retryStrategy"); + if (!(strategyConfig instanceof Map configMap)) { + return null; + } + RetryTemplate template = new RetryTemplate(); + SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); + int maxAttempts = asInt(configMap.get("maxAttempts"), 3); + retryPolicy.setMaxAttempts(maxAttempts); + ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); + long initialInterval = asLong(configMap.get("initialInterval"), 200L); + double multiplier = asDouble(configMap.get("multiplier"), 2.0d); + long maxInterval = asLong(configMap.get("maxInterval"), 2000L); + backOffPolicy.setInitialInterval(initialInterval); + backOffPolicy.setMultiplier(multiplier); + backOffPolicy.setMaxInterval(maxInterval); + template.setBackOffPolicy(backOffPolicy); + template.setRetryPolicy(retryPolicy); + RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); + advice.setRetryTemplate(template); + return advice; + } + + private final class AuthPolicyAdvice extends AbstractRequestHandlerAdvice { + private final ApiDefinitionAggregate aggregate; + + private AuthPolicyAdvice(ApiDefinitionAggregate aggregate) { + this.aggregate = aggregate; + } + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, org.springframework.messaging.Message message) { + if (aggregate.getAuthPolicy() != null) { + authPolicyEvaluator.evaluate(aggregate, (ApiInvocationContext) message.getPayload()); + } + try { + return callback.execute(); + } catch (Exception ex) { + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + if (ex instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw ServiceExceptionUtil.exception(API_STEP_EXECUTION_ERROR, ex.getMessage()); + } + } + } + + private final class RateLimitPolicyAdvice extends AbstractRequestHandlerAdvice { + private final ApiDefinitionAggregate aggregate; + + private RateLimitPolicyAdvice(ApiDefinitionAggregate aggregate) { + this.aggregate = aggregate; + } + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, org.springframework.messaging.Message message) { + if (aggregate.getRateLimitPolicy() != null) { + rateLimitPolicyEvaluator.evaluate(aggregate, (ApiInvocationContext) message.getPayload()); + } + try { + return callback.execute(); + } catch (Exception ex) { + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + if (ex instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw ServiceExceptionUtil.exception(API_STEP_EXECUTION_ERROR, ex.getMessage()); + } + } + } + + private int asInt(Object value, int defaultValue) { + if (value instanceof Number number) { + return number.intValue(); + } + if (value instanceof String text) { + try { + return Integer.parseInt(text); + } catch (NumberFormatException ignored) { + // ignore and fall back to default + } + } + return defaultValue; + } + + private long asLong(Object value, long defaultValue) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value instanceof String text) { + try { + return Long.parseLong(text); + } catch (NumberFormatException ignored) { + // ignore and fall back to default + } + } + return defaultValue; + } + + private double asDouble(Object value, double defaultValue) { + if (value instanceof Number number) { + return number.doubleValue(); + } + if (value instanceof String text) { + try { + return Double.parseDouble(text); + } catch (NumberFormatException ignored) { + // ignore and fall back to default + } + } + return defaultValue; + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java new file mode 100644 index 00000000..34a5bdef --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiDefinitionAggregate.java @@ -0,0 +1,40 @@ +package com.zt.plat.module.databus.framework.integration.gateway.domain; + +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import lombok.Builder; +import lombok.Value; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Aggregate representing an API definition with its steps and policies. + */ +@Value +@Builder(toBuilder = true) +public class ApiDefinitionAggregate { + + ApiDefinitionDO definition; + + List steps; + + Map apiLevelTransforms; + + ApiPolicyAuthDO authPolicy; + + ApiPolicyRateLimitDO rateLimitPolicy; + + ApiFlowPublication publication; + + public List getSteps() { + return steps == null ? Collections.emptyList() : steps; + } + + public Map getApiLevelTransforms() { + return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiFlowPublication.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiFlowPublication.java new file mode 100644 index 00000000..b3cba9e4 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiFlowPublication.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.databus.framework.integration.gateway.domain; + +import lombok.Builder; +import lombok.Value; + +/** + * Publication metadata for an API flow. + */ +@Value +@Builder(toBuilder = true) +public class ApiFlowPublication { + + Long id; + + String releaseTag; + + String snapshot; + + String status; + + boolean active; + + String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiStepDefinition.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiStepDefinition.java new file mode 100644 index 00000000..f25bed9b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiStepDefinition.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.databus.framework.integration.gateway.domain; + +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO; +import lombok.Builder; +import lombok.Value; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Domain representation of an orchestration step. + */ +@Value +@Builder(toBuilder = true) +public class ApiStepDefinition { + + ApiStepDO step; + + List transforms; + + Map metadata; + + public List getTransforms() { + return transforms == null ? Collections.emptyList() : transforms; + } + + public Map getMetadata() { + return metadata == null ? Collections.emptyMap() : metadata; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiTransformDefinition.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiTransformDefinition.java new file mode 100644 index 00000000..3bc0edad --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/domain/ApiTransformDefinition.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.databus.framework.integration.gateway.domain; + +import lombok.Builder; +import lombok.Value; + +/** + * Domain representation for transformation expression metadata. + */ +@Value +@Builder(toBuilder = true) +public class ApiTransformDefinition { + + Long id; + + String phase; + + String expressionType; + + String expression; + + String description; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluationContext.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluationContext.java new file mode 100644 index 00000000..fd857cb1 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluationContext.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.Builder; +import lombok.Value; + +import java.util.Map; + +/** + * Context provided to expression engines when evaluating mappings. + */ +@Value +@Builder +public class ExpressionEvaluationContext { + + ApiInvocationContext invocation; + + Object payload; + + Map variables; + + Map headers; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluator.java new file mode 100644 index 00000000..ba436115 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluator.java @@ -0,0 +1,10 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +/** + * Expression evaluator contract. + */ +public interface ExpressionEvaluator { + + Object evaluate(String expression, ExpressionEvaluationContext context) throws Exception; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluatorRegistry.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluatorRegistry.java new file mode 100644 index 00000000..26f43b6c --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionEvaluatorRegistry.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Optional; + +/** + * Registry maintaining expression evaluators per language. + */ +@Component +@RequiredArgsConstructor +public class ExpressionEvaluatorRegistry { + + private final Map evaluators = new EnumMap<>(ExpressionTypeEnum.class); + + public void register(ExpressionTypeEnum type, ExpressionEvaluator evaluator) { + evaluators.put(type, evaluator); + } + + public Optional lookup(ExpressionTypeEnum type) { + return Optional.ofNullable(evaluators.get(type)); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionExecutor.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionExecutor.java new file mode 100644 index 00000000..4fb68902 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionExecutor.java @@ -0,0 +1,51 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Optional; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_EXPRESSION_EVALUATION_FAILED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_EXPRESSION_NO_EVALUATOR; + +/** + * Executes expressions using registered evaluators. + */ +@Component +@RequiredArgsConstructor +public class ExpressionExecutor { + + private final ExpressionEvaluatorRegistry registry; + + public Object evaluate(ExpressionSpec spec, ApiInvocationContext invocation, Object payload, Map headers) throws Exception { + if (spec == null || spec.getExpression() == null) { + return null; + } + ExpressionTypeEnum type = spec.getType(); + return registry.lookup(type) + .orElseThrow(() -> ServiceExceptionUtil.exception(API_EXPRESSION_NO_EVALUATOR, type == null ? "" : type.name())) + .evaluate(spec.getExpression(), ExpressionEvaluationContext.builder() + .invocation(invocation) + .payload(payload) + .variables(invocation.getVariables()) + .headers(headers) + .build()); + } + + public Optional evaluateOptional(ExpressionSpec spec, ApiInvocationContext invocation, Object payload, Map headers) { + try { + return Optional.ofNullable(evaluate(spec, invocation, payload, headers)); + } catch (Exception ex) { + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_EXPRESSION_EVALUATION_FAILED, ex.getMessage()); + } + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpec.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpec.java new file mode 100644 index 00000000..578cb3de --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpec.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import lombok.Builder; +import lombok.Value; + +/** + * Parsed expression specification with language metadata. + */ +@Value +@Builder +public class ExpressionSpec { + + ExpressionTypeEnum type; + + String expression; + + public static ExpressionSpec of(ExpressionTypeEnum type, String expression) { + return ExpressionSpec.builder().type(type).expression(expression).build(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpecParser.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpecParser.java new file mode 100644 index 00000000..4ba4266f --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/ExpressionSpecParser.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import org.springframework.util.StringUtils; + +/** + * Helper to parse unified expression definitions. + */ +public final class ExpressionSpecParser { + + private ExpressionSpecParser() { + } + + public static ExpressionSpec parse(String rawExpression, ExpressionTypeEnum defaultType) { + if (!StringUtils.hasText(rawExpression)) { + return null; + } + String trimmed = rawExpression.trim(); + int separator = trimmed.indexOf("::"); + if (separator < 0) { + return ExpressionSpec.of(defaultType, trimmed); + } + String type = trimmed.substring(0, separator); + String expression = trimmed.substring(separator + 2); + ExpressionTypeEnum expressionTypeEnum = ExpressionTypeEnum.fromValue(type); + if (expressionTypeEnum == null) { + expressionTypeEnum = defaultType; + } + return ExpressionSpec.of(expressionTypeEnum, expression); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsScriptExpressionEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsScriptExpressionEvaluator.java new file mode 100644 index 00000000..42c8a812 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsScriptExpressionEvaluator.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +/** + * Legacy placeholder kept for binary compatibility. JSR-223 scripts are no longer supported. + */ +@Deprecated(forRemoval = true) +public class JsScriptExpressionEvaluator implements ExpressionEvaluator { + + @Override + public Object evaluate(String expression, ExpressionEvaluationContext context) { + throw new UnsupportedOperationException("JSR-223 script expressions are no longer supported. Use JSON expressions instead."); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsonataExpressionEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsonataExpressionEvaluator.java new file mode 100644 index 00000000..a4247866 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/JsonataExpressionEvaluator.java @@ -0,0 +1,50 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +import com.api.jsonata4java.expressions.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_JSONATA_BIND_FAILED; +/** + * JSONata expression evaluator for JSON payload transformation. + * @author chenbowen + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class JsonataExpressionEvaluator implements ExpressionEvaluator { + + private final ObjectMapper objectMapper; + + @Override + public Object evaluate(String expression, ExpressionEvaluationContext context) throws ParseException, EvaluateException, JsonProcessingException, IOException { + Expressions expressions = Expressions.parse(expression); + bindEnvironment(expressions.getEnvironment(), context); + JsonNode payloadNode = objectMapper.valueToTree(context.getPayload()); + JsonNode resultNode = expressions.evaluate(payloadNode); + if (resultNode == null || resultNode.isNull()) { + return null; + } + return objectMapper.treeToValue(resultNode, Object.class); + } + + private void bindEnvironment(FrameEnvironment environment, ExpressionEvaluationContext context) { + if (environment == null) { + return; + } + try { + environment.setVariable("vars", objectMapper.valueToTree(context.getVariables() == null ? java.util.Collections.emptyMap() : context.getVariables())); + environment.setVariable("headers", objectMapper.valueToTree(context.getHeaders() == null ? java.util.Collections.emptyMap() : context.getHeaders())); + environment.setVariable("ctx", objectMapper.valueToTree(context.getInvocation())); + } catch (EvaluateRuntimeException e) { + throw ServiceExceptionUtil.exception(API_JSONATA_BIND_FAILED); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/MvelExpressionEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/MvelExpressionEvaluator.java new file mode 100644 index 00000000..443526f4 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/MvelExpressionEvaluator.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +/** + * Legacy placeholder kept for binary compatibility. MVEL evaluation is no longer supported. + */ +@Deprecated(forRemoval = true) +public class MvelExpressionEvaluator implements ExpressionEvaluator { + + @Override + public Object evaluate(String expression, ExpressionEvaluationContext context) { + throw new UnsupportedOperationException("MVEL expressions are no longer supported. Use JSON expressions instead."); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/SpelExpressionEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/SpelExpressionEvaluator.java new file mode 100644 index 00000000..c6987f40 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/expression/SpelExpressionEvaluator.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.databus.framework.integration.gateway.expression; + +/** + * Legacy placeholder kept for binary compatibility. SpEL evaluation is no longer supported. + */ +@Deprecated(forRemoval = true) +public class SpelExpressionEvaluator implements ExpressionEvaluator { + + @Override + public Object evaluate(String expression, ExpressionEvaluationContext context) { + throw new UnsupportedOperationException("SpEL expressions are no longer supported. Use JSON expressions instead."); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/init/GatewayPolicyMigration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/init/GatewayPolicyMigration.java new file mode 100644 index 00000000..f7c95b6e --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/init/GatewayPolicyMigration.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.databus.framework.integration.gateway.init; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; + +/** + * Applies idempotent data adjustments required for gateway orchestration features + * before integration flows bootstrap. + */ +@Slf4j +@Component("gatewayPolicyMigration") +public class GatewayPolicyMigration { + + @PostConstruct + public void migrate() { + log.info("[API-PORTAL] gateway policy migration skipped; standard header token auth in use"); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiGatewayResponse.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiGatewayResponse.java new file mode 100644 index 00000000..3fe9455d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiGatewayResponse.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.databus.framework.integration.gateway.model; + +import lombok.Builder; +import lombok.Value; + +/** + * Standardized response wrapper returned to external clients. + */ +@Value +@Builder +public class ApiGatewayResponse { + + String code; + + String message; + + Object data; + + String traceId; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java new file mode 100644 index 00000000..3f1a6dd3 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiInvocationContext.java @@ -0,0 +1,115 @@ +package com.zt.plat.module.databus.framework.integration.gateway.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Runtime context for an API invocation flowing through the integration pipeline. + */ +@Getter +@Setter +@ToString +public class ApiInvocationContext { + + private final String requestId; + + private final Instant requestTime; + + private String apiCode; + + private String apiVersion; + + private String tenantId; + + private String httpMethod; + + private String requestPath; + + private Map requestHeaders; + + private Object requestBody; + + private Map requestQueryParams; + + private Map variables; + + private Map attributes; + + private List stepResults; + + private Object responseBody; + + private Integer responseStatus; + + private String responseMessage; + + public ApiInvocationContext() { + this.requestId = UUID.randomUUID().toString(); + this.requestTime = Instant.now(); + this.variables = new HashMap<>(); + this.attributes = new HashMap<>(); + this.stepResults = new ArrayList<>(); + this.requestHeaders = new HashMap<>(); + this.requestQueryParams = new HashMap<>(); + } + + public static ApiInvocationContext create() { + return new ApiInvocationContext(); + } + + public ApiInvocationContext copy() { + ApiInvocationContext copy = new ApiInvocationContext(); + copy.apiCode = this.apiCode; + copy.apiVersion = this.apiVersion; + copy.tenantId = this.tenantId; + copy.httpMethod = this.httpMethod; + copy.requestPath = this.requestPath; + copy.requestBody = this.requestBody; + copy.requestQueryParams.putAll(this.requestQueryParams); + copy.responseBody = this.responseBody; + copy.responseStatus = this.responseStatus; + copy.responseMessage = this.responseMessage; + copy.getRequestHeaders().putAll(this.requestHeaders); + copy.getVariables().putAll(this.variables); + copy.getAttributes().putAll(this.attributes); + return copy; + } + + public void addStepResult(ApiStepResult result) { + this.stepResults.add(result); + } + + public ApiStepResult lastStepResult() { + if (stepResults.isEmpty()) { + return null; + } + return stepResults.get(stepResults.size() - 1); + } + + public void merge(ApiInvocationContext other) { + if (other == null) { + return; + } + this.stepResults.addAll(other.getStepResults()); + this.variables.putAll(other.getVariables()); + this.attributes.putAll(other.getAttributes()); + if (other.getResponseBody() != null) { + this.responseBody = other.getResponseBody(); + } + if (other.getResponseStatus() != null) { + this.responseStatus = other.getResponseStatus(); + } + if (other.getResponseMessage() != null) { + this.responseMessage = other.getResponseMessage(); + } + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiStepResult.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiStepResult.java new file mode 100644 index 00000000..34f347ee --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/model/ApiStepResult.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.databus.framework.integration.gateway.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.Duration; + +/** + * Result of executing a single orchestration step. + */ +@Getter +@Setter +@ToString +@Builder(toBuilder = true) +public class ApiStepResult { + + private Long stepId; + + private String stepType; + + private Object request; + + private Object response; + + private boolean success; + + private Duration elapsed; + + private String errorCode; + + private String errorMessage; + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/AuthPolicyEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/AuthPolicyEvaluator.java new file mode 100644 index 00000000..fdc58a3a --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/AuthPolicyEvaluator.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.databus.framework.integration.gateway.policy; + +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; + +/** + * Performs authentication / authorization policy evaluation for a request. + */ +public interface AuthPolicyEvaluator { + + void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultAuthPolicyEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultAuthPolicyEvaluator.java new file mode 100644 index 00000000..f4f252dc --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultAuthPolicyEvaluator.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.databus.framework.integration.gateway.policy; + +import com.zt.plat.framework.common.biz.system.oauth2.OAuth2TokenCommonApi; +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_AUTH_UNAUTHORIZED; + +/** + * Basic authentication evaluator delegating token validation to system module. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DefaultAuthPolicyEvaluator implements AuthPolicyEvaluator { + + private static final String TOKEN_HEADER = "ZT-Auth-Token"; + + private final OAuth2TokenCommonApi oauth2TokenCommonApi; + + @Override + public void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) { + ApiPolicyAuthDO authPolicy = aggregate.getAuthPolicy(); + if (authPolicy == null) { + return; + } + validateHeaderToken(context); + } + + private void validateHeaderToken(ApiInvocationContext context) { + Object rawHeader = context.getRequestHeaders().get(TOKEN_HEADER); + String token = rawHeader == null ? null : String.valueOf(rawHeader).trim(); + if (!StringUtils.hasText(token)) { + throw ServiceExceptionUtil.exception(API_AUTH_UNAUTHORIZED); + } + try { + oauth2TokenCommonApi.checkAccessToken(token).getCheckedData(); + context.getAttributes().putIfAbsent("accessToken", token); + String bearerToken = token.startsWith("Bearer ") ? token : "Bearer " + token; + context.getRequestHeaders().putIfAbsent("Authorization", bearerToken); + } catch (ServiceException ex) { + log.warn("Access token validation failed: {}", ex.getMessage()); + throw ServiceExceptionUtil.exception(API_AUTH_UNAUTHORIZED); + } catch (RuntimeException ex) { + log.error("Access token validation error", ex); + throw ServiceExceptionUtil.exception(API_AUTH_UNAUTHORIZED); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultRateLimitPolicyEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultRateLimitPolicyEvaluator.java new file mode 100644 index 00000000..030be0ad --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/DefaultRateLimitPolicyEvaluator.java @@ -0,0 +1,61 @@ +package com.zt.plat.module.databus.framework.integration.gateway.policy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EVALUATION_FAILED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_RATE_LIMIT_EXCEEDED; + +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Simple Redis-backed rate limit evaluator supporting fixed window counters. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DefaultRateLimitPolicyEvaluator implements RateLimitPolicyEvaluator { + + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; + + private final ObjectMapper objectMapper; + private final StringRedisTemplate stringRedisTemplate; + + @Override + public void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) { + ApiPolicyRateLimitDO rateLimitDO = aggregate.getRateLimitPolicy(); + if (rateLimitDO == null || !StringUtils.hasText(rateLimitDO.getConfig())) { + return; + } + try { + Map config = objectMapper.readValue(rateLimitDO.getConfig(), MAP_TYPE); + long limit = ((Number) config.getOrDefault("limit", 100)).longValue(); + long windowSeconds = ((Number) config.getOrDefault("windowSeconds", 60)).longValue(); + String key = String.format("databus:api:rl:%s:%s:%s", aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion(), context.getRequestHeaders().getOrDefault("X-Client-Id", "anonymous")); + Long counter = stringRedisTemplate.opsForValue().increment(key); + if (counter != null && counter == 1L) { + stringRedisTemplate.expire(key, Duration.ofSeconds(windowSeconds)); + } + if (counter != null && counter > limit) { + throw ServiceExceptionUtil.exception(API_RATE_LIMIT_EXCEEDED); + } + } catch (JsonProcessingException | DataAccessException ex) { + log.error("Rate limit evaluation failed for api {}", aggregate.getDefinition().getApiCode(), ex); + throw ServiceExceptionUtil.exception(API_RATE_LIMIT_EVALUATION_FAILED); + } + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/RateLimitPolicyEvaluator.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/RateLimitPolicyEvaluator.java new file mode 100644 index 00000000..10f2a2f5 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/policy/RateLimitPolicyEvaluator.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.databus.framework.integration.gateway.policy; + +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; + +/** + * Applies rate limiting decisions for the invocation. + */ +public interface RateLimitPolicyEvaluator { + + void evaluate(ApiDefinitionAggregate aggregate, ApiInvocationContext context); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java new file mode 100644 index 00000000..cbf6e548 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java @@ -0,0 +1,75 @@ +package com.zt.plat.module.databus.framework.integration.gateway.security; + +import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.HmacUtils; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +/** + * Security filter performing IP allow/deny, signature validation, and tenant extraction for the unified portal. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GatewaySecurityFilter extends OncePerRequestFilter { + + private final ApiGatewayProperties properties; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String requestPath = request.getRequestURI(); + if (!pathMatcher.match(properties.getBasePath() + "/**", requestPath)) { + filterChain.doFilter(request, response); + return; + } + if (!isIpAllowed(request)) { + response.sendError(HttpStatus.FORBIDDEN.value(), "IP not allowed"); + return; + } + if (properties.isEnableSignature() && !validateSignature(request)) { + response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid signature"); + return; + } + filterChain.doFilter(request, response); + } + + private boolean isIpAllowed(HttpServletRequest request) { + String remoteIp = request.getRemoteAddr(); + List denied = properties.getDeniedIps(); + if (!CollectionUtils.isEmpty(denied) && denied.contains(remoteIp)) { + return false; + } + List allowed = properties.getAllowedIps(); + return CollectionUtils.isEmpty(allowed) || allowed.contains(remoteIp); + } + + private boolean validateSignature(HttpServletRequest request) { + String headerSignature = request.getHeader(properties.getSignatureHeader()); + if (!StringUtils.hasText(headerSignature)) { + return false; + } + String secret = properties.getSignatureSecret(); + if (!StringUtils.hasText(secret)) { + log.warn("Signature verification enabled but no secret configured"); + return false; + } + String payload = request.getRequestURI() + "|" + (request.getQueryString() == null ? "" : request.getQueryString()); + String computed = HmacUtils.hmacSha256Hex(secret, payload); + return headerSignature.equalsIgnoreCase(computed); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/ApiStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/ApiStepHandler.java new file mode 100644 index 00000000..126989cc --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/ApiStepHandler.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step; + +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import org.springframework.integration.core.GenericHandler; + +/** + * Contract for building a Spring Integration handler for a specific step type. + */ +public interface ApiStepHandler { + + boolean supports(String stepType); + + GenericHandler build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/StepHandlerFactory.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/StepHandlerFactory.java new file mode 100644 index 00000000..6038be14 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/StepHandlerFactory.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ApiStepTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import lombok.RequiredArgsConstructor; +import org.springframework.integration.core.GenericHandler; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_UNSUPPORTED_TYPE; + +/** + * Delegates step handler creation to registered implementations. + */ +@Component +@RequiredArgsConstructor +public class StepHandlerFactory { + + private final List stepHandlers; + + public GenericHandler build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + ApiStepTypeEnum type = ApiStepTypeEnum.valueOf(stepDefinition.getStep().getType().toUpperCase()); + return stepHandlers.stream() + .filter(handler -> handler.supports(type.name())) + .findFirst() + .orElseThrow(() -> ServiceExceptionUtil.exception(API_STEP_UNSUPPORTED_TYPE, type.name())) + .build(aggregate, stepDefinition); + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java new file mode 100644 index 00000000..10323696 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandler.java @@ -0,0 +1,395 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step.impl; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiStepResult; +import com.zt.plat.module.databus.framework.integration.gateway.step.ApiStepHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.integration.core.GenericHandler; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +import java.net.URI; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_HTTP_ENDPOINT_INVALID; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_HTTP_EXECUTION_FAILED; + +/** + * Step handler that performs outbound HTTP calls. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class HttpStepHandler implements ApiStepHandler { + + private final WebClient.Builder webClientBuilder; + private final ExpressionExecutor expressionExecutor; + + private static final Set DEFAULT_FORWARDED_HEADERS = Set.of( + "authorization", + "zt-auth-token", + "tenant-id", + "visit-tenant-id", + "visit-company-id", + "visit-company-name", + "visit-dept-id", + "visit-dept-name" + ); + + @Override + public boolean supports(String stepType) { + return "HTTP".equalsIgnoreCase(stepType); + } + + private HttpRequestPayload coerceRequestPayload(Object evaluated, Object fallbackBody, Map fallbackQuery) { + Map querySnapshot = new LinkedHashMap<>(fallbackQuery); + if (evaluated == null) { + return HttpRequestPayload.of(fallbackBody, querySnapshot); + } + if (evaluated instanceof HttpRequestPayload payload) { + Map mergedQuery = new LinkedHashMap<>(fallbackQuery); + mergedQuery.putAll(payload.queryParams()); + return HttpRequestPayload.of(payload.body(), mergedQuery); + } + if (evaluated instanceof MultiValueMap multiValueMap) { + mergeQueryParams(querySnapshot, multiValueMap); + return HttpRequestPayload.of(fallbackBody, querySnapshot); + } + if (evaluated instanceof Map map) { + Object queryPart = extractCaseInsensitive(map, "query", "queryParams", "params"); + if (queryPart != null) { + mergeQueryParams(querySnapshot, queryPart); + } + boolean explicitBody = containsKeyIgnoreCase(map, "body", "payload"); + Object body = explicitBody + ? Optional.ofNullable(extractCaseInsensitive(map, "body", "payload")).orElse(fallbackBody) + : (queryPart != null ? fallbackBody : evaluated); + if (!explicitBody && queryPart == null) { + return HttpRequestPayload.of(evaluated, querySnapshot); + } + return HttpRequestPayload.of(body, querySnapshot); + } + return HttpRequestPayload.of(evaluated, querySnapshot); + } + + @Override + public GenericHandler build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + return (payload, headers) -> { + Instant start = Instant.now(); + HttpRequestPayload requestPayload = null; + boolean supportsBody = false; + try { + HttpCallSpec callSpec = parseEndpoint(stepDefinition.getStep().getTargetEndpoint()); + supportsBody = supportsRequestBody(callSpec.method); + requestPayload = mapRequest(stepDefinition, payload, headers); + if (!supportsBody && requestPayload != null && requestPayload.body() != null) { + requestPayload = HttpRequestPayload.of(null, requestPayload.queryParams()); + } + Map headerMap = resolveHeaders(stepDefinition, payload); + Duration timeout = resolveTimeout(stepDefinition); + WebClient client = webClientBuilder.build(); + WebClient.RequestHeadersSpec requestSpec = buildRequest(client, callSpec, requestPayload, headerMap, supportsBody); + Mono responseMono = requestSpec.retrieve().bodyToMono(Object.class); + Object response = timeout == null ? responseMono.block() : responseMono.block(timeout); + payload.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .request(requestPayload == null ? null : requestPayload.snapshot(supportsBody)) + .response(response) + .success(true) + .elapsed(Duration.between(start, Instant.now())) + .build()); + applyResponseMapping(stepDefinition, payload, headers, response); + } catch (Exception ex) { + payload.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .request(requestPayload == null ? null : requestPayload.snapshot(supportsBody)) + .success(false) + .errorMessage(ex.getMessage()) + .elapsed(Duration.between(start, Instant.now())) + .build()); + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_STEP_HTTP_EXECUTION_FAILED, ex.getMessage()); + } + return payload; + }; + } + + private HttpRequestPayload mapRequest(ApiStepDefinition stepDefinition, ApiInvocationContext context, Map headers) throws Exception { + ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getRequestMappingExpr(), ExpressionTypeEnum.JSON); + Map baseQuery = new LinkedHashMap<>(context.getRequestQueryParams()); + Object fallbackBody = context.getRequestBody(); + if (spec == null) { + return HttpRequestPayload.of(fallbackBody, baseQuery); + } + Object evaluated = expressionExecutor.evaluate(spec, context, fallbackBody, headers); + return coerceRequestPayload(evaluated, fallbackBody, baseQuery); + } + + private void applyResponseMapping(ApiStepDefinition stepDefinition, ApiInvocationContext context, Map headers, Object response) throws Exception { + ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getResponseMappingExpr(), ExpressionTypeEnum.JSON); + if (spec == null) { + context.setResponseBody(response); + return; + } + Object mapped = expressionExecutor.evaluate(spec, context, response, headers); + if (mapped instanceof Map map) { + map.forEach((key, value) -> context.getVariables().put(String.valueOf(key), value)); + } else { + context.setResponseBody(mapped); + } + } + + private Map resolveHeaders(ApiStepDefinition stepDefinition, ApiInvocationContext context) throws Exception { + Map resolved = new LinkedHashMap<>(); + context.getRequestHeaders().forEach((key, value) -> { + if (shouldForwardHeader(key) && value != null) { + resolved.put(key, String.valueOf(value)); + } + }); + + Map configured = extractConfiguredHeaders(stepDefinition, context); + resolved.putAll(configured); + return resolved; + } + + private Map extractConfiguredHeaders(ApiStepDefinition stepDefinition, ApiInvocationContext context) throws Exception { + Object headerConfig = stepDefinition.getMetadata().getOrDefault("headers", Collections.emptyMap()); + if (headerConfig instanceof Map map) { + return toStringMap(map); + } + ExpressionSpec spec = ExpressionSpecParser.parse((String) stepDefinition.getMetadata().get("headerExpr"), ExpressionTypeEnum.JSON); + if (spec == null) { + return Collections.emptyMap(); + } + Object evaluated = expressionExecutor.evaluate(spec, context, context.getRequestBody(), Collections.emptyMap()); + if (evaluated instanceof Map map) { + return toStringMap(map); + } + return Collections.emptyMap(); + } + + private boolean shouldForwardHeader(String headerName) { + if (!StringUtils.hasText(headerName)) { + return false; + } + return DEFAULT_FORWARDED_HEADERS.contains(headerName.toLowerCase(Locale.ROOT)); + } + + private Map toStringMap(Map map) { + if (map == null) { + return Collections.emptyMap(); + } + Map result = new java.util.LinkedHashMap<>(); + map.forEach((key, value) -> { + if (value != null) { + result.put(String.valueOf(key), String.valueOf(value)); + } + }); + return result; + } + + private Duration resolveTimeout(ApiStepDefinition stepDefinition) { + Long timeout = stepDefinition.getStep().getTimeout(); + if (timeout == null || timeout <= 0) { + return Duration.ofSeconds(5); + } + return Duration.ofMillis(timeout); + } + + private HttpCallSpec parseEndpoint(String targetEndpoint) { + if (!StringUtils.hasText(targetEndpoint)) { + throw ServiceExceptionUtil.exception(API_STEP_HTTP_ENDPOINT_INVALID); + } + String trimmed = targetEndpoint.trim(); + String method = "POST"; + String url = trimmed; + int spaceIndex = trimmed.indexOf(' '); + if (spaceIndex > 0) { + method = trimmed.substring(0, spaceIndex).toUpperCase(); + url = trimmed.substring(spaceIndex + 1); + } + return new HttpCallSpec(HttpMethod.valueOf(method), url); + } + + private record HttpCallSpec(HttpMethod method, String url) { + } + + private void applyQueryParams(UriComponentsBuilder builder, Map queryParams) { + if (queryParams == null || queryParams.isEmpty()) { + return; + } + queryParams.forEach((key, value) -> addQueryParam(builder, key, value)); + } + + private void addQueryParam(UriComponentsBuilder builder, String key, Object value) { + if (!StringUtils.hasText(key)) { + return; + } + if (value == null) { + builder.queryParam(key); + return; + } + if (value instanceof MultiValueMap multiValueMap) { + multiValueMap.forEach((innerKey, values) -> addQueryParam(builder, String.valueOf(innerKey), values)); + return; + } + if (value instanceof Iterable iterable) { + iterable.forEach(item -> addQueryParam(builder, key, item)); + return; + } + if (value.getClass().isArray()) { + int length = java.lang.reflect.Array.getLength(value); + for (int i = 0; i < length; i++) { + addQueryParam(builder, key, java.lang.reflect.Array.get(value, i)); + } + return; + } + builder.queryParam(key, value); + } + + private void mergeQueryParams(Map target, Object addition) { + if (addition == null || target == null) { + return; + } + if (addition instanceof MultiValueMap multiValueMap) { + multiValueMap.forEach((key, values) -> { + if (values == null) { + return; + } + if (values.size() == 1) { + target.put(String.valueOf(key), values.get(0)); + return; + } + target.put(String.valueOf(key), new ArrayList<>(values)); + }); + return; + } + if (addition instanceof Map map) { + map.forEach((key, value) -> target.put(String.valueOf(key), value)); + } + } + + private WebClient.RequestHeadersSpec buildRequest(WebClient client, HttpCallSpec callSpec, HttpRequestPayload requestPayload, Map headerMap, boolean hasBody) { + URI uri = buildUri(callSpec, requestPayload, hasBody); + WebClient.RequestBodyUriSpec uriSpec = client.method(callSpec.method); + WebClient.RequestHeadersSpec headersSpec = uriSpec.uri(uri) + .accept(MediaType.APPLICATION_JSON) + .headers(httpHeaders -> headerMap.forEach(httpHeaders::add)); + if (hasBody) { + Object body = requestPayload.body() == null ? Collections.emptyMap() : requestPayload.body(); + headersSpec = ((WebClient.RequestBodySpec) headersSpec) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(body)); + } + return headersSpec; + } + + private URI buildUri(HttpCallSpec callSpec, HttpRequestPayload requestPayload, boolean hasBody) { + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(callSpec.url); + Map queryParams = new LinkedHashMap<>(requestPayload.queryParams()); + if (!hasBody) { + mergeQueryParams(queryParams, requestPayload.body()); + } + applyQueryParams(builder, queryParams); + return builder.build(true).toUri(); + } + + private Object extractCaseInsensitive(Map source, String... keys) { + if (source == null || source.isEmpty()) { + return null; + } + for (String key : keys) { + for (Map.Entry entry : source.entrySet()) { + if (key.equalsIgnoreCase(String.valueOf(entry.getKey()))) { + return entry.getValue(); + } + } + } + return null; + } + + private boolean containsKeyIgnoreCase(Map source, String... keys) { + if (source == null || source.isEmpty()) { + return false; + } + for (String key : keys) { + for (Object entryKey : source.keySet()) { + if (key.equalsIgnoreCase(String.valueOf(entryKey))) { + return true; + } + } + } + return false; + } + + private record HttpRequestPayload(Object body, Map queryParams) { + + private HttpRequestPayload { + Map safeQuery = queryParams == null + ? Collections.emptyMap() + : Collections.unmodifiableMap(new LinkedHashMap<>(queryParams)); + queryParams = safeQuery; + } + + static HttpRequestPayload of(Object body, Map queryParams) { + return new HttpRequestPayload(body, queryParams); + } + + Object snapshot(boolean includeBody) { + boolean hasQuery = queryParams != null && !queryParams.isEmpty(); + boolean hasBody = includeBody && body != null; + if (hasQuery && hasBody) { + Map composite = new LinkedHashMap<>(); + composite.put("query", new LinkedHashMap<>(queryParams)); + composite.put("body", body); + return composite; + } + if (hasQuery) { + return new LinkedHashMap<>(queryParams); + } + if (hasBody) { + return body; + } + return includeBody ? body : null; + } + } + + private boolean supportsRequestBody(HttpMethod method) { + if (method == null) { + return true; + } + return !(HttpMethod.GET.equals(method) + || HttpMethod.DELETE.equals(method) + || HttpMethod.HEAD.equals(method) + || HttpMethod.OPTIONS.equals(method) + || HttpMethod.TRACE.equals(method)); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/RpcStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/RpcStepHandler.java new file mode 100644 index 00000000..bec152b2 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/RpcStepHandler.java @@ -0,0 +1,154 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step.impl; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiStepResult; +import com.zt.plat.module.databus.framework.integration.gateway.step.ApiStepHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.core.GenericHandler; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Map; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_RPC_ENDPOINT_INVALID; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_RPC_EXECUTION_FAILED; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_RPC_METHOD_NOT_FOUND; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_RPC_UNSUPPORTED_SIGNATURE; + +/** + * Step handler performing intra-application RPC invocations via Spring beans. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class RpcStepHandler implements ApiStepHandler { + + private final ApplicationContext applicationContext; + private final ExpressionExecutor expressionExecutor; + + @Override + public boolean supports(String stepType) { + return "RPC".equalsIgnoreCase(stepType); + } + + @Override + public GenericHandler build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + BeanMethod beanMethod = parseEndpoint(stepDefinition.getStep().getTargetEndpoint()); + ExpressionSpec requestSpec = ExpressionSpecParser.parse(stepDefinition.getStep().getRequestMappingExpr(), ExpressionTypeEnum.JSON); + ExpressionSpec responseSpec = ExpressionSpecParser.parse(stepDefinition.getStep().getResponseMappingExpr(), ExpressionTypeEnum.JSON); + return (context, headers) -> { + Instant start = Instant.now(); + try { + Object arguments = requestSpec == null ? context.getRequestBody() : expressionExecutor.evaluate(requestSpec, context, context.getRequestBody(), headers); + Object result = invoke(beanMethod, arguments, context); + context.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .request(arguments) + .response(result) + .success(true) + .elapsed(Duration.between(start, Instant.now())) + .build()); + if (result instanceof Map map) { + map.forEach((key, value) -> context.getVariables().put(String.valueOf(key), value)); + } else if (result != null) { + context.setResponseBody(result); + } + } catch (Exception ex) { + context.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .success(false) + .errorMessage(ex.getMessage()) + .elapsed(Duration.between(start, Instant.now())) + .build()); + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_STEP_RPC_EXECUTION_FAILED, ex.getMessage()); + } + return context; + }; + } + + private Object invoke(BeanMethod beanMethod, Object argument, ApiInvocationContext context) throws Exception { + Object bean = applicationContext.getBean(beanMethod.beanName); + Method method = resolveMethod(bean, beanMethod.methodName, argument); + if (method == null) { + throw ServiceExceptionUtil.exception(API_STEP_RPC_METHOD_NOT_FOUND, beanMethod.beanName, beanMethod.methodName); + } + ReflectionUtils.makeAccessible(method); + if (method.getParameterCount() == 0) { + return method.invoke(bean); + } + if (method.getParameterCount() == 1) { + Class parameterType = method.getParameterTypes()[0]; + if (ApiInvocationContext.class.isAssignableFrom(parameterType)) { + return method.invoke(bean, context); + } + return method.invoke(bean, convertArgument(argument, parameterType)); + } + if (method.getParameterCount() == 2) { + return method.invoke(bean, context, argument); + } + throw ServiceExceptionUtil.exception(API_STEP_RPC_UNSUPPORTED_SIGNATURE, beanMethod.methodName); + } + + private Method resolveMethod(Object bean, String methodName, Object argument) { + Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass()); + return Arrays.stream(methods) + .filter(method -> method.getName().equals(methodName)) + .filter(method -> method.getParameterCount() <= 2) + .findFirst() + .orElse(null); + } + + private Object convertArgument(Object argument, Class targetType) { + if (argument == null) { + return null; + } + if (targetType.isAssignableFrom(argument.getClass())) { + return argument; + } + if (targetType.equals(String.class)) { + return String.valueOf(argument); + } + if (Number.class.isAssignableFrom(targetType) && argument instanceof Number number) { + if (targetType.equals(Integer.class)) { + return number.intValue(); + } + if (targetType.equals(Long.class)) { + return number.longValue(); + } + if (targetType.equals(Double.class)) { + return number.doubleValue(); + } + } + return argument; + } + + private BeanMethod parseEndpoint(String endpoint) { + if (!endpoint.contains("#")) { + throw ServiceExceptionUtil.exception(API_STEP_RPC_ENDPOINT_INVALID); + } + String[] parts = endpoint.split("#", 2); + return new BeanMethod(parts[0], parts[1]); + } + + private record BeanMethod(String beanName, String methodName) { + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/ScriptStepHandler.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/ScriptStepHandler.java new file mode 100644 index 00000000..fc7516a5 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/ScriptStepHandler.java @@ -0,0 +1,74 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step.impl; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.databus.enums.gateway.ExpressionTypeEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpec; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionSpecParser; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiStepResult; +import com.zt.plat.module.databus.framework.integration.gateway.step.ApiStepHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.integration.core.GenericHandler; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.Instant; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_STEP_SCRIPT_EXECUTION_FAILED; + +/** + * Step handler executing JSON-based scripted expressions. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ScriptStepHandler implements ApiStepHandler { + + private final ExpressionExecutor expressionExecutor; + + @Override + public boolean supports(String stepType) { + return "SCRIPT".equalsIgnoreCase(stepType); + } + + @Override + public GenericHandler build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition) { + ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getTargetEndpoint(), ExpressionTypeEnum.JSON); + return (context, headers) -> { + Instant start = Instant.now(); + try { + Object result = expressionExecutor.evaluate(spec, context, context.getRequestBody(), headers); + context.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .success(true) + .response(result) + .elapsed(Duration.between(start, Instant.now())) + .build()); + if (result instanceof java.util.Map map) { + map.forEach((key, value) -> context.getVariables().put(String.valueOf(key), value)); + } else if (result != null) { + context.setResponseBody(result); + } + } catch (Exception ex) { + context.addStepResult(ApiStepResult.builder() + .stepId(stepDefinition.getStep().getId()) + .stepType(stepDefinition.getStep().getType()) + .success(false) + .errorMessage(ex.getMessage()) + .elapsed(Duration.between(start, Instant.now())) + .build()); + if (ex instanceof ServiceException serviceException) { + throw serviceException; + } + throw ServiceExceptionUtil.exception(API_STEP_SCRIPT_EXECUTION_FAILED, ex.getMessage()); + } + return context; + }; + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionService.java new file mode 100644 index 00000000..85462294 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionService.java @@ -0,0 +1,57 @@ +package com.zt.plat.module.databus.service.gateway; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; + +import java.util.List; +import java.util.Optional; + +/** + * Service providing access to API definitions and their orchestration metadata. + */ +public interface ApiDefinitionService { + + /** + * Load all active API definitions for bootstrap. + */ + List loadActiveDefinitions(); + + /** + * Lookup API definition by code and version. + */ + Optional findByCodeAndVersion(String apiCode, String version); + + /** + * Refresh a specific definition by evicting cache and reloading from DB. + */ + Optional refresh(String apiCode, String version); + + /** + * Lookup API definition aggregate by primary key. + */ + Optional findById(Long id); + + /** + * Query API definitions with pagination. + */ + PageResult getPage(ApiDefinitionPageReqVO reqVO); + + /** + * Create a new API definition with orchestration metadata. + */ + Long create(ApiDefinitionSaveReqVO reqVO); + + /** + * Update an existing API definition with orchestration metadata. + */ + void update(ApiDefinitionSaveReqVO reqVO); + + /** + * Delete API definition and related metadata. + */ + void delete(Long id); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyAuthService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyAuthService.java new file mode 100644 index 00000000..7768186b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyAuthService.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.databus.service.gateway; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; + +import java.util.List; +import java.util.Optional; + +/** + * Authentication policy operations. + */ +public interface ApiPolicyAuthService { + + /** + * Paginate policies. + */ + PageResult getPage(ApiPolicyPageReqVO reqVO); + + /** + * Fetch all active policies for dropdowns. + */ + List getSimpleList(); + + /** + * Find policy detail. + */ + Optional get(Long id); + + /** + * Create policy definition. + */ + Long create(ApiPolicySaveReqVO reqVO); + + /** + * Update policy definition. + */ + void update(ApiPolicySaveReqVO reqVO); + + /** + * Delete policy definition. + */ + void delete(Long id); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyRateLimitService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyRateLimitService.java new file mode 100644 index 00000000..1cb08ae7 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/ApiPolicyRateLimitService.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.databus.service.gateway; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; + +import java.util.List; +import java.util.Optional; + +/** + * Rate limit policy operations. + */ +public interface ApiPolicyRateLimitService { + + /** + * Paginate policies. + */ + PageResult getPage(ApiPolicyPageReqVO reqVO); + + /** + * Fetch all active policies for dropdowns. + */ + List getSimpleList(); + + /** + * Find policy detail. + */ + Optional get(Long id); + + /** + * Create policy definition. + */ + Long create(ApiPolicySaveReqVO reqVO); + + /** + * Update policy definition. + */ + void update(ApiPolicySaveReqVO reqVO); + + /** + * Delete policy definition. + */ + void delete(Long id); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiDefinitionServiceImpl.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiDefinitionServiceImpl.java new file mode 100644 index 00000000..01021e42 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiDefinitionServiceImpl.java @@ -0,0 +1,431 @@ +package com.zt.plat.module.databus.service.gateway.impl; + +import cn.hutool.core.collection.CollUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiFlowPublishDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiFlowPublishMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyAuthMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper; +import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiFlowPublication; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransformDefinition; +import com.zt.plat.module.databus.service.gateway.ApiDefinitionService; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ApiDefinitionServiceImpl implements ApiDefinitionService { + + private static final String REDIS_CACHE_PREFIX = "databus:api:def:"; + + private final ApiDefinitionMapper apiDefinitionMapper; + private final ApiStepMapper apiStepMapper; + private final ApiTransformMapper apiTransformMapper; + private final ApiPolicyAuthMapper apiPolicyAuthMapper; + private final ApiPolicyRateLimitMapper apiPolicyRateLimitMapper; + private final ApiFlowPublishMapper apiFlowPublishMapper; + private final ObjectMapper objectMapper; + private final StringRedisTemplate stringRedisTemplate; + + private LoadingCache> definitionCache; + + @PostConstruct + public void initCache() { + definitionCache = Caffeine.newBuilder() + .maximumSize(512) + .expireAfterWrite(Duration.ofMinutes(5)) + .build(this::loadAggregateSync); + } + + @Override + public List loadActiveDefinitions() { + List definitions = apiDefinitionMapper.selectActiveDefinitions(Collections.singletonList(ApiStatusEnum.ONLINE.getStatus())); + if (CollUtil.isEmpty(definitions)) { + return Collections.emptyList(); + } + List aggregates = new ArrayList<>(definitions.size()); + for (ApiDefinitionDO definition : definitions) { + aggregates.add(buildAggregate(definition)); + } + return aggregates; + } + + @Override + public Optional findByCodeAndVersion(String apiCode, String version) { + String cacheKey = buildCacheKey(apiCode, version); + try { + return definitionCache.get(cacheKey); + } catch (RuntimeException ex) { + throw ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND); + } + } + + @Override + public Optional refresh(String apiCode, String version) { + String cacheKey = buildCacheKey(apiCode, version); + definitionCache.invalidate(cacheKey); + deleteRedis(cacheKey); + return findByCodeAndVersion(apiCode, version); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(apiDefinitionMapper.selectById(id)) + .map(this::buildAggregate); + } + + @Override + public PageResult getPage(ApiDefinitionPageReqVO reqVO) { + return apiDefinitionMapper.selectPage(reqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long create(ApiDefinitionSaveReqVO reqVO) { + validateDuplication(reqVO, null); + validateStructure(reqVO); + validatePolicies(reqVO); + + ApiDefinitionDO definition = buildDefinitionDO(reqVO, null); + apiDefinitionMapper.insert(definition); + Long apiId = definition.getId(); + + persistApiLevelTransforms(apiId, reqVO.getApiLevelTransforms()); + persistSteps(apiId, reqVO.getSteps()); + return apiId; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(ApiDefinitionSaveReqVO reqVO) { + ApiDefinitionDO existing = ensureExists(reqVO.getId()); + + validateDuplication(reqVO, existing.getId()); + validateStructure(reqVO); + validatePolicies(reqVO); + + ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing); + apiDefinitionMapper.updateById(updateObj); + + invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion()); + apiTransformMapper.deleteByApiId(existing.getId()); + apiStepMapper.deleteByApiId(existing.getId()); + persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms()); + persistSteps(existing.getId(), reqVO.getSteps()); + invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + ApiDefinitionDO existing = ensureExists(id); + invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion()); + apiTransformMapper.deleteByApiId(id); + apiStepMapper.deleteByApiId(id); + apiDefinitionMapper.deleteById(id); + } + + private Optional loadAggregateSync(String cacheKey) { + Optional cached = loadFromRedis(cacheKey); + if (cached.isPresent()) { + return cached; + } + String[] parts = cacheKey.split(":"); + String apiCode = parts[1]; + String version = parts[2]; + Optional aggregate = apiDefinitionMapper.selectByCodeAndVersion(apiCode, version) + .filter(definition -> ApiStatusEnum.isOnline(definition.getStatus())) + .map(this::buildAggregate); + aggregate.ifPresent(value -> persistToRedis(cacheKey, value)); + return aggregate; + } + + private Optional loadFromRedis(String cacheKey) { + try { + String json = stringRedisTemplate.opsForValue().get(REDIS_CACHE_PREFIX + cacheKey); + if (!StringUtils.hasText(json)) { + return Optional.empty(); + } + ApiDefinitionAggregate aggregate = objectMapper.readValue(json, ApiDefinitionAggregate.class); + return Optional.of(aggregate); + } catch (JsonProcessingException | DataAccessException ex) { + log.warn("Failed to deserialize API definition aggregate from redis for key {}", cacheKey, ex); + return Optional.empty(); + } + } + + private void persistToRedis(String cacheKey, ApiDefinitionAggregate aggregate) { + try { + String json = objectMapper.writeValueAsString(aggregate); + stringRedisTemplate.opsForValue().set(REDIS_CACHE_PREFIX + cacheKey, json, 5, TimeUnit.MINUTES); + } catch (JsonProcessingException | DataAccessException ex) { + log.warn("Failed to persist API definition aggregate to redis for key {}", cacheKey, ex); + } + } + + private void deleteRedis(String cacheKey) { + try { + stringRedisTemplate.delete(REDIS_CACHE_PREFIX + cacheKey); + } catch (DataAccessException ex) { + log.warn("Failed to delete API definition aggregate from redis for key {}", cacheKey, ex); + } + } + + private String buildCacheKey(String apiCode, String version) { + Long tenantId = TenantContextHolder.getTenantId(); + return buildCacheKeyForTenant(tenantId, apiCode, version); + } + + private ApiDefinitionAggregate buildAggregate(ApiDefinitionDO definition) { + List stepDOS = apiStepMapper.selectByApiId(definition.getId()); + List stepDefinitions = new ArrayList<>(stepDOS.size()); + for (ApiStepDO stepDO : stepDOS) { + List transforms = convertTransforms(apiTransformMapper.selectByStepId(stepDO.getId())); + Map metadata = new LinkedHashMap<>(); + metadata.put("retryStrategy", parseJson(stepDO.getRetryStrategy())); + metadata.put("fallbackStrategy", parseJson(stepDO.getFallbackStrategy())); + metadata.put("timeout", stepDO.getTimeout()); + metadata.put("stopOnError", stepDO.getStopOnError()); + ApiStepDefinition stepDefinition = ApiStepDefinition.builder() + .step(stepDO) + .transforms(transforms) + .metadata(metadata) + .build(); + stepDefinitions.add(stepDefinition); + } + Map apiTransforms = new LinkedHashMap<>(); + for (ApiTransformDefinition transform : convertTransforms(apiTransformMapper.selectApiLevelTransforms(definition.getId()))) { + apiTransforms.put(transform.getPhase(), transform); + } + ApiPolicyAuthDO authPolicy = Optional.ofNullable(definition.getAuthPolicyId()) + .map(apiPolicyAuthMapper::selectById) + .orElse(null); + ApiPolicyRateLimitDO rateLimitPolicy = Optional.ofNullable(definition.getRateLimitId()) + .map(apiPolicyRateLimitMapper::selectById) + .orElse(null); + ApiFlowPublication publication = apiFlowPublishMapper.selectActiveByApiId(definition.getId()) + .map(this::convertPublication) + .orElse(null); + return ApiDefinitionAggregate.builder() + .definition(definition) + .steps(stepDefinitions) + .apiLevelTransforms(apiTransforms) + .authPolicy(authPolicy) + .rateLimitPolicy(rateLimitPolicy) + .publication(publication) + .build(); + } + + private List convertTransforms(List transformDOS) { + if (CollUtil.isEmpty(transformDOS)) { + return Collections.emptyList(); + } + List definitions = new ArrayList<>(transformDOS.size()); + for (ApiTransformDO transformDO : transformDOS) { + definitions.add(ApiTransformDefinition.builder() + .id(transformDO.getId()) + .phase(transformDO.getPhase()) + .expression(transformDO.getExpression()) + .expressionType(transformDO.getExpressionType()) + .description(transformDO.getDescription()) + .build()); + } + return definitions; + } + + private Map parseJson(String json) { + if (!StringUtils.hasText(json)) { + return Collections.emptyMap(); + } + try { + return objectMapper.readValue(json, new TypeReference>() {}); + } catch (JsonProcessingException ex) { + log.warn("Failed to parse configuration JSON: {}", json, ex); + return Collections.emptyMap(); + } + } + + private ApiFlowPublication convertPublication(ApiFlowPublishDO publishDO) { + return ApiFlowPublication.builder() + .id(publishDO.getId()) + .releaseTag(publishDO.getReleaseTag()) + .snapshot(publishDO.getSnapshot()) + .status(publishDO.getStatus()) + .active(Boolean.TRUE.equals(publishDO.getActive())) + .description(publishDO.getDescription()) + .build(); + } + + private ApiDefinitionDO buildDefinitionDO(ApiDefinitionSaveReqVO reqVO, ApiDefinitionDO existing) { + ApiDefinitionDO definition = BeanUtils.toBean(reqVO, ApiDefinitionDO.class); + if (existing == null) { + definition.setId(null); + definition.setTenantId(resolveTenantIdentifier()); + definition.setDeleted(Boolean.FALSE); + } else { + definition.setId(existing.getId()); + definition.setTenantId(existing.getTenantId()); + definition.setDeleted(existing.getDeleted()); + } + return definition; + } + + private void persistApiLevelTransforms(Long apiId, List transforms) { + if (CollUtil.isEmpty(transforms)) { + return; + } + for (ApiDefinitionTransformSaveReqVO transformVO : transforms) { + ApiTransformDO transformDO = BeanUtils.toBean(transformVO, ApiTransformDO.class); + transformDO.setId(null); + transformDO.setApiId(apiId); + transformDO.setStepId(null); + applyTenantDefaults(transformDO); + apiTransformMapper.insert(transformDO); + } + } + + private void persistSteps(Long apiId, List steps) { + if (CollUtil.isEmpty(steps)) { + return; + } + List ordered = new ArrayList<>(steps); + ordered.sort(Comparator.comparing(ApiDefinitionStepSaveReqVO::getStepOrder)); + for (ApiDefinitionStepSaveReqVO stepVO : ordered) { + ApiStepDO stepDO = BeanUtils.toBean(stepVO, ApiStepDO.class); + stepDO.setId(null); + stepDO.setApiId(apiId); + applyTenantDefaults(stepDO); + apiStepMapper.insert(stepDO); + persistStepTransforms(apiId, stepDO.getId(), stepVO.getTransforms()); + } + } + + private void persistStepTransforms(Long apiId, Long stepId, List transforms) { + if (CollUtil.isEmpty(transforms)) { + return; + } + for (ApiDefinitionTransformSaveReqVO transformVO : transforms) { + ApiTransformDO transformDO = BeanUtils.toBean(transformVO, ApiTransformDO.class); + transformDO.setId(null); + transformDO.setApiId(apiId); + transformDO.setStepId(stepId); + applyTenantDefaults(transformDO); + apiTransformMapper.insert(transformDO); + } + } + + private void applyTenantDefaults(T entity) { + entity.setTenantId(resolveTenantIdentifier()); + entity.setDeleted(Boolean.FALSE); + } + + private ApiDefinitionDO ensureExists(Long id) { + if (id == null) { + throw ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND); + } + ApiDefinitionDO definition = apiDefinitionMapper.selectById(id); + if (definition == null || Boolean.TRUE.equals(definition.getDeleted())) { + throw ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND); + } + return definition; + } + + private void validateDuplication(ApiDefinitionSaveReqVO reqVO, Long currentId) { + apiDefinitionMapper.selectByCodeAndVersion(reqVO.getApiCode(), reqVO.getVersion()) + .filter(definition -> currentId == null || !Objects.equals(definition.getId(), currentId)) + .ifPresent(definition -> { throw ServiceExceptionUtil.exception(API_DEFINITION_DUPLICATE); }); + } + + private void validateStructure(ApiDefinitionSaveReqVO reqVO) { + if (CollUtil.isEmpty(reqVO.getSteps())) { + throw ServiceExceptionUtil.exception(API_DEFINITION_STEP_EMPTY); + } + Set orders = new HashSet<>(); + for (ApiDefinitionStepSaveReqVO step : reqVO.getSteps()) { + Integer order = step.getStepOrder(); + if (order == null || !orders.add(order)) { + throw ServiceExceptionUtil.exception(API_DEFINITION_STEP_ORDER_DUPLICATE); + } + validateTransformPhases(step.getTransforms()); + } + validateTransformPhases(reqVO.getApiLevelTransforms()); + } + + private void validateTransformPhases(List transforms) { + if (CollUtil.isEmpty(transforms)) { + return; + } + Set phases = new HashSet<>(); + for (ApiDefinitionTransformSaveReqVO transform : transforms) { + String phase = transform.getPhase(); + if (!StringUtils.hasText(phase) || !phases.add(phase.toUpperCase(Locale.ROOT))) { + throw ServiceExceptionUtil.exception(API_TRANSFORM_PHASE_DUPLICATE); + } + } + } + + private void validatePolicies(ApiDefinitionSaveReqVO reqVO) { + if (reqVO.getAuthPolicyId() != null && apiPolicyAuthMapper.selectById(reqVO.getAuthPolicyId()) == null) { + throw ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND); + } + if (reqVO.getRateLimitId() != null && apiPolicyRateLimitMapper.selectById(reqVO.getRateLimitId()) == null) { + throw ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND); + } + } + + private Long resolveTenantIdentifier() { + return TenantContextHolder.getTenantId(); + } + + private void invalidateCache(Long tenantId, String apiCode, String version) { + if (!StringUtils.hasText(apiCode) || !StringUtils.hasText(version)) { + return; + } + String cacheKey = buildCacheKeyForTenant(tenantId, apiCode, version); + definitionCache.invalidate(cacheKey); + deleteRedis(cacheKey); + } + + private String buildCacheKeyForTenant(Long tenantId, String apiCode, String version) { + String tenantPart = tenantId == null ? "global" : tenantId.toString(); + return tenantPart + ":" + apiCode.toLowerCase(Locale.ROOT) + ":" + version; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyAuthServiceImpl.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyAuthServiceImpl.java new file mode 100644 index 00000000..00a9e953 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyAuthServiceImpl.java @@ -0,0 +1,98 @@ +package com.zt.plat.module.databus.service.gateway.impl; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyAuthMapper; +import com.zt.plat.module.databus.service.gateway.ApiPolicyAuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.Optional; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_IN_USE; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class ApiPolicyAuthServiceImpl implements ApiPolicyAuthService { + + private final ApiPolicyAuthMapper authMapper; + private final ApiDefinitionMapper apiDefinitionMapper; + + @Override + public PageResult getPage(ApiPolicyPageReqVO reqVO) { + return authMapper.selectPage(reqVO); + } + + @Override + public List getSimpleList() { + return authMapper.selectSimpleList(); + } + + @Override + public Optional get(Long id) { + if (id == null) { + return Optional.empty(); + } + return Optional.ofNullable(authMapper.selectById(id)) + .filter(policy -> !Boolean.TRUE.equals(policy.getDeleted())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long create(ApiPolicySaveReqVO reqVO) { + ApiPolicyAuthDO policy = new ApiPolicyAuthDO(); + apply(reqVO, policy); + policy.setId(null); + policy.setTenantId(TenantContextHolder.getTenantId()); + policy.setDeleted(Boolean.FALSE); + authMapper.insert(policy); + return policy.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(ApiPolicySaveReqVO reqVO) { + ApiPolicyAuthDO existing = ensureExists(reqVO.getId()); + apply(reqVO, existing); + authMapper.updateById(existing); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + ApiPolicyAuthDO existing = ensureExists(id); + Long referenceCount = apiDefinitionMapper.selectCountByAuthPolicyId(existing.getId()); + if (referenceCount != null && referenceCount > 0) { + throw ServiceExceptionUtil.exception(API_POLICY_IN_USE); + } + authMapper.deleteById(existing.getId()); + } + + private ApiPolicyAuthDO ensureExists(Long id) { + Assert.notNull(id, "策略编号不能为空"); + return get(id).orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND)); + } + + private void apply(ApiPolicySaveReqVO reqVO, ApiPolicyAuthDO target) { + target.setName(StrUtil.trim(reqVO.getName())); + target.setType(StrUtil.trim(reqVO.getType())); + target.setConfig(normalizeNullable(reqVO.getConfig())); + target.setDescription(normalizeNullable(reqVO.getDescription())); + } + + private String normalizeNullable(String value) { + String trimmed = StrUtil.trim(value); + return StrUtil.isEmpty(trimmed) ? null : trimmed; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyRateLimitServiceImpl.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyRateLimitServiceImpl.java new file mode 100644 index 00000000..64b71025 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/ApiPolicyRateLimitServiceImpl.java @@ -0,0 +1,98 @@ +package com.zt.plat.module.databus.service.gateway.impl; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicyPageReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.policy.ApiPolicySaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper; +import com.zt.plat.module.databus.service.gateway.ApiPolicyRateLimitService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.Optional; + +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_IN_USE; +import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_POLICY_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class ApiPolicyRateLimitServiceImpl implements ApiPolicyRateLimitService { + + private final ApiPolicyRateLimitMapper rateLimitMapper; + private final ApiDefinitionMapper apiDefinitionMapper; + + @Override + public PageResult getPage(ApiPolicyPageReqVO reqVO) { + return rateLimitMapper.selectPage(reqVO); + } + + @Override + public List getSimpleList() { + return rateLimitMapper.selectSimpleList(); + } + + @Override + public Optional get(Long id) { + if (id == null) { + return Optional.empty(); + } + return Optional.ofNullable(rateLimitMapper.selectById(id)) + .filter(policy -> !Boolean.TRUE.equals(policy.getDeleted())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long create(ApiPolicySaveReqVO reqVO) { + ApiPolicyRateLimitDO policy = new ApiPolicyRateLimitDO(); + apply(reqVO, policy); + policy.setId(null); + policy.setTenantId(TenantContextHolder.getTenantId()); + policy.setDeleted(Boolean.FALSE); + rateLimitMapper.insert(policy); + return policy.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void update(ApiPolicySaveReqVO reqVO) { + ApiPolicyRateLimitDO existing = ensureExists(reqVO.getId()); + apply(reqVO, existing); + rateLimitMapper.updateById(existing); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + ApiPolicyRateLimitDO existing = ensureExists(id); + Long referenceCount = apiDefinitionMapper.selectCountByRateLimitPolicyId(existing.getId()); + if (referenceCount != null && referenceCount > 0) { + throw ServiceExceptionUtil.exception(API_POLICY_IN_USE); + } + rateLimitMapper.deleteById(existing.getId()); + } + + private ApiPolicyRateLimitDO ensureExists(Long id) { + Assert.notNull(id, "策略编号不能为空"); + return get(id).orElseThrow(() -> ServiceExceptionUtil.exception(API_POLICY_NOT_FOUND)); + } + + private void apply(ApiPolicySaveReqVO reqVO, ApiPolicyRateLimitDO target) { + target.setName(StrUtil.trim(reqVO.getName())); + target.setType(StrUtil.trim(reqVO.getType())); + target.setConfig(normalizeNullable(reqVO.getConfig())); + target.setDescription(normalizeNullable(reqVO.getDescription())); + } + + private String normalizeNullable(String value) { + String trimmed = StrUtil.trim(value); + return StrUtil.isEmpty(trimmed) ? null : trimmed; + } + +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/GatewayServiceErrorCodeConstants.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/GatewayServiceErrorCodeConstants.java new file mode 100644 index 00000000..a2df8f3d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/service/gateway/impl/GatewayServiceErrorCodeConstants.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.databus.service.gateway.impl; + +import com.zt.plat.framework.common.exception.ErrorCode; + +/** + * Error code constants for unified API portal services. + */ +public interface GatewayServiceErrorCodeConstants { + + ErrorCode API_DEFINITION_NOT_FOUND = new ErrorCode(1_010_000_001, "API 定义未发布或已下线"); + ErrorCode API_DEFINITION_DUPLICATE = new ErrorCode(1_010_000_002, "API 编码与版本已存在"); + ErrorCode API_DEFINITION_STEP_EMPTY = new ErrorCode(1_010_000_003, "至少需要配置一个编排步骤"); + ErrorCode API_DEFINITION_STEP_ORDER_DUPLICATE = new ErrorCode(1_010_000_004, "步骤序号重复"); + ErrorCode API_TRANSFORM_PHASE_DUPLICATE = new ErrorCode(1_010_000_005, "同一级别的变换阶段重复"); + ErrorCode API_POLICY_NOT_FOUND = new ErrorCode(1_010_000_006, "绑定的策略不存在"); + ErrorCode API_POLICY_IN_USE = new ErrorCode(1_010_000_028, "策略已被 API 定义引用,无法删除"); + ErrorCode API_FLOW_NOT_FOUND = new ErrorCode(1_010_000_007, "未找到可用的 API 调度流程:code={}, version={}"); + ErrorCode API_FLOW_NO_REPLY = new ErrorCode(1_010_000_008, "集成流程未返回响应:code={}, version={}"); + ErrorCode API_AUTH_UNAUTHORIZED = new ErrorCode(1_010_000_009, "请求未通过认证"); + ErrorCode API_RATE_LIMIT_EXCEEDED = new ErrorCode(1_010_000_010, "请求触发限流策略"); + ErrorCode API_RATE_LIMIT_EVALUATION_FAILED = new ErrorCode(1_010_000_011, "限流策略执行失败"); + ErrorCode API_STEP_HTTP_ENDPOINT_INVALID = new ErrorCode(1_010_000_012, "HTTP 步骤缺少目标地址"); + ErrorCode API_STEP_HTTP_EXECUTION_FAILED = new ErrorCode(1_010_000_013, "HTTP 步骤执行失败:{}"); + ErrorCode API_STEP_SCRIPT_EXECUTION_FAILED = new ErrorCode(1_010_000_014, "脚本步骤执行失败:{}"); + ErrorCode API_STEP_RPC_ENDPOINT_INVALID = new ErrorCode(1_010_000_015, "RPC 步骤的目标端点格式必须为 beanName#method"); + ErrorCode API_STEP_RPC_METHOD_NOT_FOUND = new ErrorCode(1_010_000_016, "RPC 步骤未找到目标方法:{}#{}"); + ErrorCode API_STEP_RPC_UNSUPPORTED_SIGNATURE = new ErrorCode(1_010_000_017, "RPC 步骤暂不支持该方法签名:{}"); + ErrorCode API_STEP_RPC_EXECUTION_FAILED = new ErrorCode(1_010_000_018, "RPC 步骤执行失败:{}"); + ErrorCode API_TRANSFORM_EVALUATION_FAILED = new ErrorCode(1_010_000_019, "API 层变换执行失败:{}"); + ErrorCode API_TRANSFORM_RESPONSE_STATUS_INVALID = new ErrorCode(1_010_000_020, "API 层变换返回的 responseStatus 不合法:{}"); + ErrorCode API_PARALLEL_INTERRUPTED = new ErrorCode(1_010_000_021, "并行步骤执行被中断"); + ErrorCode API_PARALLEL_FAILED = new ErrorCode(1_010_000_022, "并行步骤执行失败:{}"); + ErrorCode API_EXPRESSION_NO_EVALUATOR = new ErrorCode(1_010_000_023, "未找到可用的表达式执行器:{}"); + ErrorCode API_EXPRESSION_EVALUATION_FAILED = new ErrorCode(1_010_000_024, "表达式执行失败:{}"); + ErrorCode API_JSONATA_BIND_FAILED = new ErrorCode(1_010_000_025, "表达式环境绑定失败"); + ErrorCode API_STEP_EXECUTION_ERROR = new ErrorCode(1_010_000_026, "步骤执行出现异常"); + ErrorCode API_STEP_UNSUPPORTED_TYPE = new ErrorCode(1_010_000_027, "不支持的步骤类型:{}"); + +} diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerTest.java new file mode 100644 index 00000000..02badc3d --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/step/impl/HttpStepHandlerTest.java @@ -0,0 +1,105 @@ +package com.zt.plat.module.databus.framework.integration.gateway.step.impl; + +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO; +import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition; +import com.zt.plat.module.databus.framework.integration.gateway.expression.ExpressionExecutor; +import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.messaging.MessageHeaders; +import org.springframework.web.reactive.function.client.WebClient; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +class HttpStepHandlerTest { + + private MockWebServer server; + private ExpressionExecutor expressionExecutor; + private HttpStepHandler handler; + + @BeforeEach + void setUp() throws IOException { + server = new MockWebServer(); + server.start(); + expressionExecutor = Mockito.mock(ExpressionExecutor.class); + handler = new HttpStepHandler(WebClient.builder(), expressionExecutor); + } + + @AfterEach + void tearDown() throws IOException { + server.shutdown(); + } + + @Test + void shouldForwardQueryParamsFromContextForGet() throws Exception { + server.enqueue(new MockResponse().setBody("{\"ok\":true}").setHeader("Content-Type", "application/json")); + + ApiInvocationContext context = ApiInvocationContext.create(); + context.getRequestQueryParams().put("id", "123"); + context.setRequestBody(Collections.singletonMap("ignored", "value")); + + ApiStepDO stepDO = new ApiStepDO(); + stepDO.setId(1L); + stepDO.setType("HTTP"); + stepDO.setTargetEndpoint("GET " + server.url("/orders")); + + ApiStepDefinition stepDefinition = ApiStepDefinition.builder() + .step(stepDO) + .metadata(Collections.emptyMap()) + .transforms(Collections.emptyList()) + .build(); + + handler.build(null, stepDefinition).handle(context, new MessageHeaders(Collections.emptyMap())); + + RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/orders?id=123"); + assertThat(request.getBody().size()).isZero(); + } + + @Test + void shouldMergeExpressionBodyAndQueryParamsForPost() throws Exception { + server.enqueue(new MockResponse().setBody("{\"ok\":true}").setHeader("Content-Type", "application/json")); + + ApiInvocationContext context = ApiInvocationContext.create(); + context.setRequestBody(Collections.singletonMap("ignored", "value")); + + ApiStepDO stepDO = new ApiStepDO(); + stepDO.setId(2L); + stepDO.setType("HTTP"); + stepDO.setTargetEndpoint("POST " + server.url("/payments")); + stepDO.setRequestMappingExpr("JSON::{\"body\": {\"amount\": 100}, \"query\": {\"token\": \"abc\"}}"); + + Map expressionResult = Map.of( + "body", Map.of("amount", 100), + "query", Map.of("token", "abc") + ); + Mockito.when(expressionExecutor.evaluate(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(expressionResult); + + ApiStepDefinition stepDefinition = ApiStepDefinition.builder() + .step(stepDO) + .metadata(Collections.emptyMap()) + .transforms(Collections.emptyList()) + .build(); + + handler.build(null, stepDefinition).handle(context, new MessageHeaders(Collections.emptyMap())); + + RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS); + assertThat(request).isNotNull(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()).isEqualTo("/payments?token=abc"); + assertThat(request.getHeader("Content-Type")).contains("application/json"); + assertThat(request.getBody().readUtf8()).contains("\"amount\":100"); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java new file mode 100644 index 00000000..cfa8bf38 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/service/gateway/ApiDefinitionServiceImplTest.java @@ -0,0 +1,276 @@ +package com.zt.plat.module.databus.service.gateway; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO; +import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyAuthDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyAuthMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper; +import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper; +import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum; +import com.zt.plat.module.databus.service.gateway.impl.ApiDefinitionServiceImpl; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.context.TestPropertySource; + +import java.util.List; + +import com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Import({ApiDefinitionServiceImpl.class, ApiDefinitionServiceImplTest.JacksonTestConfiguration.class}) +@TestPropertySource(properties = { + "spring.config.import=", + "config.server-addr=localhost:8848", + "config.group=DEFAULT_GROUP", + "config.namespace=public", + "config.username=nacos", + "config.password=nacos", + "spring.cloud.nacos.config.enabled=false", + "spring.cloud.nacos.discovery.enabled=false" +}) +class ApiDefinitionServiceImplTest extends BaseDbUnitTest { + + @Resource + private ApiDefinitionService apiDefinitionService; + + @Resource + private ApiDefinitionMapper apiDefinitionMapper; + @Resource + private ApiStepMapper apiStepMapper; + @Resource + private ApiTransformMapper apiTransformMapper; + @Resource + private ApiPolicyAuthMapper apiPolicyAuthMapper; + @Resource + private ApiPolicyRateLimitMapper apiPolicyRateLimitMapper; + + @MockBean + private StringRedisTemplate stringRedisTemplate; + + @TestConfiguration + static class JacksonTestConfiguration { + + @Bean + ObjectMapper objectMapper() { + return new ObjectMapper(); + } + } + + @AfterEach + void tearDown() { + TenantContextHolder.clear(); + } + + @Test + void testCreate_success() { + TenantContextHolder.setTenantId(1L); + Long authId = insertAuthPolicy(); + Long rateId = insertRateLimitPolicy(); + ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, authId, rateId); + Long definitionId = apiDefinitionService.create(reqVO); + + ApiDefinitionDO definition = apiDefinitionMapper.selectById(definitionId); + assertNotNull(definition); + assertEquals(reqVO.getApiCode(), definition.getApiCode()); + assertEquals(reqVO.getVersion(), definition.getVersion()); + assertEquals(1L, definition.getTenantId()); + assertEquals(reqVO.getStatus(), definition.getStatus()); + assertEquals(reqVO.getDescription(), definition.getDescription()); + + List steps = apiStepMapper.selectByApiId(definitionId); + assertEquals(1, steps.size()); + ApiStepDO step = steps.get(0); + assertEquals(1, step.getStepOrder()); + assertEquals("HTTP", step.getType()); + + List apiLevelTransforms = apiTransformMapper.selectApiLevelTransforms(definitionId); + assertEquals(1, apiLevelTransforms.size()); + assertEquals("REQUEST_PRE", apiLevelTransforms.get(0).getPhase()); + + List stepTransforms = apiTransformMapper.selectByStepId(step.getId()); + assertEquals(1, stepTransforms.size()); + assertEquals("RESPONSE_PRE", stepTransforms.get(0).getPhase()); + } + + @Test + void testCreate_duplicate() { + TenantContextHolder.setTenantId(1L); + ApiDefinitionDO definition = new ApiDefinitionDO(); + definition.setTenantId(1L); + definition.setDeleted(false); + definition.setApiCode("order.create"); + definition.setVersion("v1"); + definition.setHttpMethod("POST"); + definition.setUriPattern("/order/create"); + definition.setStatus(ApiStatusEnum.ONLINE.getStatus()); + apiDefinitionMapper.insert(definition); + + ApiDefinitionSaveReqVO reqVO = buildSaveReq(null, null, null); + + ServiceException exception = assertThrows(ServiceException.class, () -> apiDefinitionService.create(reqVO)); + assertEquals(GatewayServiceErrorCodeConstants.API_DEFINITION_DUPLICATE.getCode(), exception.getCode()); + } + + @Test + void testUpdate_replaceSteps() { + TenantContextHolder.setTenantId(1L); + Long authId = insertAuthPolicy(); + Long rateId = insertRateLimitPolicy(); + ApiDefinitionDO definition = new ApiDefinitionDO(); + definition.setTenantId(1L); + definition.setDeleted(false); + definition.setApiCode("order.update"); + definition.setVersion("v1"); + definition.setHttpMethod("POST"); + definition.setUriPattern("/order/update"); + definition.setStatus(ApiStatusEnum.ONLINE.getStatus()); + apiDefinitionMapper.insert(definition); + + ApiStepDO oldStep = new ApiStepDO(); + oldStep.setApiId(definition.getId()); + oldStep.setStepOrder(1); + oldStep.setType("HTTP"); + oldStep.setTenantId(1L); + oldStep.setDeleted(false); + apiStepMapper.insert(oldStep); + + ApiTransformDO oldTransform = new ApiTransformDO(); + oldTransform.setApiId(definition.getId()); + oldTransform.setStepId(oldStep.getId()); + oldTransform.setPhase("REQUEST_PRE"); + oldTransform.setExpressionType("JSON"); + oldTransform.setExpression("{}"); + oldTransform.setTenantId(1L); + oldTransform.setDeleted(false); + apiTransformMapper.insert(oldTransform); + + ApiDefinitionSaveReqVO reqVO = buildSaveReq(definition.getId(), authId, rateId); + reqVO.setApiCode("order.update"); + reqVO.setVersion("v2"); + reqVO.getSteps().get(0).setStepOrder(2); + apiDefinitionService.update(reqVO); + + List steps = apiStepMapper.selectByApiId(definition.getId()); + assertEquals(1, steps.size()); + assertEquals(2, steps.get(0).getStepOrder()); + + List transforms = apiTransformMapper.selectByApiId(definition.getId()); + assertThat(transforms) + .extracting(ApiTransformDO::getPhase) + .containsExactlyInAnyOrder("REQUEST_PRE", "RESPONSE_PRE"); + } + + @Test + void testDelete_success() { + TenantContextHolder.setTenantId(1L); + ApiDefinitionDO definition = new ApiDefinitionDO(); + definition.setTenantId(1L); + definition.setDeleted(false); + definition.setApiCode("order.delete"); + definition.setVersion("v1"); + definition.setHttpMethod("DELETE"); + definition.setUriPattern("/order/delete"); + definition.setStatus(ApiStatusEnum.ONLINE.getStatus()); + apiDefinitionMapper.insert(definition); + + ApiStepDO step = new ApiStepDO(); + step.setApiId(definition.getId()); + step.setStepOrder(1); + step.setType("HTTP"); + step.setTenantId(1L); + step.setDeleted(false); + apiStepMapper.insert(step); + + ApiTransformDO transform = new ApiTransformDO(); + transform.setApiId(definition.getId()); + transform.setStepId(step.getId()); + transform.setPhase("REQUEST_PRE"); + transform.setExpressionType("JSON"); + transform.setExpression("{}"); + transform.setTenantId(1L); + transform.setDeleted(false); + apiTransformMapper.insert(transform); + + apiDefinitionService.delete(definition.getId()); + + ApiDefinitionDO deleted = apiDefinitionMapper.selectById(definition.getId()); + assertThat(deleted).isNull(); + assertThat(apiStepMapper.selectByApiId(definition.getId())).isEmpty(); + assertThat(apiTransformMapper.selectByApiId(definition.getId())).isEmpty(); + } + + private ApiDefinitionSaveReqVO buildSaveReq(Long id, Long authId, Long rateId) { + ApiDefinitionSaveReqVO reqVO = new ApiDefinitionSaveReqVO(); + reqVO.setId(id); + reqVO.setApiCode("order.create"); + reqVO.setVersion("v1"); + reqVO.setHttpMethod("POST"); + reqVO.setUriPattern("/order/create"); + reqVO.setStatus(ApiStatusEnum.ONLINE.getStatus()); + reqVO.setDescription("create order"); + reqVO.setAuthPolicyId(authId); + reqVO.setRateLimitId(rateId); + + ApiDefinitionTransformSaveReqVO apiTransform = new ApiDefinitionTransformSaveReqVO(); + apiTransform.setPhase("REQUEST_PRE"); + apiTransform.setExpressionType("JSON"); + apiTransform.setExpression("{}"); + reqVO.getApiLevelTransforms().add(apiTransform); + + ApiDefinitionStepSaveReqVO step = new ApiDefinitionStepSaveReqVO(); + step.setStepOrder(1); + step.setType("HTTP"); + step.setTargetEndpoint("https://api.example.com/order"); + + ApiDefinitionTransformSaveReqVO stepTransform = new ApiDefinitionTransformSaveReqVO(); + stepTransform.setPhase("RESPONSE_PRE"); + stepTransform.setExpressionType("JSON"); + stepTransform.setExpression("{}"); + step.getTransforms().add(stepTransform); + + reqVO.getSteps().add(step); + return reqVO; + } + + private Long insertAuthPolicy() { + ApiPolicyAuthDO policy = new ApiPolicyAuthDO(); + policy.setName("auth"); + policy.setType("BASIC"); + policy.setConfig("{}"); + policy.setTenantId(1L); + policy.setDeleted(false); + apiPolicyAuthMapper.insert(policy); + return policy.getId(); + } + + private Long insertRateLimitPolicy() { + ApiPolicyRateLimitDO policy = new ApiPolicyRateLimitDO(); + policy.setName("rate"); + policy.setType("GLOBAL"); + policy.setConfig("{}"); + policy.setTenantId(1L); + policy.setDeleted(false); + apiPolicyRateLimitMapper.insert(policy); + return policy.getId(); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/test/resources/application-unit-test.yaml b/zt-module-databus/zt-module-databus-server/src/test/resources/application-unit-test.yaml new file mode 100644 index 00000000..4ea84ded --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/resources/application-unit-test.yaml @@ -0,0 +1,42 @@ +spring: + config: + import: "" + main: + lazy-initialization: true + banner-mode: off + datasource: + name: databus-unit-test + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true + initial-size: 1 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + data: + redis: + host: 127.0.0.1 + port: 16379 + database: 0 +mybatis: + lazy-initialization: true +mybatis-plus: + global-config: + db-config: + id-type: AUTO + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +zt: + info: + base-package: com.zt.plat.module +env: + name: unit-test +config: + server-addr: localhost:8848 + username: nacos + password: nacos + namespace: public + group: DEFAULT_GROUP diff --git a/zt-module-databus/zt-module-databus-server/src/test/resources/application.yaml b/zt-module-databus/zt-module-databus-server/src/test/resources/application.yaml new file mode 100644 index 00000000..74942588 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/resources/application.yaml @@ -0,0 +1,5 @@ +spring: + profiles: + active: unit-test + config: + import: "" diff --git a/zt-module-databus/zt-module-databus-server/src/test/resources/sql/clean.sql b/zt-module-databus/zt-module-databus-server/src/test/resources/sql/clean.sql new file mode 100644 index 00000000..43823faa --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/resources/sql/clean.sql @@ -0,0 +1,7 @@ +DELETE FROM "databus_api_transform"; +DELETE FROM "databus_api_step"; +DELETE FROM "databus_api_definition"; +DELETE FROM "databus_policy_auth"; +DELETE FROM "databus_policy_rate_limit"; +DELETE FROM "databus_policy_audit"; +DELETE FROM "databus_api_flow_publish"; diff --git a/zt-module-databus/zt-module-databus-server/src/test/resources/sql/create_tables.sql b/zt-module-databus/zt-module-databus-server/src/test/resources/sql/create_tables.sql new file mode 100644 index 00000000..3427a929 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/test/resources/sql/create_tables.sql @@ -0,0 +1,118 @@ +CREATE TABLE IF NOT EXISTS databus_api_definition ( + id BIGINT PRIMARY KEY, + api_code VARCHAR(255) NOT NULL, + uri_pattern VARCHAR(512), + http_method VARCHAR(16), + version VARCHAR(64), + status INT, + description VARCHAR(1024), + auth_policy_id BIGINT, + rate_limit_id BIGINT, + audit_policy_id BIGINT, + response_template CLOB, + cache_strategy VARCHAR(255), + updated_at TIMESTAMP, + grey_released BOOLEAN, + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_api_step ( + id BIGINT PRIMARY KEY, + api_id BIGINT NOT NULL, + step_order INT, + parallel_group VARCHAR(64), + type VARCHAR(64), + target_endpoint VARCHAR(512), + request_mapping_expr VARCHAR(1024), + response_mapping_expr VARCHAR(1024), + transform_id BIGINT, + timeout BIGINT, + retry_strategy VARCHAR(255), + fallback_strategy VARCHAR(255), + condition_expr VARCHAR(1024), + stop_on_error BOOLEAN, + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_api_transform ( + id BIGINT PRIMARY KEY, + api_id BIGINT, + step_id BIGINT, + phase VARCHAR(64), + expression_type VARCHAR(64), + expression VARCHAR(1024), + description VARCHAR(1024), + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_policy_auth ( + id BIGINT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type VARCHAR(64), + config CLOB, + description VARCHAR(512), + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_policy_rate_limit ( + id BIGINT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type VARCHAR(64), + config CLOB, + description VARCHAR(512), + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_policy_audit ( + id BIGINT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type VARCHAR(64), + config CLOB, + description VARCHAR(512), + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); + +CREATE TABLE IF NOT EXISTS databus_api_flow_publish ( + id BIGINT PRIMARY KEY, + api_id BIGINT, + version VARCHAR(64), + status INT, + publish_time TIMESTAMP, + rollback_time TIMESTAMP, + tenant_id BIGINT, + create_time TIMESTAMP, + update_time TIMESTAMP, + creator VARCHAR(64), + updater VARCHAR(64), + deleted BOOLEAN +); diff --git a/zt-module-rule/pom.xml b/zt-module-rule/pom.xml deleted file mode 100644 index 00d6b056..00000000 --- a/zt-module-rule/pom.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - zt - com.zt.plat - ${revision} - - - zt-module-rule-api - zt-module-rule-server - - 4.0.0 - - zt-module-rule - pom - - ${project.artifactId} - - Rule 模块。 - - - diff --git a/zt-module-rule/zt-module-rule-api/pom.xml b/zt-module-rule/zt-module-rule-api/pom.xml deleted file mode 100644 index 8eb01fd4..00000000 --- a/zt-module-rule/zt-module-rule-api/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - zt-module-rule - com.zt.plat - ${revision} - - 4.0.0 - zt-module-rule-api - jar - - ${project.artifactId} - - 暴露给其它模块调用 - - - - - com.zt.plat - zt-common - - - - org.springdoc - springdoc-openapi-starter-webmvc-api - provided - - - - - org.springframework.boot - spring-boot-starter-validation - true - - - - - org.springframework.cloud - spring-cloud-starter-openfeign - true - - - - diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/RuleEngineApi.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/RuleEngineApi.java new file mode 100644 index 00000000..ca70fb50 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/RuleEngineApi.java @@ -0,0 +1,42 @@ +package com.zt.plat.module.rule.api; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.rule.api.dto.RuleChainExecuteMetaRespDTO; +import com.zt.plat.module.rule.api.dto.RuleChainLoadReqDTO; +import com.zt.plat.module.rule.api.dto.RuleChainVersionRespDTO; +import com.zt.plat.module.rule.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +/** + * RPC interface to expose rule engine capabilities to other micro services. + */ +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 规则引擎") +public interface RuleEngineApi { + + String PREFIX = ApiConstants.PREFIX + "/engine"; + + @PostMapping(PREFIX + "/load") + @Operation(summary = "按业务+版本加载链路执行元数据") + CommonResult loadChain(@Valid @RequestBody RuleChainLoadReqDTO reqDTO); + + @GetMapping(PREFIX + "/latest") + @Operation(summary = "获取业务最新链路") + CommonResult getLatestChain(@Parameter(description = "业务标识", required = true) + @RequestParam("business") String business); + + @GetMapping(PREFIX + "/versions") + @Operation(summary = "查询业务链路版本列表") + CommonResult> listChainVersions(@Parameter(description = "业务标识", required = true) + @RequestParam("business") String business); +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainExecuteMetaRespDTO.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainExecuteMetaRespDTO.java new file mode 100644 index 00000000..08009e87 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainExecuteMetaRespDTO.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.rule.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Response DTO exposing the LiteFlow execution metadata for a business chain. + */ +@Data +public class RuleChainExecuteMetaRespDTO { + + @Schema(description = "业务标识", example = "order.create") + private String business; + + @Schema(description = "链路版本", example = "v3") + private String version; + + @Schema(description = "LiteFlow 链路唯一标识", example = "order.create:v3") + private String chainId; + + @Schema(description = "LiteFlow DSL 内容", example = "{\"chain\":...}") + private String liteflowDsl; + + @Schema(description = "发布状态", example = "1") + private Integer status; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainLoadReqDTO.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainLoadReqDTO.java new file mode 100644 index 00000000..a8b47691 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainLoadReqDTO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.rule.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * Request DTO used when remote services need to fetch a LiteFlow chain. + */ +@Data +public class RuleChainLoadReqDTO { + + @Schema(description = "业务标识", example = "order.create") + @NotBlank(message = "业务标识不能为空") + private String business; + + @Schema(description = "链路版本,空则返回最新版本", example = "v3") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainSimpleRespDTO.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainSimpleRespDTO.java new file mode 100644 index 00000000..37da244e --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainSimpleRespDTO.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.rule.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Simplified view of a chain for dropdowns or quick lookups. + */ +@Data +public class RuleChainSimpleRespDTO { + + @Schema(description = "链编号", example = "1001") + private Long id; + + @Schema(description = "链编码", example = "order_main_flow") + private String code; + + @Schema(description = "链名称", example = "订单主流程") + private String name; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainVersionRespDTO.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainVersionRespDTO.java new file mode 100644 index 00000000..c2e2613c --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/api/dto/RuleChainVersionRespDTO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.rule.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * DTO describing available chain versions for a business code. + */ +@Data +public class RuleChainVersionRespDTO { + + @Schema(description = "业务标识", example = "order.create") + private String business; + + @Schema(description = "链路版本", example = "v3") + private String version; + + @Schema(description = "绑定的链编码", example = "order_main_flow") + private String chainCode; + + @Schema(description = "发布状态", example = "1") + private Integer status; + + @Schema(description = "发布人", example = "1001") + private Long releaseUserId; + + @Schema(description = "发布时间") + private LocalDateTime releaseTime; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ApiConstants.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ApiConstants.java new file mode 100644 index 00000000..eac415d0 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.enums; + +import com.zt.plat.framework.common.enums.RpcConstants; + +/** + * RPC constants for the Rule module. + */ +public interface ApiConstants { + + /** + * Spring service name for the rule module. Must align with spring.application.name. + */ + String NAME = "rule-server"; + + /** + * Prefix used by rule RPC endpoints. + */ + String PREFIX = RpcConstants.RPC_API_PREFIX + "/rule"; + + /** + * RPC contract version. + */ + String VERSION = "1.0.0"; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java deleted file mode 100644 index 8879effb..00000000 --- a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.zt.plat.module.rule.enums; - -import com.zt.plat.framework.common.exception.ErrorCode; - -/** - * rule 错误码枚举类 - * - * rule 系统,使用 1-xxx-xxx-xxx 段 - * - * @author ZT - */ -public interface ErrorCodeConstants { - - // ========== 示例模块 1-001-000-000 ========== - ErrorCode EXAMPLE_NOT_EXISTS = new ErrorCode(1_001_000_001, "示例不存在"); - -} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/OverrideStrategyEnum.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/OverrideStrategyEnum.java new file mode 100644 index 00000000..ca8e3ffa --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/OverrideStrategyEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.rule.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Strategies supported when a child business inherits parent chains. + */ +@Getter +@AllArgsConstructor +public enum OverrideStrategyEnum { + + /** + * Fully inherit parent chain without change. + */ + INHERIT(0, "继承"), + /** + * Override nodes with the same code in parent chain. + */ + OVERRIDE(1, "覆盖"), + /** + * Disable matched nodes from parent chain. + */ + DISABLE(2, "禁用"), + /** + * Append additional nodes after inherited chain. + */ + APPEND(3, "追加"); + + private final Integer strategy; + private final String label; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleNodeTypeEnum.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleNodeTypeEnum.java new file mode 100644 index 00000000..34143093 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleNodeTypeEnum.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.rule.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Node types available when orchestrating a LiteFlow chain. + */ +@Getter +@AllArgsConstructor +public enum RuleNodeTypeEnum { + + /** + * Sequential node executed in order. + */ + THEN(1, "顺序"), + /** + * Conditional branch (IF/ELSE) node. + */ + IF(2, "条件"), + /** + * Switch-case control node. + */ + SWITCH(3, "分支"), + /** + * Loop node such as FOR/WHILE. + */ + LOOP(4, "循环"), + /** + * Parallel orchestrator node. + */ + PARALLEL(5, "并行"), + /** + * Nested sub-chain invocation. + */ + CHAIN(6, "子链"), + /** + * Atomic rule node. + */ + ATOM(7, "原子规则"); + + private final Integer type; + private final String label; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RulePublishStatusEnum.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RulePublishStatusEnum.java new file mode 100644 index 00000000..ccdb8071 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RulePublishStatusEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.rule.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Release status for a business rule publication. + */ +@Getter +@AllArgsConstructor +public enum RulePublishStatusEnum { + + /** + * Waiting for publish processing. + */ + PENDING(0, "待发布"), + /** + * Successfully pushed to registry and activated. + */ + SUCCESS(1, "成功"), + /** + * Failed to publish to runtime. + */ + FAILED(2, "失败"), + /** + * Release rolled back. + */ + ROLLBACK(3, "回滚"); + + private final Integer status; + private final String label; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleStatusEnum.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleStatusEnum.java new file mode 100644 index 00000000..6aa1da1d --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleStatusEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.rule.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Common lifecycle statuses for rule entities. + */ +@Getter +@AllArgsConstructor +public enum RuleStatusEnum { + + /** + * Draft state, not yet published. + */ + DRAFT(0, "草稿"), + /** + * Active state, can be orchestrated or executed. + */ + ENABLED(1, "启用"), + /** + * Disabled state, excluded from execution. + */ + DISABLED(2, "禁用"), + /** + * Deprecated or archived state. + */ + ARCHIVED(3, "归档"); + + private final Integer status; + private final String label; +} diff --git a/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleTypeEnum.java b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleTypeEnum.java new file mode 100644 index 00000000..b6175902 --- /dev/null +++ b/zt-module-rule/zt-module-rule-api/src/main/java/com/zt/plat/module/rule/enums/RuleTypeEnum.java @@ -0,0 +1,32 @@ +package com.zt.plat.module.rule.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Types of atomic rules supported by the rule engine. + */ +@Getter +@AllArgsConstructor +public enum RuleTypeEnum { + + /** + * Script-based rule (Groovy/JS/QLExpress). + */ + SCRIPT(1, "脚本"), + /** + * Spring bean rule implementation. + */ + SPRING_BEAN(2, "Spring Bean"), + /** + * Custom LiteFlow component rule. + */ + COMPONENT(3, "组件"), + /** + * External chain reference for composite reuse. + */ + CHAIN_REFERENCE(4, "引用链"); + + private final Integer type; + private final String label; +} diff --git a/zt-module-rule/zt-module-rule-server/pom.xml b/zt-module-rule/zt-module-rule-server/pom.xml deleted file mode 100644 index b4c89e07..00000000 --- a/zt-module-rule/zt-module-rule-server/pom.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - zt-module-rule - com.zt.plat - ${revision} - - 4.0.0 - jar - - zt-module-rule-server - - ${project.artifactId} - - Rule 模块。 - - - - - - com.zt.plat - zt-spring-boot-starter-env - - - - - com.zt.plat - zt-module-system-api - ${revision} - - - com.zt.plat - zt-module-infra-api - ${revision} - - - - com.zt.plat - zt-module-rule-api - ${revision} - - - - - com.zt.plat - zt-spring-boot-starter-biz-data-permission - - - com.zt.plat - zt-spring-boot-starter-biz-tenant - - - - - com.zt.plat - zt-spring-boot-starter-web - - - - com.zt.plat - zt-spring-boot-starter-security - - - - - com.zt.plat - zt-spring-boot-starter-mybatis - - - - com.zt.plat - zt-spring-boot-starter-redis - - - - - com.zt.plat - zt-spring-boot-starter-rpc - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - - - com.zt.plat - zt-spring-boot-starter-job - - - - - com.zt.plat - zt-spring-boot-starter-mq - - - - - com.zt.plat - zt-spring-boot-starter-test - - - - - com.zt.plat - zt-spring-boot-starter-excel - - - - - com.zt.plat - zt-spring-boot-starter-monitor - - - com.zt.plat - zt-spring-boot-starter-biz-business - ${revision} - - - - - com.yomahub - liteflow-spring-boot-starter - 2.15.0 - - - - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - - - diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/RuleServerApplication.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/RuleServerApplication.java index 673b7dab..8884b452 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/RuleServerApplication.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/RuleServerApplication.java @@ -3,16 +3,10 @@ package com.zt.plat.module.rule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Rule 模块的启动类 - * - * @author ZT - */ @SpringBootApplication public class RuleServerApplication { public static void main(String[] args) { SpringApplication.run(RuleServerApplication.class, args); } - } diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/RuleBusinessController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/RuleBusinessController.java new file mode 100644 index 00000000..f4440c0a --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/RuleBusinessController.java @@ -0,0 +1,104 @@ +package com.zt.plat.module.rule.controller.admin.business; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindPageReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessChainViewRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessRelationSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessTreeNodeRespVO; +import com.zt.plat.module.rule.service.business.RuleBusinessService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 业务链路") +@RestController +@RequestMapping("/admin/rule/business") +@Validated +public class RuleBusinessController { + + @Resource + private RuleBusinessService ruleBusinessService; + + @PostMapping("/binding/save") + @Operation(summary = "保存业务链绑定") + @PreAuthorize("@ss.hasPermission('rule:business:update')") + public CommonResult saveBinding(@Valid @RequestBody RuleBusinessBindSaveReqVO reqVO) { + ruleBusinessService.saveBinding(reqVO); + return success(Boolean.TRUE); + } + + @GetMapping("/binding/page") + @Operation(summary = "分页查询业务链绑定") + @PreAuthorize("@ss.hasPermission('rule:business:query')") + public CommonResult> getBindingPage(@Valid RuleBusinessBindPageReqVO reqVO) { + return success(ruleBusinessService.getBindingPage(reqVO)); + } + + @GetMapping("/binding/get") + @Operation(summary = "获取业务链绑定详情") + @Parameter(name = "business", description = "业务标识", required = true) + @PreAuthorize("@ss.hasPermission('rule:business:query')") + public CommonResult getBinding(@RequestParam("business") String business) { + return success(ruleBusinessService.getBinding(business)); + } + + @DeleteMapping("/binding/remove") + @Operation(summary = "删除业务链绑定") + @Parameter(name = "business", description = "业务标识", required = true) + @PreAuthorize("@ss.hasPermission('rule:business:delete')") + public CommonResult removeBinding(@RequestParam("business") String business) { + ruleBusinessService.removeBinding(business); + return success(Boolean.TRUE); + } + + @PostMapping("/relation/save") + @Operation(summary = "设置业务继承关系") + @PreAuthorize("@ss.hasPermission('rule:business:update')") + public CommonResult saveRelation(@Valid @RequestBody RuleBusinessRelationSaveReqVO reqVO) { + ruleBusinessService.saveRelation(reqVO); + return success(Boolean.TRUE); + } + + @DeleteMapping("/relation/remove") + @Operation(summary = "移除业务继承关系") + @Parameter(name = "childBusiness", description = "子业务标识", required = true) + @PreAuthorize("@ss.hasPermission('rule:business:delete')") + public CommonResult removeRelation(@RequestParam("childBusiness") String childBusiness) { + ruleBusinessService.removeRelation(childBusiness); + return success(Boolean.TRUE); + } + + @GetMapping("/tree") + @Operation(summary = "查询业务继承树") + @PreAuthorize("@ss.hasPermission('rule:business:query')") + public CommonResult> getBusinessTree() { + return success(ruleBusinessService.getBusinessTree()); + } + + @GetMapping("/preview") + @Operation(summary = "预览业务链执行结构") + @PreAuthorize("@ss.hasPermission('rule:business:preview')") + public CommonResult previewBusinessChain(@RequestParam("business") @NotBlank String business, + @RequestParam(value = "version", required = false) String version) { + return success(ruleBusinessService.previewBusinessChain(business, version)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindPageReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindPageReqVO.java new file mode 100644 index 00000000..cb22008d --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindPageReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleBusinessBindPageReqVO extends PageParam { + + @Schema(description = "业务标识") + private String business; + + @Schema(description = "继承策略") + private Integer overrideStrategy; + + @Schema(description = "是否锁定") + private Boolean locked; + + private LocalDateTime[] createTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindRespVO.java new file mode 100644 index 00000000..6a78b057 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindRespVO.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class RuleBusinessBindRespVO { + + private Long id; + + @Schema(description = "业务标识") + private String business; + + @Schema(description = "链ID") + private Long ruleChainId; + + @Schema(description = "链编码") + private String ruleChainCode; + + @Schema(description = "链名称") + private String ruleChainName; + + private Integer overrideStrategy; + + private Boolean locked; + + private String effectiveVersion; + + private String remark; + + private LocalDateTime createTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindSaveReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindSaveReqVO.java new file mode 100644 index 00000000..14cb4811 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessBindSaveReqVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class RuleBusinessBindSaveReqVO { + + @Schema(description = "业务标识", example = "order.create") + @NotBlank(message = "业务标识不能为空") + private String business; + + @Schema(description = "链ID", example = "1001") + @NotNull(message = "链ID不能为空") + private Long ruleChainId; + + @Schema(description = "继承策略", example = "1") + private Integer overrideStrategy; + + @Schema(description = "是否锁定") + private Boolean locked; + + @Schema(description = "备注") + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessChainViewRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessChainViewRespVO.java new file mode 100644 index 00000000..b138f7ee --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessChainViewRespVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainStructureVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class RuleBusinessChainViewRespVO { + + @Schema(description = "业务标识") + private String business; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "链ID") + private String chainId; + + @Schema(description = "LiteFlow DSL") + private String liteflowDsl; + + private RuleChainStructureVO structure; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessRelationSaveReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessRelationSaveReqVO.java new file mode 100644 index 00000000..5d8c59f3 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessRelationSaveReqVO.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RuleBusinessRelationSaveReqVO { + + @Schema(description = "父业务标识") + @NotBlank(message = "父业务不能为空") + private String parentBusiness; + + @Schema(description = "子业务标识") + @NotBlank(message = "子业务不能为空") + private String childBusiness; + + @Schema(description = "顺序") + private Integer sort; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessTreeNodeRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessTreeNodeRespVO.java new file mode 100644 index 00000000..5f2dd62f --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/business/vo/RuleBusinessTreeNodeRespVO.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.rule.controller.admin.business.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class RuleBusinessTreeNodeRespVO { + + @Schema(description = "业务标识") + private String business; + + @Schema(description = "链编码") + private String chainCode; + + @Schema(description = "链名称") + private String chainName; + + @Schema(description = "子节点") + private List children = new ArrayList<>(); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/RuleChainController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/RuleChainController.java new file mode 100644 index 00000000..eb545b47 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/RuleChainController.java @@ -0,0 +1,90 @@ +package com.zt.plat.module.rule.controller.admin.chain; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainCreateReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainPageReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainRespVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainStructureVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainUpdateReqVO; +import com.zt.plat.module.rule.convert.chain.RuleChainConvert; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; +import com.zt.plat.module.rule.service.chain.RuleChainService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 规则链") +@RestController +@RequestMapping("/admin/rule/chain") +@Validated +public class RuleChainController { + + @Resource + private RuleChainService ruleChainService; + @Resource + private RuleChainConvert ruleChainConvert; + + @PostMapping("/create") + @Operation(summary = "创建规则链") + @PreAuthorize("@ss.hasPermission('rule:chain:create')") + public CommonResult createChain(@Valid @RequestBody RuleChainCreateReqVO reqVO) { + return success(ruleChainService.createChain(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新规则链") + @PreAuthorize("@ss.hasPermission('rule:chain:update')") + public CommonResult updateChain(@Valid @RequestBody RuleChainUpdateReqVO reqVO) { + ruleChainService.updateChain(reqVO); + return success(Boolean.TRUE); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除规则链") + @Parameter(name = "id", description = "规则链编号", required = true) + @PreAuthorize("@ss.hasPermission('rule:chain:delete')") + public CommonResult deleteChain(@RequestParam("id") Long id) { + ruleChainService.deleteChain(id); + return success(Boolean.TRUE); + } + + @GetMapping("/get") + @Operation(summary = "获取规则链详情") + @Parameter(name = "id", description = "规则链编号", required = true) + @PreAuthorize("@ss.hasPermission('rule:chain:query')") + public CommonResult getChain(@RequestParam("id") Long id) { + RuleChainDO chain = ruleChainService.getChain(id); + return success(chain != null ? ruleChainConvert.convert(chain) : null); + } + + @GetMapping("/page") + @Operation(summary = "分页查询规则链") + @PreAuthorize("@ss.hasPermission('rule:chain:query')") + public CommonResult> getChainPage(@Valid RuleChainPageReqVO reqVO) { + PageResult pageResult = ruleChainService.getChainPage(reqVO); + return success(ruleChainConvert.convertPage(pageResult)); + } + + @GetMapping("/structure") + @Operation(summary = "获取规则链结构") + @Parameter(name = "id", description = "规则链编号", required = true) + @PreAuthorize("@ss.hasPermission('rule:chain:query')") + public CommonResult getChainStructure(@RequestParam("id") Long id) { + return success(ruleChainService.getChainStructure(id)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainBaseVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainBaseVO.java new file mode 100644 index 00000000..320b9e74 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainBaseVO.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * Base VO for rule chain operations. + */ +@Data +public class RuleChainBaseVO { + + @Schema(description = "链编码", example = "order_main_flow") + @NotBlank(message = "链编码不能为空") + private String code; + + @Schema(description = "链名称", example = "订单主流程") + @NotBlank(message = "链名称不能为空") + private String name; + + @Schema(description = "链描述") + private String description; + + @Schema(description = "链状态") + private Integer status; + + @Schema(description = "链结构") + @Valid + private RuleChainStructureVO structure; + + @Schema(description = "备注") + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainCreateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainCreateReqVO.java new file mode 100644 index 00000000..d70c0767 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainCreateReqVO.java @@ -0,0 +1,13 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainCreateReqVO extends RuleChainBaseVO { + + @Schema(description = "链版本号", example = "1.0.0") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainLinkVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainLinkVO.java new file mode 100644 index 00000000..f53c8eb4 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainLinkVO.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class RuleChainLinkVO { + + @Schema(description = "连线ID") + private String id; + + @Schema(description = "起点节点ID") + private String source; + + @Schema(description = "终点节点ID") + private String target; + + @Schema(description = "条件表达式") + private String conditionExpr; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainNodeVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainNodeVO.java new file mode 100644 index 00000000..18939527 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainNodeVO.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +@Data +public class RuleChainNodeVO { + + @Schema(description = "画布节点ID") + private String id; + + @Schema(description = "节点编码") + private String code; + + @Schema(description = "节点名称") + private String name; + + @Schema(description = "节点类型") + private Integer type; + + @Schema(description = "引用的规则ID") + private Long ruleId; + + @Schema(description = "引用的规则编码") + private String ruleCode; + + @Schema(description = "排序索引") + private Integer orderIndex; + + @Schema(description = "并行组标识") + private String parallelGroup; + + @Schema(description = "条件表达式") + private String conditionExpr; + + @Schema(description = "是否禁用") + private Boolean disabled; + + @Schema(description = "节点配置") + private Map config; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainPageReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainPageReqVO.java new file mode 100644 index 00000000..723b33a6 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainPageReqVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainPageReqVO extends PageParam { + + @Schema(description = "链编码") + private String code; + + @Schema(description = "链名称") + private String name; + + @Schema(description = "链状态") + private Integer status; + + @Schema(description = "创建时间范围") + private LocalDateTime[] createTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainRespVO.java new file mode 100644 index 00000000..b143e4f5 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainRespVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainRespVO extends RuleChainBaseVO { + + @Schema(description = "链ID") + private Long id; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "LiteFlow DSL") + private String liteflowDsl; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainStructureVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainStructureVO.java new file mode 100644 index 00000000..b42d5ea1 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainStructureVO.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class RuleChainStructureVO { + + @Schema(description = "节点列表") + private List nodes = new ArrayList<>(); + + @Schema(description = "连线列表") + private List links = new ArrayList<>(); + + @Schema(description = "自定义 LiteFlow 表达式") + private String entryExpression; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainUpdateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainUpdateReqVO.java new file mode 100644 index 00000000..03b3c270 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/chain/vo/RuleChainUpdateReqVO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.rule.controller.admin.chain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainUpdateReqVO extends RuleChainBaseVO { + + @Schema(description = "链ID", example = "1001") + @NotNull(message = "链ID不能为空") + private Long id; + + @Schema(description = "链版本号", example = "1.0.1") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/RuleDefinitionController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/RuleDefinitionController.java new file mode 100644 index 00000000..996e374e --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/RuleDefinitionController.java @@ -0,0 +1,96 @@ +package com.zt.plat.module.rule.controller.admin.definition; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionCreateReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionPageReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionRespVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionUpdateReqVO; +import com.zt.plat.module.rule.convert.definition.RuleDefinitionConvert; +import com.zt.plat.module.rule.dal.dataobject.definition.RuleDefinitionDO; +import com.zt.plat.module.rule.service.definition.RuleDefinitionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 规则定义") +@RestController +@RequestMapping("/admin/rule/definition") +@Validated +public class RuleDefinitionController { + + @Resource + private RuleDefinitionService ruleDefinitionService; + + @PostMapping("/create") + @Operation(summary = "创建规则定义") + @PreAuthorize("@ss.hasPermission('rule:definition:create')") + public CommonResult createRule(@Valid @RequestBody RuleDefinitionCreateReqVO reqVO) { + return success(ruleDefinitionService.createRule(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新规则定义") + @PreAuthorize("@ss.hasPermission('rule:definition:update')") + public CommonResult updateRule(@Valid @RequestBody RuleDefinitionUpdateReqVO reqVO) { + ruleDefinitionService.updateRule(reqVO); + return success(Boolean.TRUE); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除规则定义") + @Parameter(name = "id", description = "规则定义编号", required = true) + @PreAuthorize("@ss.hasPermission('rule:definition:delete')") + public CommonResult deleteRule(@RequestParam("id") Long id) { + ruleDefinitionService.deleteRule(id); + return success(Boolean.TRUE); + } + + @GetMapping("/get") + @Operation(summary = "获取规则定义详情") + @Parameter(name = "id", description = "规则定义编号", required = true) + @PreAuthorize("@ss.hasPermission('rule:definition:query')") + public CommonResult getRule(@RequestParam("id") Long id) { + RuleDefinitionDO ruleDefinition = ruleDefinitionService.getRule(id); + return success(ruleDefinition != null ? RuleDefinitionConvert.INSTANCE.convert(ruleDefinition) : null); + } + + @GetMapping("/page") + @Operation(summary = "分页查询规则定义") + @PreAuthorize("@ss.hasPermission('rule:definition:query')") + public CommonResult> getRulePage(@Valid RuleDefinitionPageReqVO reqVO) { + PageResult pageResult = ruleDefinitionService.getRulePage(reqVO); + return success(RuleDefinitionConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/list") + @Operation(summary = "批量查询规则定义") + @PreAuthorize("@ss.hasPermission('rule:definition:query')") + public CommonResult> getRuleList(@RequestParam("ids") @NotEmpty Collection ids) { + List rules = ruleDefinitionService.getRules(ids); + if (Objects.isNull(rules) || rules.isEmpty()) { + return success(Collections.emptyList()); + } + return success(RuleDefinitionConvert.INSTANCE.convertList(rules)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionBaseVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionBaseVO.java new file mode 100644 index 00000000..70883eba --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionBaseVO.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.rule.controller.admin.definition.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Base VO for creating/updating rule definitions. + */ +@Data +public class RuleDefinitionBaseVO { + + @Schema(description = "规则编码", example = "order_amount_check") + @NotBlank(message = "规则编码不能为空") + private String code; + + @Schema(description = "规则名称", example = "订单金额校验") + @NotBlank(message = "规则名称不能为空") + private String name; + + @Schema(description = "规则类型", example = "1") + @NotNull(message = "规则类型不能为空") + private Integer type; + + @Schema(description = "LiteFlow 脚本内容") + private String dsl; + + @Schema(description = "脚本语言", example = "groovy") + private String scriptLanguage; + + @Schema(description = "Spring Bean 引用", example = "ruleAmountCheckComponent") + private String beanRef; + + @Schema(description = "规则配置 JSON") + private String configJson; + + @Schema(description = "状态", example = "1") + private Integer status; + + @Schema(description = "备注") + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionCreateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionCreateReqVO.java new file mode 100644 index 00000000..affd662f --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionCreateReqVO.java @@ -0,0 +1,16 @@ +package com.zt.plat.module.rule.controller.admin.definition.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Request VO for creating a rule definition. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleDefinitionCreateReqVO extends RuleDefinitionBaseVO { + + @Schema(description = "初始版本号", example = "1.0.0") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionPageReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionPageReqVO.java new file mode 100644 index 00000000..09a7fb53 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionPageReqVO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.rule.controller.admin.definition.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * Paging request for rule definitions. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleDefinitionPageReqVO extends PageParam { + + @Schema(description = "规则编码") + private String code; + + @Schema(description = "规则名称") + private String name; + + @Schema(description = "规则类型") + private Integer type; + + @Schema(description = "规则状态") + private Integer status; + + @Schema(description = "创建时间范围") + private LocalDateTime[] createTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionRespVO.java new file mode 100644 index 00000000..d680473b --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionRespVO.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.rule.controller.admin.definition.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * Response VO describing a rule definition. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleDefinitionRespVO extends RuleDefinitionBaseVO { + + @Schema(description = "主键", example = "1001") + private Long id; + + @Schema(description = "版本号", example = "1.0.1") + private String version; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionUpdateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionUpdateReqVO.java new file mode 100644 index 00000000..3c03e0d9 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/definition/vo/RuleDefinitionUpdateReqVO.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.rule.controller.admin.definition.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Request VO for updating a rule definition. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleDefinitionUpdateReqVO extends RuleDefinitionBaseVO { + + @Schema(description = "主键", example = "1001") + @NotNull(message = "主键不能为空") + private Long id; + + @Schema(description = "版本号", example = "1.0.1") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/RulePublishController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/RulePublishController.java new file mode 100644 index 00000000..91b84100 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/RulePublishController.java @@ -0,0 +1,74 @@ +package com.zt.plat.module.rule.controller.admin.publish; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseCreateReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleasePageReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseRespVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseRollbackReqVO; +import com.zt.plat.module.rule.convert.publish.RuleReleaseConvert; +import com.zt.plat.module.rule.dal.dataobject.release.RuleReleaseRecordDO; +import com.zt.plat.module.rule.service.publish.RulePublishService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 规则发布") +@RestController +@RequestMapping("/admin/rule/publish") +@Validated +public class RulePublishController { + + @Resource + private RulePublishService rulePublishService; + + @PostMapping("/publish") + @Operation(summary = "发布业务规则链") + @PreAuthorize("@ss.hasPermission('rule:publish:publish')") + public CommonResult publish(@Valid @RequestBody RuleReleaseCreateReqVO reqVO) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + String username = SecurityFrameworkUtils.getLoginUserNickname(); + RuleReleaseRecordDO record = rulePublishService.publish(reqVO, userId, username); + return success(RuleReleaseConvert.INSTANCE.convert(record)); + } + + @PostMapping("/rollback") + @Operation(summary = "回滚业务规则链版本") + @PreAuthorize("@ss.hasPermission('rule:publish:rollback')") + public CommonResult rollback(@Valid @RequestBody RuleReleaseRollbackReqVO reqVO) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + String username = SecurityFrameworkUtils.getLoginUserNickname(); + rulePublishService.rollback(reqVO, userId, username); + return success(Boolean.TRUE); + } + + @GetMapping("/page") + @Operation(summary = "分页查询发布记录") + @PreAuthorize("@ss.hasPermission('rule:publish:query')") + public CommonResult> getReleasePage(@Valid RuleReleasePageReqVO reqVO) { + PageResult pageResult = rulePublishService.getReleasePage(reqVO); + return success(RuleReleaseConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/latest") + @Operation(summary = "获取最新发布记录") + @Parameter(name = "business", description = "业务标识", required = true) + @PreAuthorize("@ss.hasPermission('rule:publish:query')") + public CommonResult getLatest(@RequestParam("business") String business) { + RuleReleaseRecordDO record = rulePublishService.getLatest(business); + return success(record != null ? RuleReleaseConvert.INSTANCE.convert(record) : null); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseCreateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseCreateReqVO.java new file mode 100644 index 00000000..d00064d3 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseCreateReqVO.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.rule.controller.admin.publish.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RuleReleaseCreateReqVO { + + @Schema(description = "业务标识") + @NotBlank(message = "业务标识不能为空") + private String business; + + @Schema(description = "目标版本号", example = "v3") + private String version; + + @Schema(description = "是否仅生成不发布", example = "false") + private Boolean dryRun; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleasePageReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleasePageReqVO.java new file mode 100644 index 00000000..8f1456a9 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleasePageReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.controller.admin.publish.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleReleasePageReqVO extends PageParam { + + @Schema(description = "业务标识") + private String business; + + @Schema(description = "版本号") + private String version; + + @Schema(description = "状态") + private Integer status; + + private LocalDateTime[] releaseTime; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRespVO.java new file mode 100644 index 00000000..439a4e1f --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRespVO.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.rule.controller.admin.publish.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class RuleReleaseRespVO { + + private Long id; + + private String business; + + private String chainId; + + private String chainCode; + + private String version; + + private Integer status; + + private Long releaseUserId; + + private String releaseUserName; + + private LocalDateTime releaseTime; + + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRollbackReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRollbackReqVO.java new file mode 100644 index 00000000..9784b299 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/publish/vo/RuleReleaseRollbackReqVO.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.rule.controller.admin.publish.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class RuleReleaseRollbackReqVO { + + @Schema(description = "业务标识") + @NotBlank(message = "业务标识不能为空") + private String business; + + @Schema(description = "目标回滚版本") + @NotBlank(message = "回滚版本不能为空") + private String version; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/RuleController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/RuleController.java deleted file mode 100644 index 8b36413c..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/RuleController.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule; - -import com.zt.plat.framework.common.pojo.CommonResult; -import com.zt.plat.framework.common.pojo.PageParam; -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.framework.common.util.object.BeanUtils; -import com.zt.plat.framework.excel.core.util.ExcelUtils; -import com.zt.plat.framework.operatelog.core.annotations.OperateLog; -import com.zt.plat.module.rule.controller.admin.rule.vo.*; -import com.zt.plat.module.rule.convert.rule.RuleConvert; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import com.zt.plat.module.rule.service.rule.RuleService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; -import javax.validation.Valid; -import java.io.IOException; -import java.util.List; - -import static com.zt.plat.framework.common.pojo.CommonResult.success; -import static com.zt.plat.framework.operatelog.core.enums.OperateTypeEnum.*; - -@Tag(name = "管理后台 - 规则引擎") -@RestController -@RequestMapping("/admin/rule/rule") -@Validated -public class RuleController { - - @Resource - private RuleService ruleService; - - @PostMapping("/create") - @Operation(summary = "创建规则") - @PreAuthorize("@ss.hasPermission('rule:rule:create')") - @OperateLog(type = CREATE) - public CommonResult createRule(@Valid @RequestBody RuleCreateReqVO createReqVO) { - return success(ruleService.createRule(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新规则") - @PreAuthorize("@ss.hasPermission('rule:rule:update')") - @OperateLog(type = UPDATE) - public CommonResult updateRule(@Valid @RequestBody RuleUpdateReqVO updateReqVO) { - ruleService.updateRule(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除规则") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('rule:rule:delete')") - @OperateLog(type = DELETE) - public CommonResult deleteRule(@RequestParam("id") Long id) { - ruleService.deleteRule(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得规则") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('rule:rule:query')") - public CommonResult getRule(@RequestParam("id") Long id) { - RuleDO rule = ruleService.getRule(id); - return success(RuleConvert.INSTANCE.convert(rule)); - } - - @GetMapping("/page") - @Operation(summary = "获得规则分页") - @PreAuthorize("@ss.hasPermission('rule:rule:query')") - public CommonResult> getRulePage(@Valid RulePageReqVO pageReqVO) { - PageResult pageResult = ruleService.getRulePage(pageReqVO); - return success(RuleConvert.INSTANCE.convertPage(pageResult)); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出规则 Excel") - @PreAuthorize("@ss.hasPermission('rule:rule:export')") - @OperateLog(type = EXPORT) - public void exportRuleExcel(@Valid RulePageReqVO pageReqVO, - HttpServletResponse response) throws IOException { - pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = ruleService.getRulePage(pageReqVO).getList(); - // 导出 Excel - ExcelUtils.write(response, "规则.xls", "数据", RuleRespVO.class, - BeanUtils.toBean(list, RuleRespVO.class)); - } - - @PostMapping("/execute") - @Operation(summary = "执行规则") - @PreAuthorize("@ss.hasPermission('rule:rule:execute')") - @OperateLog(type = OTHER) - public CommonResult executeRule(@Valid @RequestBody RuleExecuteReqVO executeReqVO) { - return success(ruleService.executeRule(executeReqVO)); - } - - @PutMapping("/enable") - @Operation(summary = "启用规则") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('rule:rule:update')") - @OperateLog(type = UPDATE) - public CommonResult enableRule(@RequestParam("id") Long id) { - ruleService.enableRule(id); - return success(true); - } - - @PutMapping("/disable") - @Operation(summary = "禁用规则") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('rule:rule:update')") - @OperateLog(type = UPDATE) - public CommonResult disableRule(@RequestParam("id") Long id) { - ruleService.disableRule(id); - return success(true); - } - - @PostMapping("/validate") - @Operation(summary = "验证规则配置") - @PreAuthorize("@ss.hasPermission('rule:rule:validate')") - public CommonResult validateRuleConfig(@RequestBody String config) { - return success(ruleService.validateRuleConfig(config)); - } - - @GetMapping("/hello") - @Operation(summary = "Hello Rule") - public CommonResult hello() { - return success("Hello, Rule!"); - } - -} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleBaseVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleBaseVO.java deleted file mode 100644 index 08c94924..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleBaseVO.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; - -/** - * 规则 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class RuleBaseVO { - - @Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户积分计算规则") - @NotBlank(message = "规则名称不能为空") - private String name; - - @Schema(description = "规则描述", example = "根据用户行为计算积分奖励") - private String description; - - @Schema(description = "规则类型:1-原子规则 2-链式规则", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "规则类型不能为空") - private Integer type; - - @Schema(description = "规则状态:0-禁用 1-启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "规则状态不能为空") - private Integer status; - - @Schema(description = "规则配置(JSON格式)", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "规则配置不能为空") - private String config; - - @Schema(description = "LiteFlow规则链ID", example = "userPointsChain") - private String chainId; - - @Schema(description = "规则版本", example = "1.0.0") - private String version; - - @Schema(description = "排序", example = "1") - private Integer sort; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleCreateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleCreateReqVO.java deleted file mode 100644 index 8f349b98..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleCreateReqVO.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 规则创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class RuleCreateReqVO extends RuleBaseVO { - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteReqVO.java deleted file mode 100644 index c69db1a5..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteReqVO.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.List; -import java.util.Map; - -/** - * 规则执行请求 VO - */ -@Data -public class RuleExecuteReqVO { - - /** - * 规则ID - */ - private Long ruleId; - - /** - * 规则链ID(与ruleId二选一) - */ - private String chainId; - - /** - * 执行上下文数据 - */ - private Map contextData; - - /** - * 扩展参数 - */ - private Map extParams; - - /** - * 规则执行配置(JSON格式的规则定义) - */ - @Data - public static class RuleConfig { - - /** - * 规则链配置 - */ - private ChainConfig chain; - - /** - * 节点配置列表 - */ - private List nodes; - } - - /** - * 规则链配置 - */ - @Data - public static class ChainConfig { - - /** - * 链ID - */ - private String chainId; - - /** - * 链名称 - */ - private String chainName; - - /** - * 执行表达式(THEN、WHEN、FOR等) - */ - private String expression; - - /** - * 是否启用 - */ - private Boolean enable; - } - - /** - * 节点配置 - */ - @Data - public static class NodeConfig { - - /** - * 节点ID - */ - private String nodeId; - - /** - * 节点名称 - */ - private String nodeName; - - /** - * 节点类型:common-普通节点,condition-条件节点,switch-选择节点,for-循环节点 - */ - private String nodeType; - - /** - * 节点类全限定名 - */ - private String clazz; - - /** - * 节点配置参数 - */ - private Map properties; - - /** - * 是否启用 - */ - private Boolean enable; - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteRespVO.java deleted file mode 100644 index eeddb2c1..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleExecuteRespVO.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 规则执行结果 VO - */ -@Data -public class RuleExecuteRespVO { - - /** - * 执行是否成功 - */ - private Boolean success; - - /** - * 错误消息 - */ - private String errorMessage; - - /** - * 执行结果数据 - */ - private Map resultData; - - /** - * 执行耗时(毫秒) - */ - private Long executionTime; - - /** - * 执行开始时间 - */ - private LocalDateTime startTime; - - /** - * 执行结束时间 - */ - private LocalDateTime endTime; - - /** - * 执行的规则链ID - */ - private String chainId; - - /** - * 执行的节点列表 - */ - private String executedNodes; - - /** - * 执行上下文快照 - */ - private Map contextSnapshot; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RulePageReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RulePageReqVO.java deleted file mode 100644 index f4f9eb94..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RulePageReqVO.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import com.zt.plat.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 规则分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class RulePageReqVO extends PageParam { - - @Schema(description = "规则名称", example = "用户积分规则") - private String name; - - @Schema(description = "规则类型:1-原子规则 2-链式规则", example = "1") - private Integer type; - - @Schema(description = "规则状态:0-禁用 1-启用", example = "1") - private Integer status; - - @Schema(description = "规则链ID", example = "userPointsChain") - private String chainId; - - @Schema(description = "创建时间") - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleRespVO.java deleted file mode 100644 index 40eba82b..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleRespVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - 规则 Response VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class RuleRespVO extends RuleBaseVO { - - @Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime updateTime; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleUpdateReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleUpdateReqVO.java deleted file mode 100644 index 6326f9fb..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/rule/vo/RuleUpdateReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.zt.plat.module.rule.controller.admin.rule.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import javax.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 规则更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class RuleUpdateReqVO extends RuleBaseVO { - - @Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "规则ID不能为空") - private Long id; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/RuleSimulationController.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/RuleSimulationController.java new file mode 100644 index 00000000..8f034252 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/RuleSimulationController.java @@ -0,0 +1,35 @@ +package com.zt.plat.module.rule.controller.admin.simulation; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteReqVO; +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteRespVO; +import com.zt.plat.module.rule.service.simulation.RuleSimulationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 规则模拟") +@RestController +@RequestMapping("/admin/rule/simulation") +@Validated +public class RuleSimulationController { + + @Resource + private RuleSimulationService ruleSimulationService; + + @PostMapping("/execute") + @Operation(summary = "模拟执行业务规则链") + @PreAuthorize("@ss.hasPermission('rule:simulation:execute')") + public CommonResult execute(@Valid @RequestBody RuleSimulationExecuteReqVO reqVO) { + return success(ruleSimulationService.execute(reqVO)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteReqVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteReqVO.java new file mode 100644 index 00000000..b8970006 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteReqVO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.controller.admin.simulation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.Map; + +@Data +public class RuleSimulationExecuteReqVO { + + @Schema(description = "业务标识") + @NotBlank(message = "业务标识不能为空") + private String business; + + @Schema(description = "链版本,可为空表示最新") + private String version; + + @Schema(description = "链ID,可覆盖业务+版本") + private String chainId; + + @Schema(description = "请求数据") + private Map requestData; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteRespVO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteRespVO.java new file mode 100644 index 00000000..051e3ee2 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/controller/admin/simulation/vo/RuleSimulationExecuteRespVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.rule.controller.admin.simulation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +@Data +public class RuleSimulationExecuteRespVO { + + @Schema(description = "是否执行成功") + private boolean success; + + @Schema(description = "LiteFlow 响应消息") + private String message; + + @Schema(description = "返回数据快照") + private Map slotData; + + @Schema(description = "LiteFlow Trace") + private String trace; + + @Schema(description = "使用的链ID") + private String chainId; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/business/RuleBusinessConvert.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/business/RuleBusinessConvert.java new file mode 100644 index 00000000..4fbcccb8 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/business/RuleBusinessConvert.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.convert.business; + +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindSaveReqVO; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessBindingDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface RuleBusinessConvert { + + RuleBusinessConvert INSTANCE = Mappers.getMapper(RuleBusinessConvert.class); + + @Mapping(target = "configJson", ignore = true) + @Mapping(target = "effectiveVersion", ignore = true) + RuleBusinessBindingDO convert(RuleBusinessBindSaveReqVO bean); + + RuleBusinessBindRespVO convert(RuleBusinessBindingDO bean); + + List convertList(List list); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/chain/RuleChainConvert.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/chain/RuleChainConvert.java new file mode 100644 index 00000000..f26866f8 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/chain/RuleChainConvert.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.rule.convert.chain; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainCreateReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainRespVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainStructureVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainUpdateReqVO; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring") +public abstract class RuleChainConvert { + + @Autowired + protected ObjectMapper objectMapper; + + @Mapping(target = "structureJson", ignore = true) + @Mapping(target = "liteflowDsl", ignore = true) + public abstract RuleChainDO convert(RuleChainCreateReqVO bean); + + @Mapping(target = "structureJson", ignore = true) + @Mapping(target = "liteflowDsl", ignore = true) + public abstract RuleChainDO convert(RuleChainUpdateReqVO bean); + + public RuleChainRespVO convert(RuleChainDO bean) { + RuleChainRespVO vo = new RuleChainRespVO(); + vo.setId(bean.getId()); + vo.setCode(bean.getCode()); + vo.setName(bean.getName()); + vo.setDescription(bean.getDescription()); + vo.setStatus(bean.getStatus()); + vo.setVersion(bean.getVersion()); + vo.setLiteflowDsl(bean.getLiteflowDsl()); + vo.setRemark(bean.getRemark()); + vo.setCreateTime(bean.getCreateTime()); + vo.setUpdateTime(bean.getUpdateTime()); + if (bean.getStructureJson() != null) { + vo.setStructure(readStructure(bean.getStructureJson())); + } + return vo; + } + + public List convertList(List list) { + return list.stream().map(this::convert).collect(Collectors.toList()); + } + + public PageResult convertPage(PageResult page) { + return new PageResult<>(convertList(page.getList()), page.getTotal()); + } + + public RuleChainStructureVO readStructure(String structureJson) { + try { + return objectMapper.readValue(structureJson, RuleChainStructureVO.class); + } catch (Exception ex) { + return null; + } + } + + public String writeStructure(RuleChainStructureVO structure) { + try { + return objectMapper.writeValueAsString(structure); + } catch (Exception ex) { + return null; + } + } + + public RuleChainStructureDTO toDto(RuleChainStructureVO vo) { + return objectMapper.convertValue(vo, RuleChainStructureDTO.class); + } + + public RuleChainStructureVO toVo(RuleChainStructureDTO dto) { + return objectMapper.convertValue(dto, RuleChainStructureVO.class); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/definition/RuleDefinitionConvert.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/definition/RuleDefinitionConvert.java new file mode 100644 index 00000000..c37ea1b8 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/definition/RuleDefinitionConvert.java @@ -0,0 +1,27 @@ +package com.zt.plat.module.rule.convert.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionCreateReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionRespVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionUpdateReqVO; +import com.zt.plat.module.rule.dal.dataobject.definition.RuleDefinitionDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface RuleDefinitionConvert { + + RuleDefinitionConvert INSTANCE = Mappers.getMapper(RuleDefinitionConvert.class); + + RuleDefinitionDO convert(RuleDefinitionCreateReqVO bean); + + RuleDefinitionDO convert(RuleDefinitionUpdateReqVO bean); + + RuleDefinitionRespVO convert(RuleDefinitionDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/publish/RuleReleaseConvert.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/publish/RuleReleaseConvert.java new file mode 100644 index 00000000..b8435a89 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/publish/RuleReleaseConvert.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.rule.convert.publish; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseRespVO; +import com.zt.plat.module.rule.dal.dataobject.release.RuleReleaseRecordDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface RuleReleaseConvert { + + RuleReleaseConvert INSTANCE = Mappers.getMapper(RuleReleaseConvert.class); + + RuleReleaseRespVO convert(RuleReleaseRecordDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/rule/RuleConvert.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/rule/RuleConvert.java deleted file mode 100644 index d811488a..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/convert/rule/RuleConvert.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.zt.plat.module.rule.convert.rule; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.module.rule.controller.admin.rule.vo.*; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -/** - * 规则 Convert - * - * @author 芋道源码 - */ -@Mapper -public interface RuleConvert { - - RuleConvert INSTANCE = Mappers.getMapper(RuleConvert.class); - - RuleDO convert(RuleCreateReqVO bean); - - RuleDO convert(RuleUpdateReqVO bean); - - RuleRespVO convert(RuleDO bean); - - List convertList(List list); - - PageResult convertPage(PageResult page); - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessBindingDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessBindingDO.java new file mode 100644 index 00000000..abf8a81b --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessBindingDO.java @@ -0,0 +1,47 @@ +package com.zt.plat.module.rule.dal.dataobject.business; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Mapping between business code and selected rule chain. + */ +@TableName(value = "rule_business", autoResultMap = true) +@KeySequence("rule_business_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleBusinessBindingDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * Business identifier, e.g. order.create. + */ + private String business; + + @TableField("rule_chain_id") + private Long ruleChainId; + + @TableField("override_strategy") + private Integer overrideStrategy; + + private Boolean locked; + + @TableField("effective_version") + private String effectiveVersion; + + /** + * Additional metadata retained as JSON. + */ + @TableField("config_json") + private String configJson; + + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessRelationDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessRelationDO.java new file mode 100644 index 00000000..46ab5484 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/business/RuleBusinessRelationDO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.rule.dal.dataobject.business; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Parent-child hierarchy of business codes to support inheritance. + */ +@TableName(value = "rule_business_relation", autoResultMap = true) +@KeySequence("rule_business_relation_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleBusinessRelationDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @TableField("parent_business") + private String parentBusiness; + + @TableField("child_business") + private String childBusiness; + + private Integer sort; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDO.java new file mode 100644 index 00000000..634c713f --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDO.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.rule.dal.dataobject.chain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Stored chain definition with designer structure and compiled DSL. + */ +@TableName(value = "rule_chain", autoResultMap = true) +@KeySequence("rule_chain_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private String code; + + private String name; + + private String description; + + /** + * JSON serialized structure returned by the visual orchestrator. + */ + private String structureJson; + + /** + * LiteFlow DSL compiled from the structure. + */ + private String liteflowDsl; + + private Integer status; + + private String version; + + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDependencyDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDependencyDO.java new file mode 100644 index 00000000..547b9a94 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/chain/RuleChainDependencyDO.java @@ -0,0 +1,49 @@ +package com.zt.plat.module.rule.dal.dataobject.chain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Relationship between a chain and its component rules or nested chains. + */ +@TableName(value = "rule_chain_dependency", autoResultMap = true) +@KeySequence("rule_chain_dependency_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleChainDependencyDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @TableField("parent_chain_id") + private Long parentChainId; + + @TableField("child_rule_id") + private Long childRuleId; + + /** + * Dependency type, such as ATOM/CHAIN/CONDITION/... referencing {@link com.zt.plat.module.rule.enums.RuleNodeTypeEnum}. + */ + private Integer linkType; + + @TableField("order_index") + private Integer orderIndex; + + @TableField("parallel_group") + private String parallelGroup; + + @TableField("condition_expr") + private String conditionExpr; + + /** + * Additional configuration or node metadata serialized to JSON. + */ + @TableField("config_json") + private String configJson; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/definition/RuleDefinitionDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/definition/RuleDefinitionDO.java new file mode 100644 index 00000000..ea6ea405 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/definition/RuleDefinitionDO.java @@ -0,0 +1,72 @@ +package com.zt.plat.module.rule.dal.dataobject.definition; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * Data object representing an atomic rule definition. + */ +@TableName(value = "rule_definition", autoResultMap = true) +@KeySequence("rule_definition_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleDefinitionDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * Unique code of the rule for reference in chains. + */ + private String code; + + /** + * Display name of the rule. + */ + private String name; + + /** + * Rule type, see {@link com.zt.plat.module.rule.enums.RuleTypeEnum}. + */ + private Integer type; + + /** + * DSL content for LiteFlow script nodes if applicable. + */ + private String dsl; + + /** + * Script language identifier when the rule is script based. + */ + private String scriptLanguage; + + /** + * Spring bean reference when the rule delegates to a bean. + */ + private String beanRef; + + /** + * Serialized configuration for the rule (JSON). + */ + private String configJson; + + /** + * Rule status, see {@link com.zt.plat.module.rule.enums.RuleStatusEnum}. + */ + private Integer status; + + /** + * Rule version, semantic or numeric. + */ + private String version; + + /** + * Additional remarks. + */ + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/release/RuleReleaseRecordDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/release/RuleReleaseRecordDO.java new file mode 100644 index 00000000..920b1c12 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/release/RuleReleaseRecordDO.java @@ -0,0 +1,51 @@ +package com.zt.plat.module.rule.dal.dataobject.release; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * Publication audit record for business rule chains. + */ +@TableName(value = "rule_release_record", autoResultMap = true) +@KeySequence("rule_release_record_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RuleReleaseRecordDO extends TenantBaseDO { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + private String business; + + /** + * Generated chain id, usually business:version. + */ + @TableField("chain_id") + private String chainId; + + @TableField("chain_code") + private String chainCode; + + private String version; + + private Integer status; + + @TableField("release_user_id") + private Long releaseUserId; + + @TableField("release_user_name") + private String releaseUserName; + + @TableField("release_time") + private LocalDateTime releaseTime; + + private String remark; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/rule/RuleDO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/rule/RuleDO.java deleted file mode 100644 index f134110a..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/dataobject/rule/RuleDO.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.zt.plat.module.rule.dal.dataobject.rule; - -import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * 规则 DO - * - * @author 芋道源码 - */ -@TableName("rule_rule") -@KeySequence("rule_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class RuleDO extends BaseDO { - - /** - * 规则ID - */ - @TableId - private Long id; - - /** - * 规则名称 - */ - private String name; - - /** - * 规则描述 - */ - private String description; - - /** - * 规则类型:1-原子规则 2-链式规则 - */ - private Integer type; - - /** - * 规则状态:0-禁用 1-启用 - */ - private Integer status; - - /** - * 规则配置(JSON格式) - */ - private String config; - - /** - * LiteFlow规则链ID - */ - private String chainId; - - /** - * 规则版本 - */ - private String version; - - /** - * 排序 - */ - private Integer sort; - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessBindingMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessBindingMapper.java new file mode 100644 index 00000000..5f98443c --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessBindingMapper.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.rule.dal.mysql.business; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindPageReqVO; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessBindingDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface RuleBusinessBindingMapper extends BaseMapperX { + + default RuleBusinessBindingDO selectByBusiness(String business) { + return selectOne(RuleBusinessBindingDO::getBusiness, business); + } + + default List selectChildren(String parentBusinessPrefix) { + return selectList(new LambdaQueryWrapperX() + .likeRight(RuleBusinessBindingDO::getBusiness, parentBusinessPrefix)); + } + + default PageResult selectPage(RuleBusinessBindPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(RuleBusinessBindingDO::getBusiness, reqVO.getBusiness()) + .eqIfPresent(RuleBusinessBindingDO::getOverrideStrategy, reqVO.getOverrideStrategy()) + .eqIfPresent(RuleBusinessBindingDO::getLocked, reqVO.getLocked()) + .betweenIfPresent(RuleBusinessBindingDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(RuleBusinessBindingDO::getId)); + } + + default List selectAll() { + return selectList(); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessRelationMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessRelationMapper.java new file mode 100644 index 00000000..a5ce4c87 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/business/RuleBusinessRelationMapper.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.rule.dal.mysql.business; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessRelationDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface RuleBusinessRelationMapper extends BaseMapperX { + + default List selectChildren(String parentBusiness) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(RuleBusinessRelationDO::getParentBusiness, parentBusiness) + .orderByAsc(RuleBusinessRelationDO::getSort)); + } + + default RuleBusinessRelationDO selectByChild(String childBusiness) { + return selectOne(RuleBusinessRelationDO::getChildBusiness, childBusiness); + } + + default List selectAll() { + return selectList(); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainDependencyMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainDependencyMapper.java new file mode 100644 index 00000000..07eaa374 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainDependencyMapper.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.dal.mysql.chain; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDependencyDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface RuleChainDependencyMapper extends BaseMapperX { + + default List selectByParentChainId(Long parentChainId) { + return selectList(RuleChainDependencyDO::getParentChainId, parentChainId); + } + + default void deleteByParentChainId(Long parentChainId) { + delete(RuleChainDependencyDO::getParentChainId, parentChainId); + } + + default List selectByParentChainIds(Collection parentChainIds) { + return selectList(RuleChainDependencyDO::getParentChainId, parentChainIds); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainMapper.java new file mode 100644 index 00000000..7d5f40bf --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/chain/RuleChainMapper.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.rule.dal.mysql.chain; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainPageReqVO; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface RuleChainMapper extends BaseMapperX { + + default RuleChainDO selectByCode(String code) { + return selectOne(RuleChainDO::getCode, code); + } + + default PageResult selectPage(RuleChainPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(RuleChainDO::getName, reqVO.getName()) + .eqIfPresent(RuleChainDO::getCode, reqVO.getCode()) + .eqIfPresent(RuleChainDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(RuleChainDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(RuleChainDO::getId)); + } + + default List selectByCodes(List codes) { + return selectList(RuleChainDO::getCode, codes); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/definition/RuleDefinitionMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/definition/RuleDefinitionMapper.java new file mode 100644 index 00000000..c9297990 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/definition/RuleDefinitionMapper.java @@ -0,0 +1,40 @@ +package com.zt.plat.module.rule.dal.mysql.definition; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionPageReqVO; +import com.zt.plat.module.rule.dal.dataobject.definition.RuleDefinitionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface RuleDefinitionMapper extends BaseMapperX { + + default RuleDefinitionDO selectByCode(String code) { + return selectOne(RuleDefinitionDO::getCode, code); + } + + default PageResult selectPage(RuleDefinitionPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(RuleDefinitionDO::getName, reqVO.getName()) + .eqIfPresent(RuleDefinitionDO::getCode, reqVO.getCode()) + .eqIfPresent(RuleDefinitionDO::getType, reqVO.getType()) + .eqIfPresent(RuleDefinitionDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(RuleDefinitionDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(RuleDefinitionDO::getId)); + } + + default List selectListByIds(Collection ids) { + return selectList(RuleDefinitionDO::getId, ids); + } + + default void updateStatusBatch(Collection ids, Integer status) { + update(null, new LambdaUpdateWrapper() + .in(RuleDefinitionDO::getId, ids) + .set(RuleDefinitionDO::getStatus, status)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/release/RuleReleaseRecordMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/release/RuleReleaseRecordMapper.java new file mode 100644 index 00000000..df00f71a --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/release/RuleReleaseRecordMapper.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.rule.dal.mysql.release; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleasePageReqVO; +import com.zt.plat.module.rule.dal.dataobject.release.RuleReleaseRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface RuleReleaseRecordMapper extends BaseMapperX { + + default PageResult selectPage(RuleReleasePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(RuleReleaseRecordDO::getBusiness, reqVO.getBusiness()) + .eqIfPresent(RuleReleaseRecordDO::getStatus, reqVO.getStatus()) + .likeIfPresent(RuleReleaseRecordDO::getVersion, reqVO.getVersion()) + .betweenIfPresent(RuleReleaseRecordDO::getReleaseTime, reqVO.getReleaseTime()) + .orderByDesc(RuleReleaseRecordDO::getReleaseTime)); + } + + default RuleReleaseRecordDO selectLatest(String business) { + List records = selectList(new LambdaQueryWrapperX() + .eq(RuleReleaseRecordDO::getBusiness, business) + .orderByDesc(RuleReleaseRecordDO::getReleaseTime) + .last("limit 1")); + return records.isEmpty() ? null : records.get(0); + } + + default RuleReleaseRecordDO selectByBusinessAndVersion(String business, String version) { + return selectOne(new LambdaQueryWrapperX() + .eq(RuleReleaseRecordDO::getBusiness, business) + .eq(RuleReleaseRecordDO::getVersion, version)); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/rule/RuleMapper.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/rule/RuleMapper.java deleted file mode 100644 index 27a57fb5..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/dal/mysql/rule/RuleMapper.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.zt.plat.module.rule.dal.mysql.rule; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; -import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; -import com.zt.plat.module.rule.controller.admin.rule.vo.RulePageReqVO; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * 规则 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface RuleMapper extends BaseMapperX { - - default PageResult selectPage(RulePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(RuleDO::getName, reqVO.getName()) - .eqIfPresent(RuleDO::getType, reqVO.getType()) - .eqIfPresent(RuleDO::getStatus, reqVO.getStatus()) - .eqIfPresent(RuleDO::getChainId, reqVO.getChainId()) - .betweenIfPresent(RuleDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(RuleDO::getId)); - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java index bc27ca90..2db11530 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/enums/ErrorCodeConstants.java @@ -3,19 +3,26 @@ package com.zt.plat.module.rule.enums; import com.zt.plat.framework.common.exception.ErrorCode; /** - * Rule 错误码枚举类 - * - * rule 系统,使用 1-009-000-000 段 + * Error code definitions for the rule module (range 1-045-000-000 ~ 1-045-099-999). */ public interface ErrorCodeConstants { - // ========== 规则相关错误码 1-009-001-000 ========== - ErrorCode RULE_NOT_EXISTS = new ErrorCode(1_009_001_000, "规则不存在"); - ErrorCode RULE_CONFIG_INVALID = new ErrorCode(1_009_001_001, "规则配置无效"); - ErrorCode RULE_LOAD_FAILED = new ErrorCode(1_009_001_002, "规则加载失败"); - ErrorCode RULE_NOT_ENABLED = new ErrorCode(1_009_001_003, "规则未启用"); - ErrorCode RULE_CHAIN_ID_EMPTY = new ErrorCode(1_009_001_004, "规则链ID不能为空"); - ErrorCode RULE_EXECUTE_FAILED = new ErrorCode(1_009_001_005, "规则执行失败"); - ErrorCode RULE_VALIDATION_FAILED = new ErrorCode(1_009_001_006, "规则验证失败"); + ErrorCode RULE_DEFINITION_NOT_EXISTS = new ErrorCode(1_045_000_001, "规则定义不存在"); + ErrorCode RULE_DEFINITION_CODE_DUPLICATE = new ErrorCode(1_045_000_002, "规则编码已存在"); + ErrorCode RULE_DEFINITION_STATUS_INVALID = new ErrorCode(1_045_000_003, "规则状态不允许当前操作"); -} \ No newline at end of file + ErrorCode RULE_CHAIN_NOT_EXISTS = new ErrorCode(1_045_000_101, "规则链不存在"); + ErrorCode RULE_CHAIN_CODE_DUPLICATE = new ErrorCode(1_045_000_102, "规则链编码已存在"); + ErrorCode RULE_CHAIN_STRUCTURE_INVALID = new ErrorCode(1_045_000_103, "规则链结构不合法"); + ErrorCode RULE_CHAIN_DEPEND_RULE_MISSING = new ErrorCode(1_045_000_104, "规则链引用的规则不存在"); + + ErrorCode RULE_BUSINESS_NOT_EXISTS = new ErrorCode(1_045_000_201, "业务绑定不存在"); + ErrorCode RULE_BUSINESS_RELATION_CYCLE = new ErrorCode(1_045_000_202, "业务父子关系存在循环依赖"); + ErrorCode RULE_BUSINESS_LOCKED = new ErrorCode(1_045_000_203, "业务链路已锁定不允许修改"); + + ErrorCode RULE_RELEASE_NOT_EXISTS = new ErrorCode(1_045_000_301, "规则发布记录不存在"); + ErrorCode RULE_RELEASE_IN_PROGRESS = new ErrorCode(1_045_000_302, "存在进行中的发布任务"); + ErrorCode RULE_PUBLISH_PUSH_FAILED = new ErrorCode(1_045_000_303, "发布LiteFlow DSL失败"); + + ErrorCode RULE_SIMULATION_FAILED = new ErrorCode(1_045_000_401, "规则链模拟执行失败"); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RuleModuleConfiguration.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RuleModuleConfiguration.java new file mode 100644 index 00000000..efef39fb --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RuleModuleConfiguration.java @@ -0,0 +1,12 @@ +package com.zt.plat.module.rule.framework.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Shared configuration entry point for the rule module. + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(RulePublishProperties.class) +public class RuleModuleConfiguration { +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RulePublishProperties.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RulePublishProperties.java new file mode 100644 index 00000000..26f438c9 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/RulePublishProperties.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.rule.framework.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Rule module properties customised for publishing DSL to configuration center. + */ +@Data +@ConfigurationProperties(prefix = "rule.publish") +public class RulePublishProperties { + + /** + * Nacos group used when pushing LiteFlow DSL. + */ + private String nacosGroup = "RULES"; + + /** + * DataId prefix, final DataId is prefix + "/" + business + "/" + version. + */ + private String dataIdPrefix = "rule"; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/security/config/SecurityConfiguration.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/SecurityConfiguration.java similarity index 51% rename from zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/security/config/SecurityConfiguration.java rename to zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/SecurityConfiguration.java index 62b0e12a..d112471e 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/security/config/SecurityConfiguration.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/config/SecurityConfiguration.java @@ -1,42 +1,28 @@ -package com.zt.plat.module.rule.framework.security.config; +package com.zt.plat.module.rule.framework.config; import com.zt.plat.framework.security.config.AuthorizeRequestsCustomizer; -import com.zt.plat.module.infra.enums.ApiConstants; +import com.zt.plat.module.rule.enums.ApiConstants; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; - /** - * Rule 模块的 Security 配置 - * - * @author ZT + * Security configuration for rule module web endpoints. */ @Configuration(proxyBeanMethods = false) public class SecurityConfiguration { @Bean - public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + public AuthorizeRequestsCustomizer ruleModuleAuthorizeCustomizer() { return new AuthorizeRequestsCustomizer() { - @Override public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { - // Swagger 接口文档 - registry.requestMatchers("/v3/api-docs/**").permitAll() - .requestMatchers("/webjars/**").permitAll() - .requestMatchers("/swagger-ui").permitAll() - .requestMatchers("/swagger-ui/**").permitAll(); - // Druid 监控 + registry.requestMatchers("/v3/api-docs/**", "/webjars/**", "/swagger-ui", "/swagger-ui/**").permitAll(); registry.requestMatchers("/druid/**").permitAll(); - // Spring Boot Actuator 的安全配置 - registry.requestMatchers("/actuator").permitAll() - .requestMatchers("/actuator/**").permitAll(); - // RPC 服务的安全配置 + registry.requestMatchers("/actuator", "/actuator/**").permitAll(); registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); } - }; } - } diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleChainAssembler.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleChainAssembler.java new file mode 100644 index 00000000..428d95b8 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleChainAssembler.java @@ -0,0 +1,110 @@ +package com.zt.plat.module.rule.framework.liteflow; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import com.zt.plat.module.rule.service.dto.RuleChainNodeDTO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; +import jakarta.validation.ValidationException; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +/** + * Utility responsible for validating chain structure and producing LiteFlow DSL. + */ +@Component +public class RuleChainAssembler { + + private final ObjectMapper objectMapper; + + public RuleChainAssembler(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public void validate(RuleChainStructureDTO structure) { + if (structure == null || CollectionUtils.isEmpty(structure.getNodes())) { + throw new ValidationException("链路结构不能为空"); + } + } + + public String buildLiteflowDsl(RuleChainStructureDTO structure, String chainName) { + validate(structure); + + String expression = structure.getEntryExpression(); + if (!StringUtils.hasText(expression)) { + expression = buildSequentialExpression(structure.getNodes()); + } + if (!StringUtils.hasText(expression)) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_STRUCTURE_INVALID); + } + + ObjectNode root = objectMapper.createObjectNode(); + ObjectNode flow = objectMapper.createObjectNode(); + ArrayNode chainList = flow.putArray("chainList"); + ObjectNode chain = objectMapper.createObjectNode(); + chain.put("name", chainName); + chain.put("value", expression); + chainList.add(chain); + + ArrayNode nodeList = flow.putArray("nodeList"); + for (RuleChainNodeDTO node : structure.getNodes()) { + ObjectNode nodeJson = objectMapper.createObjectNode(); + nodeJson.put("id", StringUtils.hasText(node.getCode()) ? node.getCode() : node.getId()); + nodeJson.put("name", node.getName()); + nodeJson.put("type", resolveNodeType(node)); + if (node.getConfig() != null) { + nodeJson.set("config", objectMapper.valueToTree(node.getConfig())); + } + if (StringUtils.hasText(node.getRuleCode())) { + nodeJson.put("ref", node.getRuleCode()); + } + nodeList.add(nodeJson); + } + root.set("flow", flow); + + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(root); + } catch (JsonProcessingException e) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_STRUCTURE_INVALID, e.getMessage()); + } + } + + private String buildSequentialExpression(List nodes) { + List orderedCodes = nodes.stream() + .sorted(Comparator.comparing(node -> node.getOrderIndex() == null ? Integer.MAX_VALUE : node.getOrderIndex())) + .map(node -> StringUtils.hasText(node.getRuleCode()) ? node.getRuleCode() : node.getCode()) + .filter(StringUtils::hasText) + .toList(); + if (orderedCodes.isEmpty()) { + return null; + } + if (orderedCodes.size() == 1) { + return orderedCodes.get(0); + } + return String.format(Locale.ROOT, "THEN(%s)", String.join(",", orderedCodes)); + } + + private String resolveNodeType(RuleChainNodeDTO node) { + if (node.getType() == null) { + return "COMMON"; + } + return switch (node.getType()) { + case 1 -> "THEN"; + case 2 -> "IF"; + case 3 -> "SWITCH"; + case 4 -> "FOR"; + case 5 -> "WHEN"; + case 6 -> "CHAIN"; + case 7 -> "NODE"; + default -> "COMMON"; + }; + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleLiteflowManager.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleLiteflowManager.java new file mode 100644 index 00000000..f65d3afb --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/RuleLiteflowManager.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.rule.framework.liteflow; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.enums.FlowParserTypeEnum; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.Map; + +/** + * Thin wrapper over LiteFlow {@link FlowExecutor} to register chains dynamically + * and execute sandbox simulations. + */ +@Component +public class RuleLiteflowManager { + + private final FlowExecutor flowExecutor; + + public RuleLiteflowManager(FlowExecutor flowExecutor) { + this.flowExecutor = flowExecutor; + } + + public void registerOrUpdate(String resourceId, String liteflowDsl) { + if (!StringUtils.hasText(liteflowDsl)) { + return; + } + try { + if (StringUtils.hasText(resourceId) && FlowBus.containChain(resourceId)) { + FlowBus.removeChain(resourceId); + } + FlowBus.refreshFlowMetaData(FlowParserTypeEnum.TYPE_EL_JSON, liteflowDsl); + flowExecutor.reloadRule(); + } catch (Exception ex) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_PUBLISH_PUSH_FAILED, ex.getMessage()); + } + } + + public LiteflowResponse execute(String chainId, Map requestData) { + return flowExecutor.execute2Resp(chainId, requestData); + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/AbstractJsonConfigNodeComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/AbstractJsonConfigNodeComponent.java new file mode 100644 index 00000000..eb5c724c --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/AbstractJsonConfigNodeComponent.java @@ -0,0 +1,151 @@ +package com.zt.plat.module.rule.framework.liteflow.component; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.flow.element.Node; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +/** + * Base LiteFlow component that exposes helper methods to read JSON configuration + * and interact with the shared request/context data maps. + */ +public abstract class AbstractJsonConfigNodeComponent extends NodeComponent { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + protected Map getComponentConfig() { + Node node = getRefNode(); + if (node == null) { + return Collections.emptyMap(); + } + String cmpData = node.getCmpData(); + if (!StringUtils.hasText(cmpData)) { + return Collections.emptyMap(); + } + try { + return OBJECT_MAPPER.readValue(cmpData, new TypeReference>() { + }); + } catch (Exception ex) { + logger.warn("Failed to parse component config for node {}: {}", node.getId(), ex.getMessage()); + return Collections.emptyMap(); + } + } + + @SuppressWarnings("unchecked") + protected Map getMutableRequestMap() { + Object data = getRequestData(); + if (data instanceof Map) { + return (Map) data; + } + Map mutable = new HashMap<>(); + if (data != null) { + mutable.put("requestData", data); + } + return mutable; + } + + protected void storeResult(String key, Object value) { + if (!StringUtils.hasText(key)) { + return; + } + Map requestMap = getMutableRequestMap(); + requestMap.put(key, value); + try { + getSlot().setOutput(key, value); + } catch (Exception ex) { + logger.debug("Unable to record output value for key {}: {}", key, ex.getMessage()); + } + try { + getSlot().setChainReqData(key, value); + } catch (Exception ex) { + logger.debug("Unable to record chain data for key {}: {}", key, ex.getMessage()); + } + } + + protected String readString(Map config, Map requestMap, String key) { + Object value = config.getOrDefault(key, requestMap.get(key)); + return value != null ? String.valueOf(value) : null; + } + + protected BigDecimal readDecimal(Map config, Map requestMap, String key) { + Object value = config.getOrDefault(key, requestMap.get(key)); + if (value == null) { + return null; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + if (value instanceof Number) { + return new BigDecimal(value.toString()); + } + String str = value.toString(); + if (!StringUtils.hasText(str)) { + return null; + } + try { + return new BigDecimal(str.trim()); + } catch (NumberFormatException ex) { + logger.warn("Invalid decimal value '{}' for key '{}' in node '{}'", str, key, + Optional.ofNullable(getRefNode()).map(Node::getId).orElse("unknown")); + return null; + } + } + + protected Integer readInteger(Map config, Map requestMap, String key) { + Object value = config.getOrDefault(key, requestMap.get(key)); + if (value == null) { + return null; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + String str = value.toString(); + if (!StringUtils.hasText(str)) { + return null; + } + try { + return Integer.parseInt(str.trim()); + } catch (NumberFormatException ex) { + logger.warn("Invalid integer value '{}' for key '{}' in node '{}'", str, key, + Optional.ofNullable(getRefNode()).map(Node::getId).orElse("unknown")); + return null; + } + } + + protected Boolean readBoolean(Map config, Map requestMap, String key) { + Object value = config.getOrDefault(key, requestMap.get(key)); + if (value == null) { + return null; + } + if (value instanceof Boolean) { + return (Boolean) value; + } + String str = value.toString(); + if (!StringUtils.hasText(str)) { + return null; + } + return Boolean.parseBoolean(str.toLowerCase(Locale.ROOT)); + } + + protected String resolveResultKey(Map config, String defaultKey) { + Object resultKey = config.get("resultKey"); + String key = resultKey != null ? resultKey.toString() : defaultKey; + return StringUtils.hasText(key) ? key : defaultKey; + } + + protected Logger getLogger() { + return logger; + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/DataSetComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/DataSetComponent.java index 6290be8d..4e9c057a 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/DataSetComponent.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/DataSetComponent.java @@ -1,75 +1,130 @@ package com.zt.plat.module.rule.framework.liteflow.component.action; -import com.zt.plat.module.rule.framework.liteflow.component.base.BaseRuleComponent; -import com.yomahub.liteflow.annotation.LiteflowComponent; -import lombok.extern.slf4j.Slf4j; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.module.rule.framework.liteflow.component.AbstractJsonConfigNodeComponent; +import org.springframework.util.StringUtils; -import java.util.HashMap; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Locale; import java.util.Map; /** - * 数据设置组件 - * 用于设置上下文数据 - * - * @author 芋道源码 + * Writes a value into the shared request/context map so that subsequent nodes can consume it. */ -@LiteflowComponent("dataSetNode") -@Slf4j -public class DataSetComponent extends BaseRuleComponent { +public class DataSetComponent extends AbstractJsonConfigNodeComponent { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override - public void process() throws Exception { - // 获取参数 - String dataKey = getNodeProperty("dataKey", String.class); - Object dataValue = getNodeProperty("dataValue", Object.class); - String valueType = getNodeProperty("valueType", String.class); + public void process() { + Map config = getComponentConfig(); + Map request = getMutableRequestMap(); - if (dataKey == null) { - throw new IllegalArgumentException("数据设置组件参数不完整: dataKey 不能为空"); + String dataKey = readString(config, request, "dataKey"); + if (!StringUtils.hasText(dataKey)) { + getLogger().warn("DataSetComponent skipped because dataKey is missing"); + return; } - Object finalValue = dataValue; - - // 根据valueType进行类型转换 - if (valueType != null && dataValue != null) { - String strValue = String.valueOf(dataValue); - switch (valueType.toLowerCase()) { - case "string": - finalValue = strValue; - break; - case "integer": - case "int": - finalValue = Integer.valueOf(strValue); - break; - case "long": - finalValue = Long.valueOf(strValue); - break; - case "double": - finalValue = Double.valueOf(strValue); - break; - case "boolean": - finalValue = Boolean.valueOf(strValue); - break; - case "object": - // 保持原有类型 - break; - default: - log.warn("不支持的数据类型: {}, 将保持原有类型", valueType); - } - } - - // 设置数据到上下文 - setContextData(dataKey, finalValue); - - // 同时设置到结果数据中 - Map resultData = getContextData("resultData", Map.class); - if (resultData == null) { - resultData = new HashMap<>(); - setContextData("resultData", resultData); - } - resultData.put(dataKey, finalValue); - - log.info("设置数据: key={}, value={}, valueType={}", dataKey, finalValue, valueType); + Object value = resolveValue(config, request); + storeResult(dataKey, value); } -} \ No newline at end of file + private Object resolveValue(Map config, Map request) { + String valueKey = readString(config, request, "valueKey"); + Object value; + if (StringUtils.hasText(valueKey) && request.containsKey(valueKey)) { + value = request.get(valueKey); + } else if (config.containsKey("dataValue")) { + value = config.get("dataValue"); + } else { + value = null; + } + + String valueType = readString(config, request, "valueType"); + if (!StringUtils.hasText(valueType) || value == null) { + return value; + } + return switch (valueType.trim().toLowerCase(Locale.ROOT)) { + case "string" -> String.valueOf(value); + case "int", "integer" -> convertToInteger(value); + case "long" -> convertToLong(value); + case "decimal", "bigdecimal" -> convertToDecimal(value); + case "double" -> convertToDouble(value); + case "boolean" -> convertToBoolean(value); + case "json" -> parseJson(value); + case "map" -> value instanceof Map ? value : Collections.emptyMap(); + default -> value; + }; + } + + private Integer convertToInteger(Object value) { + if (value instanceof Number number) { + return number.intValue(); + } + try { + return Integer.parseInt(String.valueOf(value)); + } catch (NumberFormatException ex) { + getLogger().warn("Failed to convert value '{}' to integer", value); + return null; + } + } + + private Long convertToLong(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException ex) { + getLogger().warn("Failed to convert value '{}' to long", value); + return null; + } + } + + private BigDecimal convertToDecimal(Object value) { + if (value instanceof BigDecimal decimal) { + return decimal; + } + if (value instanceof Number number) { + return new BigDecimal(number.toString()); + } + try { + return new BigDecimal(String.valueOf(value)); + } catch (NumberFormatException ex) { + getLogger().warn("Failed to convert value '{}' to BigDecimal", value); + return null; + } + } + + private Double convertToDouble(Object value) { + if (value instanceof Number number) { + return number.doubleValue(); + } + try { + return Double.parseDouble(String.valueOf(value)); + } catch (NumberFormatException ex) { + getLogger().warn("Failed to convert value '{}' to double", value); + return null; + } + } + + private Boolean convertToBoolean(Object value) { + if (value instanceof Boolean bool) { + return bool; + } + return Boolean.parseBoolean(String.valueOf(value)); + } + + private Object parseJson(Object value) { + try { + return OBJECT_MAPPER.readValue(String.valueOf(value), new TypeReference>() { + }); + } catch (Exception ex) { + getLogger().warn("Failed to parse JSON value '{}': {}", value, ex.getMessage()); + return Collections.emptyMap(); + } + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/MathCalculateComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/MathCalculateComponent.java index 368b215a..e6f7f61b 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/MathCalculateComponent.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/action/MathCalculateComponent.java @@ -1,96 +1,83 @@ package com.zt.plat.module.rule.framework.liteflow.component.action; -import com.zt.plat.module.rule.framework.liteflow.component.base.BaseRuleComponent; -import com.yomahub.liteflow.annotation.LiteflowComponent; -import lombok.extern.slf4j.Slf4j; +import com.zt.plat.module.rule.framework.liteflow.component.AbstractJsonConfigNodeComponent; +import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; /** - * 数值计算组件 - * 支持加减乘除等数学运算 - * - * @author 芋道源码 + * Executes basic math operations and stores the result for subsequent nodes. */ -@LiteflowComponent("mathCalculateNode") -@Slf4j -public class MathCalculateComponent extends BaseRuleComponent { +public class MathCalculateComponent extends AbstractJsonConfigNodeComponent { @Override - public void process() throws Exception { - // 获取参数 - String leftValue = getNodeProperty("leftValue", String.class); - String rightValue = getNodeProperty("rightValue", String.class); - String operator = getNodeProperty("operator", String.class); - String resultKey = getNodeProperty("resultKey", String.class); - Integer scale = getNodeProperty("scale", Integer.class); + public void process() { + Map config = getComponentConfig(); + Map request = getMutableRequestMap(); - if (leftValue == null || rightValue == null || operator == null) { - throw new IllegalArgumentException("数值计算组件参数不完整: leftValue, rightValue, operator 都不能为空"); + BigDecimal left = Optional.ofNullable(readDecimal(config, request, "leftValue")).orElse(BigDecimal.ZERO); + BigDecimal right = Optional.ofNullable(readDecimal(config, request, "rightValue")).orElse(BigDecimal.ZERO); + String operator = readString(config, request, "operator"); + if (!StringUtils.hasText(operator)) { + getLogger().warn("MathCalculateComponent skipped due to missing operator"); + return; } - if (resultKey == null) { - resultKey = "calculateResult"; - } + int scale = Optional.ofNullable(readInteger(config, request, "scale")).orElse(2); + RoundingMode roundingMode = resolveRoundingMode(readString(config, request, "roundingMode")); - if (scale == null) { - scale = 2; // 默认保留2位小数 - } - - try { - // 转换为数值 - BigDecimal left = new BigDecimal(leftValue); - BigDecimal right = new BigDecimal(rightValue); - BigDecimal result; - - // 根据操作符进行计算 - switch (operator) { - case "+": - case "add": - result = left.add(right); - break; - case "-": - case "subtract": - result = left.subtract(right); - break; - case "*": - case "multiply": - result = left.multiply(right); - break; - case "/": - case "divide": - if (right.compareTo(BigDecimal.ZERO) == 0) { - throw new IllegalArgumentException("除数不能为0"); - } - result = left.divide(right, scale, RoundingMode.HALF_UP); - break; - case "%": - case "mod": - result = left.remainder(right); - break; - case "max": - result = left.max(right); - break; - case "min": - result = left.min(right); - break; - case "pow": - result = left.pow(right.intValue()); - break; - default: - throw new IllegalArgumentException("不支持的数学操作符: " + operator); + BigDecimal result = switch (operator.trim()) { + case "+" -> left.add(right); + case "-" -> left.subtract(right); + case "*" -> left.multiply(right); + case "/" -> right.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : left.divide(right, scale, roundingMode); + case "%" -> right.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : left.remainder(right); + default -> { + getLogger().warn("Unsupported math operator: {}", operator); + yield null; } + }; - // 设置计算结果 - setContextData(resultKey, result); - setContextData("lastCalculateResult", result); - - log.info("数值计算: {} {} {} = {}", leftValue, operator, rightValue, result); + if (result == null) { + return; + } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("数值格式错误: leftValue=" + leftValue + ", rightValue=" + rightValue, e); + String resultType = readString(config, request, "resultType"); + Object storedValue = convertResult(result, resultType, scale, roundingMode); + storeResult(resolveResultKey(config, "mathCalculateResult"), storedValue); + } + + private RoundingMode resolveRoundingMode(String mode) { + if (!StringUtils.hasText(mode)) { + return RoundingMode.HALF_UP; + } + try { + return RoundingMode.valueOf(mode.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + getLogger().warn("Unsupported rounding mode '{}', fallback to HALF_UP", mode); + return RoundingMode.HALF_UP; } } -} \ No newline at end of file + private Object convertResult(BigDecimal value, String resultType, int scale, RoundingMode roundingMode) { + if (!StringUtils.hasText(resultType)) { + return value; + } + String normalized = resultType.trim().toLowerCase(Locale.ROOT); + return switch (normalized) { + case "int", "integer" -> value.setScale(0, roundingMode).intValue(); + case "long" -> value.setScale(0, roundingMode).longValue(); + case "double" -> value.doubleValue(); + case "float" -> value.floatValue(); + case "string" -> value.setScale(scale, roundingMode).toPlainString(); + default -> { + getLogger().warn("Unknown result type '{}', keep BigDecimal", resultType); + yield value; + } + }; + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/base/BaseRuleComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/base/BaseRuleComponent.java deleted file mode 100644 index 97eeb4ba..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/base/BaseRuleComponent.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.zt.plat.module.rule.framework.liteflow.component.base; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.yomahub.liteflow.core.NodeComponent; -import com.yomahub.liteflow.slot.DefaultContext; - -import java.util.Map; - -/** - * 基础规则组件 - * 提供通用的属性获取和上下文操作方法 - * - * @author 芋道源码 - */ -public abstract class BaseRuleComponent extends NodeComponent { - - /** - * 获取节点配置属性 - * - * @param key 属性键 - * @param clazz 属性类型 - * @return 属性值 - */ - @SuppressWarnings("unchecked") - protected T getNodeProperty(String key, Class clazz) { - DefaultContext context = this.getContextBean(DefaultContext.class); - if (context == null) { - return null; - } - - // 首先尝试从节点配置中获取 - String nodeConfigKey = "nodeConfig_" + this.getNodeId(); - Object nodeConfig = context.getData(nodeConfigKey); - - if (nodeConfig instanceof Map) { - Map configMap = (Map) nodeConfig; - Object value = configMap.get(key); - if (value != null) { - return convertValue(value, clazz); - } - } - - // 其次尝试从上下文数据中获取 - Object value = context.getData(key); - if (value != null) { - return convertValue(value, clazz); - } - - return null; - } - - /** - * 设置上下文数据 - * - * @param key 键 - * @param value 值 - */ - protected void setContextData(String key, Object value) { - DefaultContext context = this.getContextBean(DefaultContext.class); - if (context != null) { - context.setData(key, value); - } - } - - /** - * 获取上下文数据 - * - * @param key 键 - * @param clazz 类型 - * @return 值 - */ - @SuppressWarnings("unchecked") - protected T getContextData(String key, Class clazz) { - DefaultContext context = this.getContextBean(DefaultContext.class); - if (context == null) { - return null; - } - - Object value = context.getData(key); - if (value != null) { - return convertValue(value, clazz); - } - - return null; - } - - /** - * 类型转换 - * - * @param value 原始值 - * @param clazz 目标类型 - * @return 转换后的值 - */ - @SuppressWarnings("unchecked") - private T convertValue(Object value, Class clazz) { - if (value == null) { - return null; - } - - if (clazz.isInstance(value)) { - return (T) value; - } - - if (clazz == String.class) { - return (T) String.valueOf(value); - } - - if (clazz == Integer.class || clazz == int.class) { - if (value instanceof Number) { - return (T) Integer.valueOf(((Number) value).intValue()); - } - return (T) Integer.valueOf(String.valueOf(value)); - } - - if (clazz == Long.class || clazz == long.class) { - if (value instanceof Number) { - return (T) Long.valueOf(((Number) value).longValue()); - } - return (T) Long.valueOf(String.valueOf(value)); - } - - if (clazz == Boolean.class || clazz == boolean.class) { - if (value instanceof Boolean) { - return (T) value; - } - return (T) Boolean.valueOf(String.valueOf(value)); - } - - if (clazz == Double.class || clazz == double.class) { - if (value instanceof Number) { - return (T) Double.valueOf(((Number) value).doubleValue()); - } - return (T) Double.valueOf(String.valueOf(value)); - } - - // 尝试JSON转换 - if (value instanceof String && !clazz.isPrimitive()) { - String strValue = (String) value; - if (JSONUtil.isTypeJSON(strValue)) { - return JSONUtil.toBean(strValue, clazz); - } - } - - throw new IllegalArgumentException("无法将类型 " + value.getClass().getSimpleName() + - " 转换为 " + clazz.getSimpleName()); - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/NumberCompareComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/NumberCompareComponent.java index b2045cd7..df56cc41 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/NumberCompareComponent.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/NumberCompareComponent.java @@ -1,80 +1,48 @@ package com.zt.plat.module.rule.framework.liteflow.component.common; -import com.zt.plat.module.rule.framework.liteflow.component.base.BaseRuleComponent; -import com.yomahub.liteflow.annotation.LiteflowComponent; -import com.yomahub.liteflow.core.NodeComponent; -import lombok.extern.slf4j.Slf4j; +import com.zt.plat.module.rule.framework.liteflow.component.AbstractJsonConfigNodeComponent; +import org.springframework.util.StringUtils; import java.math.BigDecimal; +import java.util.Map; /** - * 条件判断组件 - 数值比较 - * 支持大于、小于、等于、大于等于、小于等于比较 - * - * @author 芋道源码 + * Performs a numeric comparison based on configured operands and operator. */ -@LiteflowComponent("numberCompareNode") -@Slf4j -public class NumberCompareComponent extends BaseRuleComponent { +public class NumberCompareComponent extends AbstractJsonConfigNodeComponent { @Override - public void process() throws Exception { - // 获取参数 - String leftValue = getNodeProperty("leftValue", String.class); - String operator = getNodeProperty("operator", String.class); - String rightValue = getNodeProperty("rightValue", String.class); + public void process() { + Map config = getComponentConfig(); + Map request = getMutableRequestMap(); - if (leftValue == null || operator == null || rightValue == null) { - throw new IllegalArgumentException("数值比较组件参数不完整: leftValue, operator, rightValue 都不能为空"); + BigDecimal left = readDecimal(config, request, "leftValue"); + BigDecimal right = readDecimal(config, request, "rightValue"); + String operator = readString(config, request, "operator"); + + if (left == null || right == null || !StringUtils.hasText(operator)) { + getLogger().warn("NumberCompareComponent skipped due to missing operands/operator"); + storeResult(resolveResultKey(config, "numberCompareResult"), Boolean.FALSE); + return; } - try { - // 转换为数值 - BigDecimal left = new BigDecimal(leftValue); - BigDecimal right = new BigDecimal(rightValue); - - boolean result = false; - - // 根据操作符进行比较 - switch (operator) { - case ">": - case "gt": - result = left.compareTo(right) > 0; - break; - case "<": - case "lt": - result = left.compareTo(right) < 0; - break; - case "=": - case "==": - case "eq": - result = left.compareTo(right) == 0; - break; - case ">=": - case "gte": - result = left.compareTo(right) >= 0; - break; - case "<=": - case "lte": - result = left.compareTo(right) <= 0; - break; - case "!=": - case "ne": - result = left.compareTo(right) != 0; - break; - default: - throw new IllegalArgumentException("不支持的比较操作符: " + operator); + boolean result = switch (operator.trim()) { + case ">" -> left.compareTo(right) > 0; + case ">=" -> left.compareTo(right) >= 0; + case "<" -> left.compareTo(right) < 0; + case "<=" -> left.compareTo(right) <= 0; + case "==", "=" -> left.compareTo(right) == 0; + case "!=" -> left.compareTo(right) != 0; + default -> { + getLogger().warn("Unsupported compare operator: {}", operator); + yield false; } + }; - // 设置比较结果 - this.setIsEnd(!result); - getSlot().setData("compareResult", result); - - log.info("数值比较: {} {} {} = {}", leftValue, operator, rightValue, result); - - } catch (NumberFormatException e) { - throw new IllegalArgumentException("数值格式错误: leftValue=" + leftValue + ", rightValue=" + rightValue, e); + storeResult(resolveResultKey(config, "numberCompareResult"), result); + Boolean stopOnFailure = readBoolean(config, request, "stopOnFailure"); + if (!result && Boolean.TRUE.equals(stopOnFailure)) { + setIsEnd(true); } } - -} \ No newline at end of file +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/StringConditionComponent.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/StringConditionComponent.java index 1d9a9033..f364c767 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/StringConditionComponent.java +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/component/common/StringConditionComponent.java @@ -1,98 +1,66 @@ package com.zt.plat.module.rule.framework.liteflow.component.common; -import cn.hutool.core.util.StrUtil; -import com.zt.plat.module.rule.framework.liteflow.component.base.BaseRuleComponent; -import com.yomahub.liteflow.annotation.LiteflowComponent; -import lombok.extern.slf4j.Slf4j; +import com.zt.plat.module.rule.framework.liteflow.component.AbstractJsonConfigNodeComponent; +import org.springframework.util.StringUtils; + +import java.util.Locale; +import java.util.Map; /** - * 字符串判断组件 - * 支持字符串相等、包含、长度等判断 - * - * @author 芋道源码 + * Evaluates string based conditions such as equality or containment checks. */ -@LiteflowComponent("stringConditionNode") -@Slf4j -public class StringConditionComponent extends BaseRuleComponent { +public class StringConditionComponent extends AbstractJsonConfigNodeComponent { @Override - public void process() throws Exception { - // 获取参数 - String sourceValue = getNodeProperty("sourceValue", String.class); - String targetValue = getNodeProperty("targetValue", String.class); - String operator = getNodeProperty("operator", String.class); + public void process() { + Map config = getComponentConfig(); + Map request = getMutableRequestMap(); - if (sourceValue == null || operator == null) { - throw new IllegalArgumentException("字符串判断组件参数不完整: sourceValue, operator 不能为空"); + String left = readString(config, request, "leftValue"); + String right = readString(config, request, "rightValue"); + String operator = readString(config, request, "operator"); + boolean ignoreCase = Boolean.TRUE.equals(readBoolean(config, request, "ignoreCase")); + + if (!StringUtils.hasText(operator)) { + getLogger().warn("StringConditionComponent skipped due to missing operator"); + storeResult(resolveResultKey(config, "stringConditionResult"), Boolean.FALSE); + return; } - boolean result = false; - - // 根据操作符进行判断 - switch (operator) { - case "equals": - case "eq": - result = StrUtil.equals(sourceValue, targetValue); - break; - case "equalsIgnoreCase": - case "eqIgnoreCase": - result = StrUtil.equalsIgnoreCase(sourceValue, targetValue); - break; - case "contains": - result = StrUtil.contains(sourceValue, targetValue); - break; - case "startsWith": - result = StrUtil.startWith(sourceValue, targetValue); - break; - case "endsWith": - result = StrUtil.endWith(sourceValue, targetValue); - break; - case "isEmpty": - result = StrUtil.isEmpty(sourceValue); - break; - case "isNotEmpty": - result = StrUtil.isNotEmpty(sourceValue); - break; - case "isBlank": - result = StrUtil.isBlank(sourceValue); - break; - case "isNotBlank": - result = StrUtil.isNotBlank(sourceValue); - break; - case "lengthEquals": - Integer expectedLength = getNodeProperty("expectedLength", Integer.class); - if (expectedLength != null) { - result = sourceValue.length() == expectedLength; - } - break; - case "lengthGreaterThan": - Integer minLength = getNodeProperty("minLength", Integer.class); - if (minLength != null) { - result = sourceValue.length() > minLength; - } - break; - case "lengthLessThan": - Integer maxLength = getNodeProperty("maxLength", Integer.class); - if (maxLength != null) { - result = sourceValue.length() < maxLength; - } - break; - case "matches": - String pattern = getNodeProperty("pattern", String.class); - if (pattern != null) { - result = sourceValue.matches(pattern); - } - break; - default: - throw new IllegalArgumentException("不支持的字符串操作符: " + operator); + String normalizedOp = operator.trim().toLowerCase(Locale.ROOT); + String leftValue = left != null ? left : ""; + String rightValue = right != null ? right : ""; + if (ignoreCase) { + leftValue = leftValue.toLowerCase(Locale.ROOT); + rightValue = rightValue.toLowerCase(Locale.ROOT); } - // 设置判断结果 - this.setIsEnd(!result); - setContextData("stringConditionResult", result); - - log.info("字符串判断: sourceValue={}, operator={}, targetValue={}, result={}", - sourceValue, operator, targetValue, result); + boolean result = switch (normalizedOp) { + case "equals", "==", "=" -> leftValue.equals(rightValue); + case "notequals", "!=", "<>" -> !leftValue.equals(rightValue); + case "contains" -> leftValue.contains(rightValue); + case "startswith" -> leftValue.startsWith(rightValue); + case "endswith" -> leftValue.endsWith(rightValue); + case "blank" -> !StringUtils.hasText(left); + case "notblank" -> StringUtils.hasText(left); + case "matches" -> { + try { + yield (left != null ? left : "").matches(right != null ? right : ""); + } catch (Exception ex) { + getLogger().warn("Invalid regex '{}' in StringConditionComponent: {}", right, ex.getMessage()); + yield false; + } + } + default -> { + getLogger().warn("Unsupported string operator: {}", operator); + yield false; + } + }; + + storeResult(resolveResultKey(config, "stringConditionResult"), result); + Boolean stopOnFailure = readBoolean(config, request, "stopOnFailure"); + if (!result && Boolean.TRUE.equals(stopOnFailure)) { + setIsEnd(true); + } } - -} \ No newline at end of file +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/config/LiteFlowConfiguration.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/config/LiteFlowConfiguration.java deleted file mode 100644 index 4bf273e3..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/config/LiteFlowConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.zt.plat.module.rule.framework.liteflow.config; - -import com.yomahub.liteflow.spring.ComponentScanner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * LiteFlow 配置类 - * - * @author 芋道源码 - */ -@Configuration -public class LiteFlowConfiguration { - - /** - * 组件扫描器 - * 自动扫描LiteFlow组件 - */ - @Bean - public ComponentScanner componentScanner() { - return new ComponentScanner("com.zt.plat.module.rule.framework.liteflow.component"); - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/service/LiteFlowService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/service/LiteFlowService.java deleted file mode 100644 index cc2850b0..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/liteflow/service/LiteFlowService.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.zt.plat.module.rule.framework.liteflow.service; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.zt.plat.module.rule.controller.admin.rule.vo.RuleExecuteReqVO; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import com.yomahub.liteflow.core.FlowExecutor; -import com.yomahub.liteflow.flow.LiteflowResponse; -import com.yomahub.liteflow.flow.element.chain.LiteFlowChain; -import com.yomahub.liteflow.flow.element.chain.LiteFlowChainBuilder; -import com.yomahub.liteflow.slot.DefaultContext; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * LiteFlow 服务实现类 - * - * @author 芋道源码 - */ -@Service -@Slf4j -public class LiteFlowService { - - @Resource - private FlowExecutor flowExecutor; - - /** - * 执行规则 - * - * @param chainId 规则链ID - * @param contextData 上下文数据 - * @param extParams 扩展参数 - * @return 执行结果 - */ - public Map executeRule(String chainId, Map contextData, Map extParams) { - try { - // 创建上下文 - DefaultContext context = new DefaultContext(); - - // 设置上下文数据 - if (MapUtil.isNotEmpty(contextData)) { - contextData.forEach(context::setData); - } - - // 设置扩展参数 - if (MapUtil.isNotEmpty(extParams)) { - extParams.forEach(context::setData); - } - - // 执行规则链 - LiteflowResponse response = flowExecutor.execute2Resp(chainId, null, context); - - // 构建返回结果 - Map resultData = new HashMap<>(); - resultData.put("success", response.isSuccess()); - resultData.put("message", response.getMessage()); - resultData.put("executeStepStr", response.getExecuteStepStr()); - - // 获取执行结果数据 - if (response.getSlot() != null && response.getSlot().getContextBean() instanceof DefaultContext) { - DefaultContext resultContext = (DefaultContext) response.getSlot().getContextBean(); - if (resultContext != null) { - resultData.put("contextData", resultContext.getData()); - } - } - - return resultData; - - } catch (Exception e) { - log.error("执行规则链失败: chainId={}", chainId, e); - throw new RuntimeException("规则执行失败: " + e.getMessage(), e); - } - } - - /** - * 加载规则到LiteFlow - * - * @param rule 规则DO - */ - public void loadRule(RuleDO rule) { - try { - if (StrUtil.isBlank(rule.getConfig())) { - throw new IllegalArgumentException("规则配置不能为空"); - } - - // 解析JSON配置 - JSONObject configJson = JSONUtil.parseObj(rule.getConfig()); - RuleExecuteReqVO.RuleConfig ruleConfig = JSONUtil.toBean(configJson, RuleExecuteReqVO.RuleConfig.class); - - if (ruleConfig.getChain() == null) { - throw new IllegalArgumentException("规则链配置不能为空"); - } - - // 构建LiteFlow规则链 - String chainId = StrUtil.isNotBlank(rule.getChainId()) ? rule.getChainId() : ruleConfig.getChain().getChainId(); - String expression = ruleConfig.getChain().getExpression(); - - if (StrUtil.isBlank(expression)) { - throw new IllegalArgumentException("规则链表达式不能为空"); - } - - // 创建并加载规则链 - LiteFlowChain chain = LiteFlowChainBuilder.createChain() - .setChainId(chainId) - .setChainName(ruleConfig.getChain().getChainName()) - .setEL(expression) - .build(); - - // 注册规则链 - flowExecutor.getChainContainer().setChain(chainId, chain); - - log.info("成功加载规则链: chainId={}, expression={}", chainId, expression); - - } catch (Exception e) { - log.error("加载规则到LiteFlow失败: ruleId={}", rule.getId(), e); - throw new RuntimeException("加载规则失败: " + e.getMessage(), e); - } - } - - /** - * 重新加载规则 - * - * @param rule 规则DO - */ - public void reloadRule(RuleDO rule) { - // 先移除再加载 - if (StrUtil.isNotBlank(rule.getChainId())) { - removeRule(rule.getChainId()); - } - loadRule(rule); - } - - /** - * 移除规则 - * - * @param chainId 规则链ID - */ - public void removeRule(String chainId) { - try { - if (StrUtil.isNotBlank(chainId)) { - flowExecutor.getChainContainer().removeChain(chainId); - log.info("成功移除规则链: chainId={}", chainId); - } - } catch (Exception e) { - log.error("移除规则链失败: chainId={}", chainId, e); - throw new RuntimeException("移除规则失败: " + e.getMessage(), e); - } - } - - /** - * 验证规则配置 - * - * @param config 规则配置JSON - * @return 验证结果 - */ - public Boolean validateRuleConfig(String config) { - try { - if (StrUtil.isBlank(config)) { - return false; - } - - // 解析JSON - JSONObject configJson = JSONUtil.parseObj(config); - RuleExecuteReqVO.RuleConfig ruleConfig = JSONUtil.toBean(configJson, RuleExecuteReqVO.RuleConfig.class); - - // 验证必要字段 - if (ruleConfig.getChain() == null) { - log.warn("规则链配置为空"); - return false; - } - - if (StrUtil.isBlank(ruleConfig.getChain().getChainId())) { - log.warn("规则链ID为空"); - return false; - } - - if (StrUtil.isBlank(ruleConfig.getChain().getExpression())) { - log.warn("规则链表达式为空"); - return false; - } - - // 验证节点配置 - List nodes = ruleConfig.getNodes(); - if (nodes != null) { - for (RuleExecuteReqVO.NodeConfig node : nodes) { - if (StrUtil.isBlank(node.getNodeId())) { - log.warn("节点ID为空"); - return false; - } - if (StrUtil.isBlank(node.getClazz())) { - log.warn("节点类名为空: nodeId={}", node.getNodeId()); - return false; - } - } - } - - return true; - - } catch (Exception e) { - log.warn("验证规则配置时发生异常", e); - return false; - } - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/nacos/RuleNacosPublisher.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/nacos/RuleNacosPublisher.java new file mode 100644 index 00000000..9a65276f --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/framework/nacos/RuleNacosPublisher.java @@ -0,0 +1,57 @@ +package com.zt.plat.module.rule.framework.nacos; + +import com.alibaba.cloud.nacos.NacosConfigManager; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.exception.NacosException; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import com.zt.plat.module.rule.framework.config.RulePublishProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.nio.charset.StandardCharsets; + +/** + * Publishes LiteFlow DSL to Nacos for runtime consumption. + */ +@Component +public class RuleNacosPublisher { + + private final NacosConfigManager nacosConfigManager; + private final RulePublishProperties properties; + + public RuleNacosPublisher(NacosConfigManager nacosConfigManager, + RulePublishProperties properties) { + this.nacosConfigManager = nacosConfigManager; + this.properties = properties; + } + + public void publish(String business, String version, String liteflowDsl) { + if (!StringUtils.hasText(liteflowDsl)) { + return; + } + String dataId = buildDataId(business, version); + try { + ConfigService configService = nacosConfigManager.getConfigService(); + boolean success = configService.publishConfig(dataId, properties.getNacosGroup(), liteflowDsl, StandardCharsets.UTF_8.name()); + if (!success) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_PUBLISH_PUSH_FAILED, "Nacos publish returned false"); + } + } catch (NacosException ex) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_PUBLISH_PUSH_FAILED, ex.getErrMsg()); + } + } + + public void remove(String business, String version) { + String dataId = buildDataId(business, version); + try { + nacosConfigManager.getConfigService().removeConfig(dataId, properties.getNacosGroup()); + } catch (NacosException ex) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_PUBLISH_PUSH_FAILED, ex.getErrMsg()); + } + } + + public String buildDataId(String business, String version) { + return properties.getDataIdPrefix() + "/" + business + "/" + version; + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessService.java new file mode 100644 index 00000000..5c8f62e5 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessService.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.rule.service.business; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindPageReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessChainViewRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessRelationSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessTreeNodeRespVO; +import com.zt.plat.module.rule.service.dto.BusinessChainAssembleResultDTO; + +import java.util.List; + +public interface RuleBusinessService { + + void saveBinding(RuleBusinessBindSaveReqVO reqVO); + + PageResult getBindingPage(RuleBusinessBindPageReqVO reqVO); + + RuleBusinessBindRespVO getBinding(String business); + + void removeBinding(String business); + + void saveRelation(RuleBusinessRelationSaveReqVO reqVO); + + void removeRelation(String childBusiness); + + List getBusinessTree(); + + RuleBusinessChainViewRespVO previewBusinessChain(String business, String version); + + BusinessChainAssembleResultDTO assembleBusinessChain(String business, String version); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessServiceImpl.java new file mode 100644 index 00000000..f5e95d6d --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/business/RuleBusinessServiceImpl.java @@ -0,0 +1,331 @@ +package com.zt.plat.module.rule.service.business; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindPageReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessBindSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessChainViewRespVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessRelationSaveReqVO; +import com.zt.plat.module.rule.controller.admin.business.vo.RuleBusinessTreeNodeRespVO; +import com.zt.plat.module.rule.convert.business.RuleBusinessConvert; +import com.zt.plat.module.rule.convert.chain.RuleChainConvert; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessBindingDO; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessRelationDO; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; +import com.zt.plat.module.rule.dal.mysql.business.RuleBusinessBindingMapper; +import com.zt.plat.module.rule.dal.mysql.business.RuleBusinessRelationMapper; +import com.zt.plat.module.rule.dal.mysql.chain.RuleChainMapper; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import com.zt.plat.module.rule.enums.OverrideStrategyEnum; +import com.zt.plat.module.rule.framework.liteflow.RuleChainAssembler; +import com.zt.plat.module.rule.service.dto.BusinessChainAssembleResultDTO; +import com.zt.plat.module.rule.service.dto.RuleChainNodeDTO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class RuleBusinessServiceImpl implements RuleBusinessService { + + @Resource + private RuleBusinessBindingMapper bindingMapper; + @Resource + private RuleBusinessRelationMapper relationMapper; + @Resource + private RuleChainMapper ruleChainMapper; + @Resource + private RuleChainConvert ruleChainConvert; + @Resource + private RuleChainAssembler ruleChainAssembler; + @Resource + private ObjectMapper objectMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveBinding(RuleBusinessBindSaveReqVO reqVO) { + RuleBusinessBindingDO existed = bindingMapper.selectByBusiness(reqVO.getBusiness()); + RuleBusinessBindingDO entity = RuleBusinessConvert.INSTANCE.convert(reqVO); + if (Boolean.TRUE.equals(existed != null ? existed.getLocked() : Boolean.FALSE)) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_LOCKED); + } + if (existed == null) { + bindingMapper.insert(entity); + } else { + entity.setId(existed.getId()); + entity.setEffectiveVersion(existed.getEffectiveVersion()); + bindingMapper.updateById(entity); + } + } + + @Override + public PageResult getBindingPage(RuleBusinessBindPageReqVO reqVO) { + PageResult pageResult = bindingMapper.selectPage(reqVO); + List list = pageResult.getList(); + if (CollectionUtils.isEmpty(list)) { + return new PageResult<>(List.of(), pageResult.getTotal()); + } + Map chainMap = ruleChainMapper.selectBatchIds( + list.stream().map(RuleBusinessBindingDO::getRuleChainId).collect(Collectors.toSet())) + .stream().collect(Collectors.toMap(RuleChainDO::getId, c -> c)); + List records = list.stream().map(item -> { + RuleBusinessBindRespVO vo = RuleBusinessConvert.INSTANCE.convert(item); + RuleChainDO chain = chainMap.get(item.getRuleChainId()); + if (chain != null) { + vo.setRuleChainCode(chain.getCode()); + vo.setRuleChainName(chain.getName()); + } + return vo; + }).collect(Collectors.toList()); + return new PageResult<>(records, pageResult.getTotal()); + } + + @Override + public RuleBusinessBindRespVO getBinding(String business) { + RuleBusinessBindingDO entity = bindingMapper.selectByBusiness(business); + if (entity == null) { + return null; + } + RuleBusinessBindRespVO vo = RuleBusinessConvert.INSTANCE.convert(entity); + RuleChainDO chain = ruleChainMapper.selectById(entity.getRuleChainId()); + if (chain != null) { + vo.setRuleChainCode(chain.getCode()); + vo.setRuleChainName(chain.getName()); + } + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeBinding(String business) { + RuleBusinessBindingDO entity = bindingMapper.selectByBusiness(business); + if (entity != null) { + if (Boolean.TRUE.equals(entity.getLocked())) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_LOCKED); + } + bindingMapper.deleteById(entity.getId()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveRelation(RuleBusinessRelationSaveReqVO reqVO) { + validateNoCycle(reqVO.getParentBusiness(), reqVO.getChildBusiness()); + RuleBusinessRelationDO existed = relationMapper.selectByChild(reqVO.getChildBusiness()); + if (existed == null) { + RuleBusinessRelationDO entity = new RuleBusinessRelationDO(); + entity.setParentBusiness(reqVO.getParentBusiness()); + entity.setChildBusiness(reqVO.getChildBusiness()); + entity.setSort(reqVO.getSort()); + relationMapper.insert(entity); + } else { + existed.setParentBusiness(reqVO.getParentBusiness()); + existed.setSort(reqVO.getSort()); + relationMapper.updateById(existed); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void removeRelation(String childBusiness) { + RuleBusinessRelationDO existed = relationMapper.selectByChild(childBusiness); + if (existed != null) { + relationMapper.deleteById(existed.getId()); + } + } + + @Override + public List getBusinessTree() { + Map nodeMap = new HashMap<>(); + List bindings = bindingMapper.selectAll(); + for (RuleBusinessBindingDO binding : bindings) { + RuleBusinessTreeNodeRespVO node = nodeMap.computeIfAbsent(binding.getBusiness(), key -> new RuleBusinessTreeNodeRespVO()); + node.setBusiness(binding.getBusiness()); + RuleChainDO chain = ruleChainMapper.selectById(binding.getRuleChainId()); + if (chain != null) { + node.setChainCode(chain.getCode()); + node.setChainName(chain.getName()); + } + } + List relations = relationMapper.selectAll(); + Set childSet = new HashSet<>(); + for (RuleBusinessRelationDO relation : relations) { + RuleBusinessTreeNodeRespVO parent = nodeMap.computeIfAbsent(relation.getParentBusiness(), key -> { + RuleBusinessTreeNodeRespVO vo = new RuleBusinessTreeNodeRespVO(); + vo.setBusiness(key); + return vo; + }); + RuleBusinessTreeNodeRespVO child = nodeMap.computeIfAbsent(relation.getChildBusiness(), key -> { + RuleBusinessTreeNodeRespVO vo = new RuleBusinessTreeNodeRespVO(); + vo.setBusiness(key); + return vo; + }); + parent.getChildren().add(child); + childSet.add(relation.getChildBusiness()); + } + return nodeMap.values().stream() + .filter(node -> !childSet.contains(node.getBusiness())) + .collect(Collectors.toList()); + } + + @Override + public RuleBusinessChainViewRespVO previewBusinessChain(String business, String version) { + BusinessChainAssembleResultDTO assembled = assembleBusinessChain(business, version); + RuleBusinessChainViewRespVO respVO = new RuleBusinessChainViewRespVO(); + respVO.setBusiness(assembled.getBusiness()); + respVO.setVersion(assembled.getVersion()); + respVO.setChainId(assembled.getChainId()); + respVO.setLiteflowDsl(assembled.getLiteflowDsl()); + respVO.setStructure(ruleChainConvert.toVo(assembled.getStructure())); + return respVO; + } + + @Override + public BusinessChainAssembleResultDTO assembleBusinessChain(String business, String version) { + Deque lineage = new ArrayDeque<>(); + String current = business; + while (StringUtils.hasText(current)) { + if (!lineage.add(current)) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_RELATION_CYCLE); + } + RuleBusinessRelationDO relation = relationMapper.selectByChild(current); + current = relation != null ? relation.getParentBusiness() : null; + } + List businesses = new ArrayList<>(lineage); + Collections.reverse(businesses); + + RuleChainStructureDTO aggregatedStructure = null; + Long lastChainId = null; + String chainCode = null; + for (String biz : businesses) { + RuleBusinessBindingDO binding = bindingMapper.selectByBusiness(biz); + if (binding == null) { + continue; + } + RuleChainDO chain = ruleChainMapper.selectById(binding.getRuleChainId()); + if (chain == null) { + continue; + } + RuleChainStructureDTO structure = readStructure(chain.getStructureJson()); + aggregatedStructure = mergeStructure(aggregatedStructure, structure, binding.getOverrideStrategy()); + lastChainId = chain.getId(); + chainCode = chain.getCode(); + } + if (aggregatedStructure == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_NOT_EXISTS); + } + BusinessChainAssembleResultDTO result = new BusinessChainAssembleResultDTO(); + result.setBusiness(business); + result.setVersion(version != null ? version : "latest"); + result.setChainDbId(lastChainId); + String finalChainCode = StringUtils.hasText(chainCode) ? chainCode : business; + result.setChainCode(finalChainCode); + result.setStructure(aggregatedStructure); + result.setChainId(finalChainCode + ":" + result.getVersion()); + result.setLiteflowDsl(ruleChainAssembler.buildLiteflowDsl(aggregatedStructure, result.getChainId())); + return result; + } + + private RuleChainStructureDTO mergeStructure(RuleChainStructureDTO base, + RuleChainStructureDTO addition, + Integer strategyCode) { + if (base == null) { + return cloneStructure(addition); + } + RuleChainStructureDTO result = cloneStructure(base); + OverrideStrategyEnum strategy = resolveStrategy(strategyCode); + if (strategy == OverrideStrategyEnum.INHERIT) { + return result; + } + if (strategy == OverrideStrategyEnum.DISABLE) { + Set disableCodes = addition.getNodes().stream() + .map(RuleChainNodeDTO::getRuleCode) + .filter(StringUtils::hasText) + .collect(Collectors.toSet()); + result.setNodes(result.getNodes().stream() + .filter(node -> !disableCodes.contains(node.getRuleCode())) + .collect(Collectors.toCollection(ArrayList::new))); + return result; + } + List additionNodes = addition.getNodes().stream() + .map(this::cloneNode) + .collect(Collectors.toList()); + if (strategy == OverrideStrategyEnum.OVERRIDE) { + Set additionCodes = additionNodes.stream() + .map(RuleChainNodeDTO::getRuleCode) + .filter(StringUtils::hasText) + .collect(Collectors.toSet()); + result.setNodes(result.getNodes().stream() + .filter(node -> !additionCodes.contains(node.getRuleCode())) + .collect(Collectors.toCollection(ArrayList::new))); + } + result.getNodes().addAll(additionNodes); + if (!CollectionUtils.isEmpty(addition.getLinks())) { + if (CollectionUtils.isEmpty(result.getLinks())) { + result.setLinks(new ArrayList<>()); + } + result.getLinks().addAll(addition.getLinks()); + } + return result; + } + + private OverrideStrategyEnum resolveStrategy(Integer code) { + for (OverrideStrategyEnum value : OverrideStrategyEnum.values()) { + if (Objects.equals(value.getStrategy(), code)) { + return value; + } + } + return OverrideStrategyEnum.INHERIT; + } + + private RuleChainStructureDTO cloneStructure(RuleChainStructureDTO source) { + return objectMapper.convertValue(source, RuleChainStructureDTO.class); + } + + private RuleChainNodeDTO cloneNode(RuleChainNodeDTO node) { + return objectMapper.convertValue(node, RuleChainNodeDTO.class); + } + + private RuleChainStructureDTO readStructure(String json) { + try { + return objectMapper.readValue(json, RuleChainStructureDTO.class); + } catch (Exception ex) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_STRUCTURE_INVALID, ex.getMessage()); + } + } + + private void validateNoCycle(String parent, String child) { + if (Objects.equals(parent, child)) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_RELATION_CYCLE); + } + Set visited = new HashSet<>(); + Deque stack = new ArrayDeque<>(); + stack.push(parent); + while (!stack.isEmpty()) { + String current = stack.pop(); + if (!visited.add(current)) { + continue; + } + if (Objects.equals(current, child)) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_BUSINESS_RELATION_CYCLE); + } + relationMapper.selectChildren(current).forEach(rel -> stack.push(rel.getChildBusiness())); + } + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainService.java new file mode 100644 index 00000000..33a2dd03 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainService.java @@ -0,0 +1,23 @@ +package com.zt.plat.module.rule.service.chain; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainCreateReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainPageReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainStructureVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainUpdateReqVO; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; + +public interface RuleChainService { + + Long createChain(RuleChainCreateReqVO reqVO); + + void updateChain(RuleChainUpdateReqVO reqVO); + + void deleteChain(Long id); + + RuleChainDO getChain(Long id); + + PageResult getChainPage(RuleChainPageReqVO reqVO); + + RuleChainStructureVO getChainStructure(Long id); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainServiceImpl.java new file mode 100644 index 00000000..2d0f83fd --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/chain/RuleChainServiceImpl.java @@ -0,0 +1,188 @@ +package com.zt.plat.module.rule.service.chain; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainCreateReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainPageReqVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainStructureVO; +import com.zt.plat.module.rule.controller.admin.chain.vo.RuleChainUpdateReqVO; +import com.zt.plat.module.rule.convert.chain.RuleChainConvert; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDO; +import com.zt.plat.module.rule.dal.dataobject.chain.RuleChainDependencyDO; +import com.zt.plat.module.rule.dal.mysql.chain.RuleChainDependencyMapper; +import com.zt.plat.module.rule.dal.mysql.chain.RuleChainMapper; +import com.zt.plat.module.rule.dal.mysql.definition.RuleDefinitionMapper; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import com.zt.plat.module.rule.framework.liteflow.RuleChainAssembler; +import com.zt.plat.module.rule.service.dto.RuleChainNodeDTO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; +import jakarta.annotation.Resource; +import jakarta.validation.ValidationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +public class RuleChainServiceImpl implements RuleChainService { + + @Resource + private RuleChainMapper ruleChainMapper; + @Resource + private RuleChainDependencyMapper ruleChainDependencyMapper; + @Resource + private RuleDefinitionMapper ruleDefinitionMapper; + @Resource + private RuleChainAssembler ruleChainAssembler; + @Resource + private RuleChainConvert ruleChainConvert; + @Resource + private ObjectMapper objectMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createChain(RuleChainCreateReqVO reqVO) { + validateChainCodeUnique(null, reqVO.getCode()); + RuleChainStructureDTO structure = ruleChainConvert.toDto(reqVO.getStructure()); + validateStructure(structure); + + RuleChainDO chain = ruleChainConvert.convert(reqVO); + chain.setStructureJson(writeStructure(reqVO.getStructure())); + chain.setLiteflowDsl(ruleChainAssembler.buildLiteflowDsl(structure, reqVO.getCode())); + if (!StringUtils.hasText(chain.getVersion())) { + chain.setVersion("1.0.0"); + } + ruleChainMapper.insert(chain); + + persistDependencies(chain.getId(), structure); + return chain.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateChain(RuleChainUpdateReqVO reqVO) { + RuleChainDO existed = validateChainExists(reqVO.getId()); + validateChainCodeUnique(reqVO.getId(), reqVO.getCode()); + RuleChainStructureDTO structure = ruleChainConvert.toDto(reqVO.getStructure()); + validateStructure(structure); + + RuleChainDO updateObj = ruleChainConvert.convert(reqVO); + updateObj.setStructureJson(writeStructure(reqVO.getStructure())); + updateObj.setLiteflowDsl(ruleChainAssembler.buildLiteflowDsl(structure, reqVO.getCode())); + if (!StringUtils.hasText(updateObj.getVersion())) { + updateObj.setVersion(existed.getVersion()); + } + ruleChainMapper.updateById(updateObj); + + ruleChainDependencyMapper.deleteByParentChainId(reqVO.getId()); + persistDependencies(reqVO.getId(), structure); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteChain(Long id) { + validateChainExists(id); + ruleChainMapper.deleteById(id); + ruleChainDependencyMapper.deleteByParentChainId(id); + } + + @Override + public RuleChainDO getChain(Long id) { + return ruleChainMapper.selectById(id); + } + + @Override + public PageResult getChainPage(RuleChainPageReqVO reqVO) { + return ruleChainMapper.selectPage(reqVO); + } + + @Override + public RuleChainStructureVO getChainStructure(Long id) { + RuleChainDO chain = validateChainExists(id); + try { + return objectMapper.readValue(chain.getStructureJson(), RuleChainStructureVO.class); + } catch (Exception ex) { + throw new ValidationException("链路结构数据异常: " + ex.getMessage()); + } + } + + private RuleChainDO validateChainExists(Long id) { + RuleChainDO chain = ruleChainMapper.selectById(id); + if (chain == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_NOT_EXISTS); + } + return chain; + } + + private void validateChainCodeUnique(Long id, String code) { + RuleChainDO existed = ruleChainMapper.selectByCode(code); + if (existed != null && (id == null || !Objects.equals(existed.getId(), id))) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_CODE_DUPLICATE); + } + } + + private void validateStructure(RuleChainStructureDTO structure) { + ruleChainAssembler.validate(structure); + if (!CollectionUtils.isEmpty(structure.getNodes())) { + List missingRuleIds = structure.getNodes().stream() + .map(RuleChainNodeDTO::getRuleId) + .filter(Objects::nonNull) + .distinct() + .filter(ruleId -> ruleDefinitionMapper.selectById(ruleId) == null) + .collect(Collectors.toList()); + if (!missingRuleIds.isEmpty()) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_CHAIN_DEPEND_RULE_MISSING, + "缺少规则: " + missingRuleIds); + } + } + } + + private void persistDependencies(Long chainId, RuleChainStructureDTO structure) { + if (CollectionUtils.isEmpty(structure.getNodes())) { + return; + } + List dependencies = new ArrayList<>(); + for (RuleChainNodeDTO node : structure.getNodes()) { + if (node.getRuleId() == null) { + continue; + } + RuleChainDependencyDO dependency = new RuleChainDependencyDO(); + dependency.setParentChainId(chainId); + dependency.setChildRuleId(node.getRuleId()); + dependency.setLinkType(node.getType()); + dependency.setOrderIndex(node.getOrderIndex()); + dependency.setParallelGroup(node.getParallelGroup()); + dependency.setConditionExpr(node.getConditionExpr()); + dependency.setConfigJson(writeConfig(node)); + dependencies.add(dependency); + } + if (!dependencies.isEmpty()) { + dependencies.forEach(ruleChainDependencyMapper::insert); + } + } + + private String writeStructure(RuleChainStructureVO structure) { + try { + return objectMapper.writeValueAsString(structure); + } catch (Exception ex) { + throw new ValidationException("链路结构序列化失败: " + ex.getMessage()); + } + } + + private String writeConfig(RuleChainNodeDTO node) { + if (node.getConfig() == null) { + return null; + } + try { + return objectMapper.writeValueAsString(node.getConfig()); + } catch (Exception ex) { + return null; + } + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionService.java new file mode 100644 index 00000000..bb6619d1 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionService.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.rule.service.definition; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionCreateReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionPageReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionUpdateReqVO; +import com.zt.plat.module.rule.dal.dataobject.definition.RuleDefinitionDO; + +import java.util.Collection; +import java.util.List; + +public interface RuleDefinitionService { + + Long createRule(RuleDefinitionCreateReqVO reqVO); + + void updateRule(RuleDefinitionUpdateReqVO reqVO); + + void deleteRule(Long id); + + RuleDefinitionDO getRule(Long id); + + PageResult getRulePage(RuleDefinitionPageReqVO reqVO); + + List getRules(Collection ids); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionServiceImpl.java new file mode 100644 index 00000000..e45f03e3 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/definition/RuleDefinitionServiceImpl.java @@ -0,0 +1,86 @@ +package com.zt.plat.module.rule.service.definition; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionCreateReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionPageReqVO; +import com.zt.plat.module.rule.controller.admin.definition.vo.RuleDefinitionUpdateReqVO; +import com.zt.plat.module.rule.convert.definition.RuleDefinitionConvert; +import com.zt.plat.module.rule.dal.dataobject.definition.RuleDefinitionDO; +import com.zt.plat.module.rule.dal.mysql.definition.RuleDefinitionMapper; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.List; + +@Service +public class RuleDefinitionServiceImpl implements RuleDefinitionService { + + @Resource + private RuleDefinitionMapper ruleDefinitionMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createRule(RuleDefinitionCreateReqVO reqVO) { + validateRuleCodeUnique(null, reqVO.getCode()); + RuleDefinitionDO rule = RuleDefinitionConvert.INSTANCE.convert(reqVO); + if (!StringUtils.hasText(rule.getVersion())) { + rule.setVersion("1.0.0"); + } + ruleDefinitionMapper.insert(rule); + return rule.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateRule(RuleDefinitionUpdateReqVO reqVO) { + RuleDefinitionDO existed = validateRuleExists(reqVO.getId()); + validateRuleCodeUnique(reqVO.getId(), reqVO.getCode()); + RuleDefinitionDO updateObj = RuleDefinitionConvert.INSTANCE.convert(reqVO); + if (!StringUtils.hasText(updateObj.getVersion())) { + updateObj.setVersion(existed.getVersion()); + } + ruleDefinitionMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteRule(Long id) { + validateRuleExists(id); + ruleDefinitionMapper.deleteById(id); + } + + @Override + public RuleDefinitionDO getRule(Long id) { + return ruleDefinitionMapper.selectById(id); + } + + @Override + public PageResult getRulePage(RuleDefinitionPageReqVO reqVO) { + return ruleDefinitionMapper.selectPage(reqVO); + } + + @Override + public List getRules(Collection ids) { + return ruleDefinitionMapper.selectListByIds(ids); + } + + private RuleDefinitionDO validateRuleExists(Long id) { + RuleDefinitionDO rule = ruleDefinitionMapper.selectById(id); + if (rule == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_DEFINITION_NOT_EXISTS); + } + return rule; + } + + private void validateRuleCodeUnique(Long id, String code) { + RuleDefinitionDO existed = ruleDefinitionMapper.selectByCode(code); + if (existed != null && (id == null || !existed.getId().equals(id))) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_DEFINITION_CODE_DUPLICATE); + } + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/BusinessChainAssembleResultDTO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/BusinessChainAssembleResultDTO.java new file mode 100644 index 00000000..38994a18 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/BusinessChainAssembleResultDTO.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.rule.service.dto; + +import lombok.Data; + +/** + * Aggregation result after applying business inheritance rules. + */ +@Data +public class BusinessChainAssembleResultDTO { + + private String business; + + private String version; + + private String chainId; + + private Long chainDbId; + + private String chainCode; + + private RuleChainStructureDTO structure; + + private String liteflowDsl; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainLinkDTO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainLinkDTO.java new file mode 100644 index 00000000..284a3180 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainLinkDTO.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.rule.service.dto; + +import lombok.Data; + +/** + * Edge representation connecting nodes on the designer canvas. + */ +@Data +public class RuleChainLinkDTO { + + private String id; + + private String source; + + private String target; + + /** + * Optional condition for the edge. + */ + private String conditionExpr; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainNodeDTO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainNodeDTO.java new file mode 100644 index 00000000..4d92f87c --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainNodeDTO.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.rule.service.dto; + +import lombok.Data; + +import java.util.Map; + +/** + * Node representation used by the rule chain designer. + */ +@Data +public class RuleChainNodeDTO { + + private String id; + + private String code; + + private String name; + + /** + * Node type referencing {@link com.zt.plat.module.rule.enums.RuleNodeTypeEnum}. + */ + private Integer type; + + private Long ruleId; + + private String ruleCode; + + private Integer orderIndex; + + private String parallelGroup; + + private String conditionExpr; + + private Boolean disabled; + + private Map config; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainStructureDTO.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainStructureDTO.java new file mode 100644 index 00000000..3af25703 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/dto/RuleChainStructureDTO.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.rule.service.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Structured representation of a chain composed by nodes and edges. + */ +@Data +public class RuleChainStructureDTO { + + private List nodes = new ArrayList<>(); + + private List links = new ArrayList<>(); + + /** + * Custom LiteFlow expression assembled on the client, optional. + */ + private String entryExpression; +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishService.java new file mode 100644 index 00000000..97285709 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishService.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.rule.service.publish; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseCreateReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleasePageReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseRollbackReqVO; +import com.zt.plat.module.rule.dal.dataobject.release.RuleReleaseRecordDO; + +public interface RulePublishService { + + RuleReleaseRecordDO publish(RuleReleaseCreateReqVO reqVO, Long userId, String username); + + void rollback(RuleReleaseRollbackReqVO reqVO, Long userId, String username); + + PageResult getReleasePage(RuleReleasePageReqVO reqVO); + + RuleReleaseRecordDO getLatest(String business); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishServiceImpl.java new file mode 100644 index 00000000..0c07bc6b --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/publish/RulePublishServiceImpl.java @@ -0,0 +1,141 @@ +package com.zt.plat.module.rule.service.publish; + +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseCreateReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleasePageReqVO; +import com.zt.plat.module.rule.controller.admin.publish.vo.RuleReleaseRollbackReqVO; +import com.zt.plat.module.rule.dal.dataobject.business.RuleBusinessBindingDO; +import com.zt.plat.module.rule.dal.dataobject.release.RuleReleaseRecordDO; +import com.zt.plat.module.rule.dal.mysql.business.RuleBusinessBindingMapper; +import com.zt.plat.module.rule.dal.mysql.release.RuleReleaseRecordMapper; +import com.zt.plat.module.rule.enums.ErrorCodeConstants; +import com.zt.plat.module.rule.enums.RulePublishStatusEnum; +import com.zt.plat.module.rule.framework.liteflow.RuleLiteflowManager; +import com.zt.plat.module.rule.framework.nacos.RuleNacosPublisher; +import com.zt.plat.module.rule.service.business.RuleBusinessService; +import com.zt.plat.module.rule.service.dto.BusinessChainAssembleResultDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; + +@Service +public class RulePublishServiceImpl implements RulePublishService { + + @Resource + private RuleBusinessService ruleBusinessService; + @Resource + private RuleReleaseRecordMapper ruleReleaseRecordMapper; + @Resource + private RuleBusinessBindingMapper ruleBusinessBindingMapper; + @Resource + private RuleNacosPublisher ruleNacosPublisher; + @Resource + private RuleLiteflowManager ruleLiteflowManager; + + @Override + @Transactional(rollbackFor = Exception.class) + public RuleReleaseRecordDO publish(RuleReleaseCreateReqVO reqVO, Long userId, String username) { + String business = reqVO.getBusiness(); + String targetVersion = StringUtils.hasText(reqVO.getVersion()) ? reqVO.getVersion() : nextVersion(business); + BusinessChainAssembleResultDTO assembleResult = ruleBusinessService.assembleBusinessChain(business, targetVersion); + assembleResult.setVersion(targetVersion); + assembleResult.setChainId(assembleResult.getChainCode() + ":" + targetVersion); + + RuleReleaseRecordDO record = buildReleaseRecord(assembleResult, RulePublishStatusEnum.PENDING, userId, username); + if (Boolean.TRUE.equals(reqVO.getDryRun())) { + return record; + } + + try { + ruleNacosPublisher.publish(business, targetVersion, assembleResult.getLiteflowDsl()); + ruleLiteflowManager.registerOrUpdate(assembleResult.getChainId(), assembleResult.getLiteflowDsl()); + + record.setStatus(RulePublishStatusEnum.SUCCESS.getStatus()); + record.setReleaseTime(LocalDateTime.now()); + ruleReleaseRecordMapper.insert(record); + + RuleBusinessBindingDO binding = ruleBusinessBindingMapper.selectByBusiness(business); + if (binding != null) { + binding.setEffectiveVersion(targetVersion); + ruleBusinessBindingMapper.updateById(binding); + } + return record; + } catch (Exception ex) { + record.setStatus(RulePublishStatusEnum.FAILED.getStatus()); + record.setRemark(ex.getMessage()); + ruleReleaseRecordMapper.insert(record); + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_PUBLISH_PUSH_FAILED, ex.getMessage()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void rollback(RuleReleaseRollbackReqVO reqVO, Long userId, String username) { + RuleReleaseRecordDO existed = ruleReleaseRecordMapper.selectByBusinessAndVersion(reqVO.getBusiness(), reqVO.getVersion()); + if (existed == null) { + throw ServiceExceptionUtil.exception(ErrorCodeConstants.RULE_RELEASE_NOT_EXISTS); + } + BusinessChainAssembleResultDTO assembleResult = ruleBusinessService.assembleBusinessChain(reqVO.getBusiness(), reqVO.getVersion()); + assembleResult.setChainId(assembleResult.getChainCode() + ":" + assembleResult.getVersion()); + + ruleNacosPublisher.publish(reqVO.getBusiness(), reqVO.getVersion(), assembleResult.getLiteflowDsl()); + ruleLiteflowManager.registerOrUpdate(assembleResult.getChainId(), assembleResult.getLiteflowDsl()); + + RuleReleaseRecordDO record = buildReleaseRecord(assembleResult, RulePublishStatusEnum.ROLLBACK, userId, username); + record.setReleaseTime(LocalDateTime.now()); + ruleReleaseRecordMapper.insert(record); + + RuleBusinessBindingDO binding = ruleBusinessBindingMapper.selectByBusiness(reqVO.getBusiness()); + if (binding != null) { + binding.setEffectiveVersion(reqVO.getVersion()); + ruleBusinessBindingMapper.updateById(binding); + } + } + + @Override + public PageResult getReleasePage(RuleReleasePageReqVO reqVO) { + return ruleReleaseRecordMapper.selectPage(reqVO); + } + + @Override + public RuleReleaseRecordDO getLatest(String business) { + return ruleReleaseRecordMapper.selectLatest(business); + } + + private String nextVersion(String business) { + RuleReleaseRecordDO latest = ruleReleaseRecordMapper.selectLatest(business); + if (latest == null) { + return "v1"; + } + String version = latest.getVersion(); + if (!StringUtils.hasText(version)) { + return "v1"; + } + try { + String numeric = version.startsWith("v") ? version.substring(1) : version; + int num = Integer.parseInt(numeric); + return "v" + (num + 1); + } catch (NumberFormatException ex) { + return version + "_next"; + } + } + + private RuleReleaseRecordDO buildReleaseRecord(BusinessChainAssembleResultDTO assembleResult, + RulePublishStatusEnum statusEnum, + Long userId, + String username) { + RuleReleaseRecordDO record = new RuleReleaseRecordDO(); + record.setBusiness(assembleResult.getBusiness()); + record.setChainId(assembleResult.getChainId()); + record.setChainCode(assembleResult.getChainCode()); + record.setVersion(assembleResult.getVersion()); + record.setStatus(statusEnum.getStatus()); + record.setReleaseUserId(userId); + record.setReleaseUserName(username); + return record; + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleService.java deleted file mode 100644 index 1fb8e474..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleService.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.zt.plat.module.rule.service.rule; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.module.rule.controller.admin.rule.vo.*; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; - -import javax.validation.Valid; - -/** - * 规则 Service 接口 - * - * @author 芋道源码 - */ -public interface RuleService { - - /** - * 创建规则 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createRule(@Valid RuleCreateReqVO createReqVO); - - /** - * 更新规则 - * - * @param updateReqVO 更新信息 - */ - void updateRule(@Valid RuleUpdateReqVO updateReqVO); - - /** - * 删除规则 - * - * @param id 编号 - */ - void deleteRule(Long id); - - /** - * 获得规则 - * - * @param id 编号 - * @return 规则 - */ - RuleDO getRule(Long id); - - /** - * 获得规则分页 - * - * @param pageReqVO 分页查询 - * @return 规则分页 - */ - PageResult getRulePage(RulePageReqVO pageReqVO); - - /** - * 执行规则 - * - * @param executeReqVO 执行请求 - * @return 执行结果 - */ - RuleExecuteRespVO executeRule(@Valid RuleExecuteReqVO executeReqVO); - - /** - * 启用规则 - * - * @param id 规则ID - */ - void enableRule(Long id); - - /** - * 禁用规则 - * - * @param id 规则ID - */ - void disableRule(Long id); - - /** - * 验证规则配置 - * - * @param config 规则配置JSON - * @return 验证结果 - */ - Boolean validateRuleConfig(String config); - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleServiceImpl.java deleted file mode 100644 index 59df10ff..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/rule/RuleServiceImpl.java +++ /dev/null @@ -1,252 +0,0 @@ -package com.zt.plat.module.rule.service.rule; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.module.rule.controller.admin.rule.vo.*; -import com.zt.plat.module.rule.convert.rule.RuleConvert; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import com.zt.plat.module.rule.dal.mysql.rule.RuleMapper; -import com.zt.plat.module.rule.enums.ErrorCodeConstants; -import com.zt.plat.module.rule.framework.liteflow.service.LiteFlowService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import javax.validation.Valid; -import java.time.LocalDateTime; -import java.util.Map; - -import static com.zt.plat.module.rule.enums.ErrorCodeConstants.*; - -/** - * 规则 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class RuleServiceImpl implements RuleService { - - @Resource - private RuleMapper ruleMapper; - - @Resource - private LiteFlowService liteFlowService; - - @Override - @Transactional(rollbackFor = Exception.class) - public Long createRule(RuleCreateReqVO createReqVO) { - // 验证规则配置 - if (!validateRuleConfig(createReqVO.getConfig())) { - throw ServiceExceptionUtil.exception(RULE_CONFIG_INVALID); - } - - // 插入 - RuleDO rule = RuleConvert.INSTANCE.convert(createReqVO); - rule.setVersion("1.0.0"); - ruleMapper.insert(rule); - - // 如果是启用状态,加载到LiteFlow中 - if (rule.getStatus() == 1) { - try { - liteFlowService.loadRule(rule); - } catch (Exception e) { - log.error("加载规则到LiteFlow失败", e); - throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED); - } - } - - return rule.getId(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void updateRule(RuleUpdateReqVO updateReqVO) { - // 校验存在 - validateRuleExists(updateReqVO.getId()); - - // 验证规则配置 - if (!validateRuleConfig(updateReqVO.getConfig())) { - throw ServiceExceptionUtil.exception(RULE_CONFIG_INVALID); - } - - // 更新 - RuleDO updateObj = RuleConvert.INSTANCE.convert(updateReqVO); - ruleMapper.updateById(updateObj); - - // 重新加载到LiteFlow中 - RuleDO rule = ruleMapper.selectById(updateReqVO.getId()); - if (rule.getStatus() == 1) { - try { - liteFlowService.reloadRule(rule); - } catch (Exception e) { - log.error("重新加载规则到LiteFlow失败", e); - throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED); - } - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void deleteRule(Long id) { - // 校验存在 - RuleDO rule = validateRuleExists(id); - - // 从LiteFlow中移除 - try { - liteFlowService.removeRule(rule.getChainId()); - } catch (Exception e) { - log.error("从LiteFlow中移除规则失败", e); - } - - // 删除 - ruleMapper.deleteById(id); - } - - private RuleDO validateRuleExists(Long id) { - RuleDO rule = ruleMapper.selectById(id); - if (rule == null) { - throw ServiceExceptionUtil.exception(RULE_NOT_EXISTS); - } - return rule; - } - - @Override - public RuleDO getRule(Long id) { - return ruleMapper.selectById(id); - } - - @Override - public PageResult getRulePage(RulePageReqVO pageReqVO) { - return ruleMapper.selectPage(pageReqVO); - } - - @Override - public RuleExecuteRespVO executeRule(RuleExecuteReqVO executeReqVO) { - LocalDateTime startTime = LocalDateTime.now(); - - try { - // 获取规则配置 - String chainId; - if (executeReqVO.getRuleId() != null) { - RuleDO rule = getRule(executeReqVO.getRuleId()); - if (rule == null) { - throw ServiceExceptionUtil.exception(RULE_NOT_EXISTS); - } - if (rule.getStatus() != 1) { - throw ServiceExceptionUtil.exception(RULE_NOT_ENABLED); - } - chainId = rule.getChainId(); - } else { - chainId = executeReqVO.getChainId(); - } - - if (StrUtil.isBlank(chainId)) { - throw ServiceExceptionUtil.exception(RULE_CHAIN_ID_EMPTY); - } - - // 执行规则 - Map resultData = liteFlowService.executeRule( - chainId, - executeReqVO.getContextData(), - executeReqVO.getExtParams() - ); - - LocalDateTime endTime = LocalDateTime.now(); - - // 构建响应 - RuleExecuteRespVO response = new RuleExecuteRespVO(); - response.setSuccess(true); - response.setResultData(resultData); - response.setStartTime(startTime); - response.setEndTime(endTime); - response.setExecutionTime(java.time.Duration.between(startTime, endTime).toMillis()); - response.setChainId(chainId); - response.setContextSnapshot(executeReqVO.getContextData()); - - return response; - - } catch (Exception e) { - log.error("规则执行失败", e); - - LocalDateTime endTime = LocalDateTime.now(); - - RuleExecuteRespVO response = new RuleExecuteRespVO(); - response.setSuccess(false); - response.setErrorMessage(e.getMessage()); - response.setStartTime(startTime); - response.setEndTime(endTime); - response.setExecutionTime(java.time.Duration.between(startTime, endTime).toMillis()); - response.setContextSnapshot(executeReqVO.getContextData()); - - return response; - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void enableRule(Long id) { - RuleDO rule = validateRuleExists(id); - - // 更新状态 - RuleDO updateObj = new RuleDO(); - updateObj.setId(id); - updateObj.setStatus(1); - ruleMapper.updateById(updateObj); - - // 加载到LiteFlow中 - try { - rule.setStatus(1); - liteFlowService.loadRule(rule); - } catch (Exception e) { - log.error("启用规则时加载到LiteFlow失败", e); - throw ServiceExceptionUtil.exception(RULE_LOAD_FAILED); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void disableRule(Long id) { - RuleDO rule = validateRuleExists(id); - - // 更新状态 - RuleDO updateObj = new RuleDO(); - updateObj.setId(id); - updateObj.setStatus(0); - ruleMapper.updateById(updateObj); - - // 从LiteFlow中移除 - try { - liteFlowService.removeRule(rule.getChainId()); - } catch (Exception e) { - log.error("禁用规则时从LiteFlow中移除失败", e); - } - } - - @Override - public Boolean validateRuleConfig(String config) { - if (StrUtil.isBlank(config)) { - return false; - } - - try { - // 验证JSON格式 - if (!JSONUtil.isTypeJSON(config)) { - return false; - } - - // 进一步验证规则配置结构 - return liteFlowService.validateRuleConfig(config); - - } catch (Exception e) { - log.warn("规则配置验证失败", e); - return false; - } - } - -} \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationService.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationService.java new file mode 100644 index 00000000..27a138d6 --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationService.java @@ -0,0 +1,9 @@ +package com.zt.plat.module.rule.service.simulation; + +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteReqVO; +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteRespVO; + +public interface RuleSimulationService { + + RuleSimulationExecuteRespVO execute(RuleSimulationExecuteReqVO reqVO); +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationServiceImpl.java b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationServiceImpl.java new file mode 100644 index 00000000..e8f77e6b --- /dev/null +++ b/zt-module-rule/zt-module-rule-server/src/main/java/com/zt/plat/module/rule/service/simulation/RuleSimulationServiceImpl.java @@ -0,0 +1,94 @@ +package com.zt.plat.module.rule.service.simulation; + +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteReqVO; +import com.zt.plat.module.rule.controller.admin.simulation.vo.RuleSimulationExecuteRespVO; +import com.zt.plat.module.rule.framework.liteflow.RuleLiteflowManager; +import com.zt.plat.module.rule.service.business.RuleBusinessService; +import com.zt.plat.module.rule.service.dto.BusinessChainAssembleResultDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +@Service +public class RuleSimulationServiceImpl implements RuleSimulationService { + + @Resource + private RuleBusinessService ruleBusinessService; + @Resource + private RuleLiteflowManager ruleLiteflowManager; + + @Override + public RuleSimulationExecuteRespVO execute(RuleSimulationExecuteReqVO reqVO) { + BusinessChainAssembleResultDTO assembleResult = ruleBusinessService.assembleBusinessChain(reqVO.getBusiness(), reqVO.getVersion()); + String runtimeChainId = determineChainId(reqVO, assembleResult); + + ruleLiteflowManager.registerOrUpdate(assembleResult.getChainId(), assembleResult.getLiteflowDsl()); + if (!Objects.equals(runtimeChainId, assembleResult.getChainId())) { + ruleLiteflowManager.registerOrUpdate(runtimeChainId, assembleResult.getLiteflowDsl()); + } + + Map requestData = reqVO.getRequestData(); + if (requestData == null || requestData.isEmpty()) { + requestData = Collections.emptyMap(); + } + LiteflowResponse response = ruleLiteflowManager.execute(runtimeChainId, requestData); + + RuleSimulationExecuteRespVO respVO = new RuleSimulationExecuteRespVO(); + respVO.setChainId(runtimeChainId); + respVO.setSuccess(response != null && response.isSuccess()); + respVO.setMessage(response != null ? response.getMessage() : null); + respVO.setTrace(extractTrace(response)); + respVO.setSlotData(extractSlotData(response)); + return respVO; + } + + private String determineChainId(RuleSimulationExecuteReqVO reqVO, BusinessChainAssembleResultDTO assembleResult) { + if (StringUtils.hasText(reqVO.getChainId())) { + return reqVO.getChainId(); + } + if (StringUtils.hasText(assembleResult.getChainId())) { + return assembleResult.getChainId(); + } + return assembleResult.getBusiness() + ":" + assembleResult.getVersion(); + } + + private String extractTrace(LiteflowResponse response) { + if (response == null) { + return null; + } + try { + Method method = response.getClass().getMethod("getExecuteStepStr"); + Object trace = method.invoke(response); + return trace != null ? trace.toString() : null; + } catch (Exception ignored) { + return null; + } + } + + @SuppressWarnings("unchecked") + private Map extractSlotData(LiteflowResponse response) { + if (response == null) { + return Collections.emptyMap(); + } + try { + Object slot = response.getSlot(); + if (slot == null) { + return Collections.emptyMap(); + } + Method method = slot.getClass().getMethod("getDataMap"); + Object value = method.invoke(slot); + if (value instanceof Map) { + return (Map) value; + } + return Collections.emptyMap(); + } catch (Exception ignored) { + return Collections.emptyMap(); + } + } +} diff --git a/zt-module-rule/zt-module-rule-server/src/main/resources/application-dev.yml b/zt-module-rule/zt-module-rule-server/src/main/resources/application-dev.yml deleted file mode 100644 index 30abeecd..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/resources/application-dev.yml +++ /dev/null @@ -1,107 +0,0 @@ -spring: - # 数据源配置项 - autoconfigure: - exclude: - datasource: - druid: # Druid 【监控】相关的全局配置 - web-stat-filter: - enabled: true - stat-view-servlet: - enabled: true - allow: # 设置白名单,不填则允许所有访问 - url-pattern: /druid/* - login-username: # 控制台管理用户名和密码 - login-password: - filter: - stat: - enabled: true - log-slow-sql: true # 慢 SQL 记录 - slow-sql-millis: 100 - merge-sql: true - wall: - config: - multi-statement-allow: true - dynamic: # 多数据源配置 - druid: # Druid 【连接池】相关的全局配置 - initial-size: 5 # 初始连接数 - min-idle: 10 # 最小连接池数量 - max-active: 20 # 最大连接池数量 - max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 - time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 - min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 - max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 - validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 - test-while-idle: true - test-on-borrow: false - test-on-return: false - primary: master - datasource: - master: - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO - username: SYSDBA - password: pgbsci6ddJ6Sqj@e - slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 - lazy: true # 开启懒加载,保证启动速度 - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO - username: SYSDBA - password: pgbsci6ddJ6Sqj@e - - # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - data: - redis: - host: 172.16.46.63 # 地址 - port: 30379 # 端口 - database: 0 # 数据库索引 -# password: 123456 # 密码,建议生产环境开启 - -xxl: - job: - admin: - addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 - -# Lock4j 配置项 -lock4j: - acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 - expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 - -# Actuator 监控端点的配置项 -management: - endpoints: - web: - base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator - exposure: - include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 - -# 日志文件配置 -logging: - file: - name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 - - -justauth: - enabled: true - type: - DINGTALK: # 钉钉 - client-id: dingvrnreaje3yqvzhxg - client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI - ignore-check-redirect-uri: true - WECHAT_ENTERPRISE: # 企业微信 - client-id: wwd411c69a39ad2e54 - client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw - agent-id: 1000004 - ignore-check-redirect-uri: true - # noinspection SpringBootApplicationYaml - WECHAT_MINI_PROGRAM: # 微信小程序 - client-id: ${dollar}{wx.miniapp.appid} - client-secret: ${dollar}{wx.miniapp.secret} - ignore-check-redirect-uri: true - ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验 - WECHAT_MP: # 微信公众号 - client-id: ${dollar}{wx.mp.app-id} - client-secret: ${dollar}{wx.mp.secret} - ignore-check-redirect-uri: true - cache: - type: REDIS - prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: - timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 - diff --git a/zt-module-rule/zt-module-rule-server/src/main/resources/application-local.yml b/zt-module-rule/zt-module-rule-server/src/main/resources/application-local.yml deleted file mode 100644 index e00acea2..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/resources/application-local.yml +++ /dev/null @@ -1,97 +0,0 @@ -spring: - # 数据源配置项 - autoconfigure: - # noinspection SpringBootApplicationYaml - exclude: - - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源 - datasource: - druid: # Druid 【监控】相关的全局配置 - web-stat-filter: - enabled: true - stat-view-servlet: - enabled: true - allow: # 设置白名单,不填则允许所有访问 - url-pattern: /druid/* - login-username: # 控制台管理用户名和密码 - login-password: - filter: - stat: - enabled: true - log-slow-sql: true # 慢 SQL 记录 - slow-sql-millis: 100 - merge-sql: true - wall: - config: - multi-statement-allow: true - dynamic: # 多数据源配置 - druid: # Druid 【连接池】相关的全局配置 - initial-size: 1 # 初始连接数 - min-idle: 1 # 最小连接池数量 - max-active: 20 # 最大连接池数量 - max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 - time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 - min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 - max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 - validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 - test-while-idle: true - test-on-borrow: false - test-on-return: false - primary: master - datasource: - master: - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO - username: SYSDBA - password: pgbsci6ddJ6Sqj@e - slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 - lazy: true # 开启懒加载,保证启动速度 - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO - username: SYSDBA - password: pgbsci6ddJ6Sqj@e - - # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - data: - redis: - host: 172.16.46.63 # 地址 - port: 30379 # 端口 - database: 0 # 数据库索引 -# password: 123456 # 密码,建议生产环境开启 - -xxl: - job: - admin: - addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 - -# Lock4j 配置项 -lock4j: - acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 - expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 - -# Actuator 监控端点的配置项 -management: - endpoints: - web: - base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator - exposure: - include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 - -# 日志文件配置 -logging: - level: - # 配置自己写的 MyBatis Mapper 打印日志 - com.zt.plat.module.rule.dal.mysql: debug - org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR - -mybatis-plus: - configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - - -# 芋道配置项,设置当前项目所有自定义的配置 -zt: - env: # 多环境的配置项 - tag: ${HOSTNAME} - security: - mock-enable: true - access-log: # 访问日志的配置项 - enable: true - diff --git a/zt-module-rule/zt-module-rule-server/src/main/resources/application.yml b/zt-module-rule/zt-module-rule-server/src/main/resources/application.yml index 34cc2113..61443a57 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/resources/application.yml +++ b/zt-module-rule/zt-module-rule-server/src/main/resources/application.yml @@ -128,8 +128,8 @@ liteflow: enable: true # 解析格式,支持 xml,json,yml parse-type: json - # 是否开启主要过程耗时统计 - main-executor-works: true + # 主执行器并发线程数 + main-executor-works: 16 # 是否开启监控 monitor: enable-log: true diff --git a/zt-module-rule/zt-module-rule-server/src/main/resources/liteflow/default-rules.json b/zt-module-rule/zt-module-rule-server/src/main/resources/liteflow/default-rules.json index 635e5ae3..70af80af 100644 --- a/zt-module-rule/zt-module-rule-server/src/main/resources/liteflow/default-rules.json +++ b/zt-module-rule/zt-module-rule-server/src/main/resources/liteflow/default-rules.json @@ -1,35 +1,37 @@ { "flow": { - "nodes": [ - { - "id": "numberCompareNode", - "name": "数值比较节点", - "type": "common", - "clazz": "com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent" - }, - { - "id": "stringConditionNode", - "name": "字符串条件节点", - "type": "common", - "clazz": "com.zt.plat.module.rule.framework.liteflow.component.common.StringConditionComponent" - }, - { - "id": "mathCalculateNode", - "name": "数学计算节点", - "type": "common", - "clazz": "com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent" - }, - { - "id": "dataSetNode", - "name": "数据设置节点", - "type": "common", - "clazz": "com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent" - } - ], - "chains": [ + "nodes": { + "node": [ + { + "id": "numberCompareNode", + "name": "数值比较节点", + "type": "COMMON", + "class": "com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent" + }, + { + "id": "stringConditionNode", + "name": "字符串条件节点", + "type": "COMMON", + "class": "com.zt.plat.module.rule.framework.liteflow.component.common.StringConditionComponent" + }, + { + "id": "mathCalculateNode", + "name": "数学计算节点", + "type": "COMMON", + "class": "com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent" + }, + { + "id": "dataSetNode", + "name": "数据设置节点", + "type": "COMMON", + "class": "com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent" + } + ] + }, + "chain": [ { "name": "示例规则链", - "condition": "THEN(numberCompareNode, mathCalculateNode, dataSetNode)" + "value": "THEN(numberCompareNode,mathCalculateNode,dataSetNode)" } ] } diff --git a/zt-module-rule/zt-module-rule-server/src/main/resources/sql/rule_rule.sql b/zt-module-rule/zt-module-rule-server/src/main/resources/sql/rule_rule.sql deleted file mode 100644 index 6cd7767a..00000000 --- a/zt-module-rule/zt-module-rule-server/src/main/resources/sql/rule_rule.sql +++ /dev/null @@ -1,31 +0,0 @@ --- ---------------------------- --- Table structure for rule_rule --- ---------------------------- -DROP TABLE IF EXISTS `rule_rule`; -CREATE TABLE `rule_rule` ( - `id` bigint NOT NULL AUTO_INCREMENT COMMENT '规则ID', - `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则名称', - `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规则描述', - `type` tinyint NOT NULL COMMENT '规则类型:1-原子规则 2-链式规则', - `status` tinyint NOT NULL DEFAULT '1' COMMENT '规则状态:0-禁用 1-启用', - `config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '规则配置(JSON格式)', - `chain_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'LiteFlow规则链ID', - `version` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1.0.0' COMMENT '规则版本', - `sort` int DEFAULT '1' COMMENT '排序', - `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', - `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE KEY `uk_chain_id` (`chain_id`,`deleted`,`tenant_id`) USING BTREE COMMENT '规则链ID唯一索引', - KEY `idx_name` (`name`) USING BTREE COMMENT '规则名称索引', - KEY `idx_status` (`status`) USING BTREE COMMENT '规则状态索引', - KEY `idx_type` (`type`) USING BTREE COMMENT '规则类型索引' -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则表'; - --- ---------------------------- --- Records of rule_rule --- ---------------------------- -INSERT INTO `rule_rule` VALUES (1, '用户积分计算规则', '根据用户行为计算积分奖励', 2, 1, '{"chain":{"chainId":"userPointsChain","chainName":"用户积分计算链","expression":"THEN(numberCompareNode, mathCalculateNode, dataSetNode)","enable":true},"nodes":[{"nodeId":"numberCompareNode","nodeName":"数值比较","nodeType":"common","clazz":"com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent","properties":{"leftValue":"100","operator":">","rightValue":"50"},"enable":true},{"nodeId":"mathCalculateNode","nodeName":"积分计算","nodeType":"common","clazz":"com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent","properties":{"leftValue":"100","operator":"*","rightValue":"1.5","resultKey":"finalPoints","scale":"0"},"enable":true},{"nodeId":"dataSetNode","nodeName":"设置结果","nodeType":"common","clazz":"com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent","properties":{"dataKey":"userPoints","dataValue":"150","valueType":"integer"},"enable":true}]}', 'userPointsChain', '1.0.0', 1, '', '2024-01-01 00:00:00', '', '2024-01-01 00:00:00', b'0', 1); \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/example/RuleEngineExample.java b/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/example/RuleEngineExample.java index 10f115b6..9174d2b0 100644 --- a/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/example/RuleEngineExample.java +++ b/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/example/RuleEngineExample.java @@ -1,222 +1,29 @@ package com.zt.plat.module.rule.example; -import cn.hutool.json.JSONUtil; -import com.zt.plat.module.rule.controller.admin.rule.vo.RuleExecuteReqVO; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.module.rule.framework.liteflow.RuleChainAssembler; +import com.zt.plat.module.rule.service.dto.RuleChainNodeDTO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; /** - * 规则引擎使用示例 - * 演示如何使用JSON配置来定义和组合原子规则 - * - * @author 芋道源码 + * Minimal example helper that produces a LiteFlow DSL snippet using the rebuilt module structures. */ -@Slf4j -public class RuleEngineExample { +public final class RuleEngineExample { - /** - * 示例1:用户积分计算规则 - * 规则逻辑:如果用户消费金额大于100元,则积分=消费金额*1.5,否则积分=消费金额*1.0 - */ - public static String buildUserPointsRule() { - RuleExecuteReqVO.RuleConfig ruleConfig = new RuleExecuteReqVO.RuleConfig(); - - // 设置规则链 - RuleExecuteReqVO.ChainConfig chain = new RuleExecuteReqVO.ChainConfig(); - chain.setChainId("userPointsChain"); - chain.setChainName("用户积分计算链"); - chain.setExpression("IF(amountCheck, THEN(highLevelCalc, setHighPoints), THEN(normalCalc, setNormalPoints))"); - chain.setEnable(true); - ruleConfig.setChain(chain); - - // 设置节点列表 - List nodes = new ArrayList<>(); - - // 节点1:金额检查 - RuleExecuteReqVO.NodeConfig amountCheckNode = new RuleExecuteReqVO.NodeConfig(); - amountCheckNode.setNodeId("amountCheck"); - amountCheckNode.setNodeName("金额检查"); - amountCheckNode.setNodeType("condition"); - amountCheckNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent"); - Map amountCheckProps = new HashMap<>(); - amountCheckProps.put("leftValue", "#{amount}"); // 从上下文获取 - amountCheckProps.put("operator", ">"); - amountCheckProps.put("rightValue", "100"); - amountCheckNode.setProperties(amountCheckProps); - amountCheckNode.setEnable(true); - nodes.add(amountCheckNode); - - // 节点2:高等级计算 - RuleExecuteReqVO.NodeConfig highLevelCalcNode = new RuleExecuteReqVO.NodeConfig(); - highLevelCalcNode.setNodeId("highLevelCalc"); - highLevelCalcNode.setNodeName("高等级积分计算"); - highLevelCalcNode.setNodeType("common"); - highLevelCalcNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent"); - Map highCalcProps = new HashMap<>(); - highCalcProps.put("leftValue", "#{amount}"); - highCalcProps.put("operator", "*"); - highCalcProps.put("rightValue", "1.5"); - highCalcProps.put("resultKey", "points"); - highCalcProps.put("scale", "0"); - highLevelCalcNode.setProperties(highCalcProps); - highLevelCalcNode.setEnable(true); - nodes.add(highLevelCalcNode); - - // 节点3:普通计算 - RuleExecuteReqVO.NodeConfig normalCalcNode = new RuleExecuteReqVO.NodeConfig(); - normalCalcNode.setNodeId("normalCalc"); - normalCalcNode.setNodeName("普通积分计算"); - normalCalcNode.setNodeType("common"); - normalCalcNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent"); - Map normalCalcProps = new HashMap<>(); - normalCalcProps.put("leftValue", "#{amount}"); - normalCalcProps.put("operator", "*"); - normalCalcProps.put("rightValue", "1.0"); - normalCalcProps.put("resultKey", "points"); - normalCalcProps.put("scale", "0"); - normalCalcNode.setProperties(normalCalcProps); - normalCalcNode.setEnable(true); - nodes.add(normalCalcNode); - - // 节点4:设置高积分结果 - RuleExecuteReqVO.NodeConfig setHighPointsNode = new RuleExecuteReqVO.NodeConfig(); - setHighPointsNode.setNodeId("setHighPoints"); - setHighPointsNode.setNodeName("设置高积分结果"); - setHighPointsNode.setNodeType("common"); - setHighPointsNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent"); - Map setHighProps = new HashMap<>(); - setHighProps.put("dataKey", "level"); - setHighProps.put("dataValue", "VIP"); - setHighProps.put("valueType", "string"); - setHighPointsNode.setProperties(setHighProps); - setHighPointsNode.setEnable(true); - nodes.add(setHighPointsNode); - - // 节点5:设置普通积分结果 - RuleExecuteReqVO.NodeConfig setNormalPointsNode = new RuleExecuteReqVO.NodeConfig(); - setNormalPointsNode.setNodeId("setNormalPoints"); - setNormalPointsNode.setNodeName("设置普通积分结果"); - setNormalPointsNode.setNodeType("common"); - setNormalPointsNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent"); - Map setNormalProps = new HashMap<>(); - setNormalProps.put("dataKey", "level"); - setNormalProps.put("dataValue", "NORMAL"); - setNormalProps.put("valueType", "string"); - setNormalPointsNode.setProperties(setNormalProps); - setNormalPointsNode.setEnable(true); - nodes.add(setNormalPointsNode); - - ruleConfig.setNodes(nodes); - - return JSONUtil.toJsonPrettyStr(ruleConfig); + private RuleEngineExample() { } - /** - * 示例2:商品折扣规则 - * 规则逻辑:VIP用户享受8折,普通用户满200元享受9折,否则无折扣 - */ - public static String buildDiscountRule() { - RuleExecuteReqVO.RuleConfig ruleConfig = new RuleExecuteReqVO.RuleConfig(); - - // 设置规则链 - RuleExecuteReqVO.ChainConfig chain = new RuleExecuteReqVO.ChainConfig(); - chain.setChainId("discountChain"); - chain.setChainName("商品折扣计算链"); - chain.setExpression("IF(vipCheck, THEN(setVipDiscount), IF(amountCheck, setNormalDiscount, setNoDiscount))"); - chain.setEnable(true); - ruleConfig.setChain(chain); + public static String buildSampleDsl() { + RuleChainStructureDTO structure = new RuleChainStructureDTO(); + RuleChainNodeDTO node = new RuleChainNodeDTO(); + node.setId("node-1"); + node.setName("Sample Node"); + node.setRuleCode("sampleNode"); + node.setType(7); + structure.getNodes().add(node); + structure.setEntryExpression("sampleNode"); - // 设置节点列表 - List nodes = new ArrayList<>(); - - // 节点1:VIP检查 - RuleExecuteReqVO.NodeConfig vipCheckNode = new RuleExecuteReqVO.NodeConfig(); - vipCheckNode.setNodeId("vipCheck"); - vipCheckNode.setNodeName("VIP用户检查"); - vipCheckNode.setNodeType("condition"); - vipCheckNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.common.StringConditionComponent"); - Map vipCheckProps = new HashMap<>(); - vipCheckProps.put("sourceValue", "#{userLevel}"); - vipCheckProps.put("operator", "equals"); - vipCheckProps.put("targetValue", "VIP"); - vipCheckNode.setProperties(vipCheckProps); - vipCheckNode.setEnable(true); - nodes.add(vipCheckNode); - - // 节点2:金额检查 - RuleExecuteReqVO.NodeConfig amountCheckNode = new RuleExecuteReqVO.NodeConfig(); - amountCheckNode.setNodeId("amountCheck"); - amountCheckNode.setNodeName("金额检查"); - amountCheckNode.setNodeType("condition"); - amountCheckNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent"); - Map amountCheckProps = new HashMap<>(); - amountCheckProps.put("leftValue", "#{totalAmount}"); - amountCheckProps.put("operator", ">="); - amountCheckProps.put("rightValue", "200"); - amountCheckNode.setProperties(amountCheckProps); - amountCheckNode.setEnable(true); - nodes.add(amountCheckNode); - - // 节点3:设置VIP折扣 - RuleExecuteReqVO.NodeConfig setVipDiscountNode = new RuleExecuteReqVO.NodeConfig(); - setVipDiscountNode.setNodeId("setVipDiscount"); - setVipDiscountNode.setNodeName("设置VIP折扣"); - setVipDiscountNode.setNodeType("common"); - setVipDiscountNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent"); - Map setVipProps = new HashMap<>(); - setVipProps.put("dataKey", "discount"); - setVipProps.put("dataValue", "0.8"); - setVipProps.put("valueType", "double"); - setVipDiscountNode.setProperties(setVipProps); - setVipDiscountNode.setEnable(true); - nodes.add(setVipDiscountNode); - - // 节点4:设置普通折扣 - RuleExecuteReqVO.NodeConfig setNormalDiscountNode = new RuleExecuteReqVO.NodeConfig(); - setNormalDiscountNode.setNodeId("setNormalDiscount"); - setNormalDiscountNode.setNodeName("设置普通折扣"); - setNormalDiscountNode.setNodeType("common"); - setNormalDiscountNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent"); - Map setNormalProps = new HashMap<>(); - setNormalProps.put("dataKey", "discount"); - setNormalProps.put("dataValue", "0.9"); - setNormalProps.put("valueType", "double"); - setNormalDiscountNode.setProperties(setNormalProps); - setNormalDiscountNode.setEnable(true); - nodes.add(setNormalDiscountNode); - - // 节点5:设置无折扣 - RuleExecuteReqVO.NodeConfig setNoDiscountNode = new RuleExecuteReqVO.NodeConfig(); - setNoDiscountNode.setNodeId("setNoDiscount"); - setNoDiscountNode.setNodeName("设置无折扣"); - setNoDiscountNode.setNodeType("common"); - setNoDiscountNode.setClazz("com.zt.plat.module.rule.framework.liteflow.component.action.DataSetComponent"); - Map setNoProps = new HashMap<>(); - setNoProps.put("dataKey", "discount"); - setNoProps.put("dataValue", "1.0"); - setNoProps.put("valueType", "double"); - setNoDiscountNode.setProperties(setNoProps); - setNoDiscountNode.setEnable(true); - nodes.add(setNoDiscountNode); - - ruleConfig.setNodes(nodes); - - return JSONUtil.toJsonPrettyStr(ruleConfig); + RuleChainAssembler assembler = new RuleChainAssembler(new ObjectMapper()); + return assembler.buildLiteflowDsl(structure, "sampleChain:v1"); } - - /** - * 打印示例配置 - */ - public static void main(String[] args) { - log.info("=== 用户积分计算规则配置 ==="); - log.info(buildUserPointsRule()); - - log.info("\n=== 商品折扣规则配置 ==="); - log.info(buildDiscountRule()); - } - } \ No newline at end of file diff --git a/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/service/rule/RuleServiceImplTest.java b/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/service/rule/RuleServiceImplTest.java index 097b54b3..4194dd91 100644 --- a/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/service/rule/RuleServiceImplTest.java +++ b/zt-module-rule/zt-module-rule-server/src/test/java/com/zt/plat/module/rule/service/rule/RuleServiceImplTest.java @@ -1,193 +1,35 @@ package com.zt.plat.module.rule.service.rule; -import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; -import com.zt.plat.module.rule.controller.admin.rule.vo.*; -import com.zt.plat.module.rule.dal.dataobject.rule.RuleDO; -import com.zt.plat.module.rule.dal.mysql.rule.RuleMapper; -import com.zt.plat.module.rule.framework.liteflow.service.LiteFlowService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.module.rule.framework.liteflow.RuleChainAssembler; +import com.zt.plat.module.rule.service.dto.RuleChainNodeDTO; +import com.zt.plat.module.rule.service.dto.RuleChainStructureDTO; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import javax.annotation.Resource; -import java.util.HashMap; -import java.util.Map; - -import static com.zt.plat.framework.test.core.util.AssertUtils.*; -import static com.zt.plat.framework.test.core.util.RandomUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; /** - * {@link RuleServiceImpl} 的单元测试类 - * - * @author 芋道源码 + * Lightweight regression test to ensure DSL assembly honours the provided chain identifier. */ -@Import(RuleServiceImpl.class) -public class RuleServiceImplTest extends BaseDbUnitTest { +class RuleServiceImplTest { - @Resource - private RuleServiceImpl ruleService; - - @Resource - private RuleMapper ruleMapper; - - @MockBean - private LiteFlowService liteFlowService; + private final RuleChainAssembler assembler = new RuleChainAssembler(new ObjectMapper()); @Test - public void testCreateRule_success() { - // 准备参数 - RuleCreateReqVO createReqVO = randomPojo(RuleCreateReqVO.class, o -> { - o.setName("测试规则"); - o.setType(1); - o.setStatus(1); - o.setConfig(buildValidRuleConfig()); - }); + void buildLiteflowDsl_usesProvidedChainIdentifier() { + RuleChainStructureDTO structure = new RuleChainStructureDTO(); + RuleChainNodeDTO node = new RuleChainNodeDTO(); + node.setId("node-A"); + node.setName("First Node"); + node.setRuleCode("rule.node.A"); + node.setType(7); + structure.getNodes().add(node); + structure.setEntryExpression("rule.node.A"); - // mock 方法 - when(liteFlowService.validateRuleConfig(anyString())).thenReturn(true); + String dsl = assembler.buildLiteflowDsl(structure, "business:v2"); - // 调用 - Long ruleId = ruleService.createRule(createReqVO); - - // 断言 - assertNotNull(ruleId); - RuleDO rule = ruleMapper.selectById(ruleId); - assertNotNull(rule); - assertEquals("测试规则", rule.getName()); - assertEquals(1, rule.getType()); - assertEquals(1, rule.getStatus()); - assertEquals("1.0.0", rule.getVersion()); + assertThat(dsl) + .contains("\"name\" : \"business:v2\"") + .contains("\"value\" : \"rule.node.A\""); } - - @Test - public void testExecuteRule_success() { - // 准备数据 - RuleDO rule = randomPojo(RuleDO.class, o -> { - o.setId(1L); - o.setName("测试规则"); - o.setStatus(1); - o.setChainId("testChain"); - o.setConfig(buildValidRuleConfig()); - }); - ruleMapper.insert(rule); - - // 准备参数 - RuleExecuteReqVO executeReqVO = new RuleExecuteReqVO(); - executeReqVO.setRuleId(1L); - Map contextData = new HashMap<>(); - contextData.put("amount", 100); - executeReqVO.setContextData(contextData); - - // mock 方法 - Map mockResult = new HashMap<>(); - mockResult.put("success", true); - mockResult.put("result", "执行成功"); - when(liteFlowService.executeRule(eq("testChain"), any(), any())).thenReturn(mockResult); - - // 调用 - RuleExecuteRespVO result = ruleService.executeRule(executeReqVO); - - // 断言 - assertTrue(result.getSuccess()); - assertNotNull(result.getResultData()); - assertEquals("testChain", result.getChainId()); - assertNotNull(result.getExecutionTime()); - } - - @Test - public void testValidateRuleConfig_success() { - // 准备参数 - String config = buildValidRuleConfig(); - - // mock 方法 - when(liteFlowService.validateRuleConfig(config)).thenReturn(true); - - // 调用 - Boolean result = ruleService.validateRuleConfig(config); - - // 断言 - assertTrue(result); - verify(liteFlowService).validateRuleConfig(config); - } - - @Test - public void testEnableRule_success() { - // 准备数据 - RuleDO rule = randomPojo(RuleDO.class, o -> { - o.setId(1L); - o.setStatus(0); - o.setChainId("testChain"); - o.setConfig(buildValidRuleConfig()); - }); - ruleMapper.insert(rule); - - // 调用 - ruleService.enableRule(1L); - - // 断言 - RuleDO updatedRule = ruleMapper.selectById(1L); - assertEquals(1, updatedRule.getStatus()); - verify(liteFlowService).loadRule(any(RuleDO.class)); - } - - @Test - public void testDisableRule_success() { - // 准备数据 - RuleDO rule = randomPojo(RuleDO.class, o -> { - o.setId(1L); - o.setStatus(1); - o.setChainId("testChain"); - }); - ruleMapper.insert(rule); - - // 调用 - ruleService.disableRule(1L); - - // 断言 - RuleDO updatedRule = ruleMapper.selectById(1L); - assertEquals(0, updatedRule.getStatus()); - verify(liteFlowService).removeRule("testChain"); - } - - private String buildValidRuleConfig() { - return "{\n" + - " \"chain\": {\n" + - " \"chainId\": \"testChain\",\n" + - " \"chainName\": \"测试规则链\",\n" + - " \"expression\": \"THEN(numberCompareNode, mathCalculateNode)\",\n" + - " \"enable\": true\n" + - " },\n" + - " \"nodes\": [\n" + - " {\n" + - " \"nodeId\": \"numberCompareNode\",\n" + - " \"nodeName\": \"数值比较\",\n" + - " \"nodeType\": \"common\",\n" + - " \"clazz\": \"com.zt.plat.module.rule.framework.liteflow.component.common.NumberCompareComponent\",\n" + - " \"properties\": {\n" + - " \"leftValue\": \"100\",\n" + - " \"operator\": \">\",\n" + - " \"rightValue\": \"50\"\n" + - " },\n" + - " \"enable\": true\n" + - " },\n" + - " {\n" + - " \"nodeId\": \"mathCalculateNode\",\n" + - " \"nodeName\": \"数学计算\",\n" + - " \"nodeType\": \"common\",\n" + - " \"clazz\": \"com.zt.plat.module.rule.framework.liteflow.component.action.MathCalculateComponent\",\n" + - " \"properties\": {\n" + - " \"leftValue\": \"100\",\n" + - " \"operator\": \"*\",\n" + - " \"rightValue\": \"1.5\",\n" + - " \"resultKey\": \"result\"\n" + - " },\n" + - " \"enable\": true\n" + - " }\n" + - " ]\n" + - "}"; - } - } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApi.java new file mode 100644 index 00000000..237450f5 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApi.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.system.api.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.system.api.dept.dto.DeptExternalCodeRespDTO; +import com.zt.plat.module.system.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 部门外部组织编码映射") +@RequestMapping(ApiConstants.PREFIX + "/dept-external-code") +public interface DeptExternalCodeApi { + + @GetMapping("/get-by-system-and-external") + @Operation(summary = "根据外部系统与外部组织编码查询映射") + CommonResult getBySystemCodeAndExternalCode( + @RequestParam("systemCode") @Parameter(description = "外部系统标识", required = true) String systemCode, + @RequestParam("externalDeptCode") @Parameter(description = "外部组织编码", required = true) String externalDeptCode); + + @GetMapping("/get-by-system-and-dept") + @Operation(summary = "根据外部系统与部门编号查询映射") + CommonResult getBySystemCodeAndDeptId( + @RequestParam("systemCode") @Parameter(description = "外部系统标识", required = true) String systemCode, + @RequestParam("deptId") @Parameter(description = "部门编号", required = true) Long deptId); + + @GetMapping("/list-by-dept") + @Operation(summary = "根据部门编号查询映射列表") + CommonResult> getListByDeptId( + @RequestParam("deptId") @Parameter(description = "部门编号", required = true) Long deptId); + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptExternalCodeRespDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptExternalCodeRespDTO.java new file mode 100644 index 00000000..f7a12889 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptExternalCodeRespDTO.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.system.api.dept.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "RPC - 部门外部组织编码映射 Response DTO") +@Data +public class DeptExternalCodeRespDTO { + + @Schema(description = "映射编号", example = "1024") + private Long id; + + @Schema(description = "部门编号", example = "2048") + private Long deptId; + + @Schema(description = "部门名称", example = "技术部") + private String deptName; + + @Schema(description = "部门编码", example = "DEPT_001") + private String deptCode; + + @Schema(description = "外部系统标识", example = "ERP") + private String systemCode; + + @Schema(description = "外部组织编码", example = "100200") + private String externalDeptCode; + + @Schema(description = "外部组织名称", example = "总部-华东区") + private String externalDeptName; + + @Schema(description = "状态", example = "0") + private Integer status; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "最后更新时间") + private LocalDateTime updateTime; + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java index ec102499..5d41c488 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java @@ -20,6 +20,8 @@ public interface ErrorCodeConstants { ErrorCode AUTH_TEST_LOGIN_NOT_ALLOWED = new ErrorCode(1_002_000_009, "测试登录接口仅在测试环境和本地开发环境下可用"); ErrorCode AUTH_OAUTH2_CALLBACK_ERROR = new ErrorCode(1_002_000_010, "OAuth2回调处理失败:{}"); ErrorCode AUTH_LOGIN_INTERNAL_USER_PASSWORD_NOT_ALLOWED = new ErrorCode(1_002_000_011, "内部用户不允许使用账号密码登录,请通过e办进行统一登录"); + ErrorCode AUTH_LOGIN_EBAN_TOKEN_INVALID = new ErrorCode(1_002_000_012, "token 无效"); + ErrorCode AUTH_LOGIN_EBAN_USER_NOT_SYNC = new ErrorCode(1_002_000_013, "用户未同步到此应用,请联系管理员进行同步"); // ========== 菜单模块 1-002-001-000 ========== ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单"); @@ -65,6 +67,9 @@ public interface ErrorCodeConstants { ErrorCode DEPT_TENANT_RELATION_EXISTS = new ErrorCode(1_002_004_008, "当前租户已经关联组织机构"); ErrorCode DEPT_CODE_NOT_NULL = new ErrorCode(1_002_004_009, "部门编码不能为空"); ErrorCode DEPT_CODE_DUPLICATE = new ErrorCode(1_002_004_010, "已经存在该编码的部门"); + ErrorCode DEPT_EXTERNAL_RELATION_EXISTS = new ErrorCode(1_002_004_011, "已经存在该部门在外部系统({})的编码映射"); + ErrorCode DEPT_EXTERNAL_CODE_DUPLICATE = new ErrorCode(1_002_004_012, "已经存在该外部系统({})的外部组织编码({})"); + ErrorCode DEPT_EXTERNAL_RELATION_NOT_EXISTS = new ErrorCode(1_002_004_013, "组织外部编码映射不存在"); // ========== 岗位模块 1-002-005-000 ========== diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApiImpl.java new file mode 100644 index 00000000..07e9a91a --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptExternalCodeApiImpl.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.system.api.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.collection.CollectionUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.api.dept.dto.DeptExternalCodeRespDTO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; +import com.zt.plat.module.system.service.dept.DeptExternalCodeService; +import com.zt.plat.module.system.service.dept.DeptService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@RestController +@Validated +public class DeptExternalCodeApiImpl implements DeptExternalCodeApi { + + @Resource + private DeptExternalCodeService deptExternalCodeService; + @Resource + private DeptService deptService; + + @Override + public CommonResult getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) { + DeptExternalCodeDO entity = deptExternalCodeService.getBySystemCodeAndExternalCode(systemCode, externalDeptCode); + if (entity == null) { + return success(null); + } + return success(buildResp(entity)); + } + + @Override + public CommonResult getBySystemCodeAndDeptId(String systemCode, Long deptId) { + DeptExternalCodeDO entity = deptExternalCodeService.getBySystemCodeAndDeptId(systemCode, deptId); + if (entity == null) { + return success(null); + } + return success(buildResp(entity)); + } + + @Override + public CommonResult> getListByDeptId(Long deptId) { + List list = deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + List respList = BeanUtils.toBean(list, DeptExternalCodeRespDTO.class); + fillDeptInfo(respList); + return success(respList); + } + + private DeptExternalCodeRespDTO buildResp(DeptExternalCodeDO entity) { + DeptExternalCodeRespDTO respDTO = BeanUtils.toBean(entity, DeptExternalCodeRespDTO.class); + fillDeptInfo(List.of(respDTO)); + return respDTO; + } + + private void fillDeptInfo(List list) { + if (list == null || list.isEmpty()) { + return; + } + Set deptIds = CollectionUtils.convertSet(list, DeptExternalCodeRespDTO::getDeptId); + if (deptIds == null || deptIds.isEmpty()) { + return; + } + Map deptMap = CollectionUtils.convertMap(deptService.getDeptList(deptIds), DeptDO::getId); + list.forEach(item -> { + DeptDO dept = deptMap.get(item.getDeptId()); + if (dept != null) { + item.setDeptName(dept.getName()); + item.setDeptCode(dept.getCode()); + } + }); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java index 6727ef8e..510eb674 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java @@ -99,6 +99,13 @@ public class AuthController { return success(authService.refreshToken(refreshToken)); } + @PostMapping("/verify-password") + @Operation(summary = "校验当前密码是否正确") + public CommonResult verifyPassword(@RequestBody @Valid AuthVerifyPasswordReqVO reqVO) { + authService.verifyPassword(getLoginUserId(), reqVO.getPassword()); + return success(Boolean.TRUE); + } + @GetMapping("/get-permission-info") @Operation(summary = "获取登录用户的权限信息") public CommonResult getPermissionInfo() { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthVerifyPasswordReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthVerifyPasswordReqVO.java new file mode 100644 index 00000000..59aad245 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthVerifyPasswordReqVO.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.system.controller.admin.auth.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +@Schema(description = "管理后台 - 校验当前登录密码 Request VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AuthVerifyPasswordReqVO { + + @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptExternalCodeController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptExternalCodeController.java new file mode 100644 index 00000000..4c30672f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptExternalCodeController.java @@ -0,0 +1,116 @@ +package com.zt.plat.module.system.controller.admin.dept; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodePageReqVO; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeRespVO; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; +import com.zt.plat.module.system.service.dept.DeptExternalCodeService; +import com.zt.plat.module.system.service.dept.DeptService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 部门外部组织编码映射") +@RestController +@RequestMapping("/system/dept-external-code") +@Validated +public class DeptExternalCodeController { + + @Resource + private DeptExternalCodeService deptExternalCodeService; + @Resource + private DeptService deptService; + + @PostMapping("/create") + @Operation(summary = "创建外部组织编码映射") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:create')") + public CommonResult create(@Valid @RequestBody DeptExternalCodeSaveReqVO createReqVO) { + Long id = deptExternalCodeService.createDeptExternalCode(createReqVO); + return success(id); + } + + @PutMapping("/update") + @Operation(summary = "修改外部组织编码映射") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:update')") + public CommonResult update(@Valid @RequestBody DeptExternalCodeSaveReqVO updateReqVO) { + deptExternalCodeService.updateDeptExternalCode(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除外部组织编码映射") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:delete')") + public CommonResult delete(@RequestParam("id") Long id) { + deptExternalCodeService.deleteDeptExternalCode(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获取外部组织编码映射详情") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:query')") + public CommonResult get(@RequestParam("id") Long id) { + DeptExternalCodeDO entity = deptExternalCodeService.getDeptExternalCode(id); + DeptExternalCodeRespVO respVO = BeanUtils.toBean(entity, DeptExternalCodeRespVO.class); + fillDeptInfo(List.of(respVO)); + return success(respVO); + } + + @GetMapping("/page") + @Operation(summary = "分页查询外部组织编码映射") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:query')") + public CommonResult> page(@Valid DeptExternalCodePageReqVO reqVO) { + PageResult pageResult = deptExternalCodeService.getDeptExternalCodePage(reqVO); + PageResult result = BeanUtils.toBean(pageResult, DeptExternalCodeRespVO.class); + fillDeptInfo(result.getList()); + return success(result); + } + + @GetMapping("/list-by-dept") + @Operation(summary = "根据部门获取外部组织编码映射") + @Parameter(name = "deptId", description = "部门编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:dept-external-code:query')") + public CommonResult> listByDept(@RequestParam("deptId") Long deptId) { + List list = deptExternalCodeService.getDeptExternalCodeListByDeptId(deptId); + List respList = BeanUtils.toBean(list, DeptExternalCodeRespVO.class); + fillDeptInfo(respList); + return success(respList); + } + + private void fillDeptInfo(List list) { + if (list == null || list.isEmpty()) { + return; + } + Set deptIds = list.stream() + .map(DeptExternalCodeRespVO::getDeptId) + .collect(Collectors.toCollection(HashSet::new)); + if (deptIds == null || deptIds.isEmpty()) { + return; + } + Map deptMap = deptService.getDeptList(deptIds).stream() + .collect(Collectors.toMap(DeptDO::getId, dept -> dept, (left, right) -> left)); + list.forEach(item -> { + DeptDO dept = deptMap.get(item.getDeptId()); + if (dept != null) { + item.setDeptName(dept.getName()); + item.setDeptCode(dept.getCode()); + } + }); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeBaseVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeBaseVO.java new file mode 100644 index 00000000..31b2307b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeBaseVO.java @@ -0,0 +1,41 @@ +package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - 部门外部组织编码映射基础信息") +@Data +public class DeptExternalCodeBaseVO { + + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "部门不能为空") + private Long deptId; + + @Schema(description = "外部系统标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERP") + @NotBlank(message = "外部系统标识不能为空") + @Size(max = 64, message = "外部系统标识长度不能超过 64 个字符") + private String systemCode; + + @Schema(description = "外部组织编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "100200") + @NotBlank(message = "外部组织编码不能为空") + @Size(max = 128, message = "外部组织编码长度不能超过 128 个字符") + private String externalDeptCode; + + @Schema(description = "外部组织名称", example = "总部-华东区") + @Size(max = 255, message = "外部组织名称长度不能超过 255 个字符") + private String externalDeptName; + + @Schema(description = "状态", example = "0", requiredMode = Schema.RequiredMode.REQUIRED) + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; + + @Schema(description = "备注", example = "用于 ERP 同步") + @Size(max = 512, message = "备注长度不能超过 512 个字符") + private String remark; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodePageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodePageReqVO.java new file mode 100644 index 00000000..04d4d9ad --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodePageReqVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 部门外部组织编码映射分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptExternalCodePageReqVO extends PageParam { + + @Schema(description = "部门编号", example = "1024") + private Long deptId; + + @Schema(description = "外部系统标识", example = "ERP") + private String systemCode; + + @Schema(description = "外部组织编码", example = "100200") + private String externalDeptCode; + + @Schema(description = "状态", example = "0") + private Integer status; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeRespVO.java new file mode 100644 index 00000000..d77a25a8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeRespVO.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 部门外部组织编码映射 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptExternalCodeRespVO extends DeptExternalCodeBaseVO { + + @Schema(description = "映射编号", example = "1024") + private Long id; + + @Schema(description = "所属部门名称", example = "技术部") + private String deptName; + + @Schema(description = "所属部门编码", example = "DEPT_001") + private String deptCode; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "最后更新时间") + private LocalDateTime updateTime; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeSaveReqVO.java new file mode 100644 index 00000000..9bb7eb07 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/DeptExternalCodeSaveReqVO.java @@ -0,0 +1,15 @@ +package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 部门外部组织编码映射创建/修改 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptExternalCodeSaveReqVO extends DeptExternalCodeBaseVO { + + @Schema(description = "映射编号", example = "1024") + private Long id; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptExternalCodeDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptExternalCodeDO.java new file mode 100644 index 00000000..81fcef42 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptExternalCodeDO.java @@ -0,0 +1,61 @@ +package com.zt.plat.module.system.dal.dataobject.dept; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 部门外部组织编码映射 DO + * + * 维护本系统部门与外部系统组织编码的映射关系 + */ +@TableName("system_dept_external_code") +@KeySequence("system_dept_external_code_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptExternalCodeDO extends TenantBaseDO { + + /** + * 主键编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 本系统部门 ID + */ + private Long deptId; + + /** + * 外部系统标识 + */ + private String systemCode; + + /** + * 外部系统组织编码 + */ + private String externalDeptCode; + + /** + * 外部系统组织名称 + */ + private String externalDeptName; + + /** + * 映射状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java new file mode 100644 index 00000000..0a9dbd57 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptExternalCodeMapper.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.system.dal.mysql.dept; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodePageReqVO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface DeptExternalCodeMapper extends BaseMapperX { + + default PageResult selectPage(DeptExternalCodePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(DeptExternalCodeDO::getDeptId, reqVO.getDeptId()) + .eqIfPresent(DeptExternalCodeDO::getSystemCode, reqVO.getSystemCode()) + .likeIfPresent(DeptExternalCodeDO::getExternalDeptCode, reqVO.getExternalDeptCode()) + .eqIfPresent(DeptExternalCodeDO::getStatus, reqVO.getStatus()) + .orderByDesc(DeptExternalCodeDO::getId)); + } + + default DeptExternalCodeDO selectBySystemCodeAndDeptId(String systemCode, Long deptId) { + return selectOne(new LambdaQueryWrapperX() + .eq(DeptExternalCodeDO::getSystemCode, systemCode) + .eq(DeptExternalCodeDO::getDeptId, deptId)); + } + + default DeptExternalCodeDO selectBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) { + return selectOne(new LambdaQueryWrapperX() + .eq(DeptExternalCodeDO::getSystemCode, systemCode) + .eq(DeptExternalCodeDO::getExternalDeptCode, externalDeptCode)); + } + + default List selectListByDeptId(Long deptId) { + return selectList(DeptExternalCodeDO::getDeptId, deptId); + } + + default List selectListBySystemCode(String systemCode) { + return selectList(DeptExternalCodeDO::getSystemCode, systemCode); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java index 219d9985..e3b982fe 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java @@ -19,10 +19,28 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX() + .eq(OAuth2AccessTokenDO::getAccessToken, accessToken) + .eq(OAuth2AccessTokenDO::getClientId, clientId)); + } + + default OAuth2AccessTokenDO selectByUserIdAndClientId(Long userId, String clientId) { + return selectOne(new LambdaQueryWrapperX() + .eq(OAuth2AccessTokenDO::getUserId, userId) + .eq(OAuth2AccessTokenDO::getClientId, clientId)); + } + default List selectListByRefreshToken(String refreshToken) { return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken); } + default int deleteByUserIdAndClientId(Long userId, String clientId) { + return delete(new LambdaQueryWrapperX() + .eq(OAuth2AccessTokenDO::getUserId, userId) + .eq(OAuth2AccessTokenDO::getClientId, clientId)); + } + default PageResult selectPage(OAuth2AccessTokenPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId()) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthService.java index 47ba26df..0dcc8f06 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthService.java @@ -78,6 +78,14 @@ public interface AdminAuthService { */ AuthLoginRespVO refreshToken(String refreshToken); + /** + * 校验当前登录用户的密码是否正确 + * + * @param userId 用户编号 + * @param password 密码 + */ + void verifyPassword(Long userId, String password); + /** * 用户注册 * diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImpl.java index 8ed4d3f5..7bc3e06d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImpl.java @@ -277,6 +277,20 @@ public class AdminAuthServiceImpl implements AdminAuthService { return AuthConvert.INSTANCE.convert(accessTokenDO); } + @Override + public void verifyPassword(Long userId, String password) { + if (userId == null) { + throw exception(USER_NOT_EXISTS); + } + AdminUserDO user = userService.getUser(userId); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + if (!userService.isPasswordMatch(password, user.getPassword())) { + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + } + @Override public void logout(String token, Integer logType) { // 删除访问令牌 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java new file mode 100644 index 00000000..43a434be --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeService.java @@ -0,0 +1,62 @@ +package com.zt.plat.module.system.service.dept; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodePageReqVO; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; + +import java.util.List; + +/** + * 部门外部组织编码映射 Service 接口 + */ +public interface DeptExternalCodeService { + + /** + * 创建映射关系 + * + * @param createReqVO 创建请求 + * @return 新增记录编号 + */ + Long createDeptExternalCode(DeptExternalCodeSaveReqVO createReqVO); + + /** + * 更新映射关系 + * + * @param updateReqVO 更新请求 + */ + void updateDeptExternalCode(DeptExternalCodeSaveReqVO updateReqVO); + + /** + * 删除映射关系 + * + * @param id 记录编号 + */ + void deleteDeptExternalCode(Long id); + + /** + * 获取映射详情 + */ + DeptExternalCodeDO getDeptExternalCode(Long id); + + /** + * 分页查询映射 + */ + PageResult getDeptExternalCodePage(DeptExternalCodePageReqVO reqVO); + + /** + * 根据部门查询全部映射 + */ + List getDeptExternalCodeListByDeptId(Long deptId); + + /** + * 根据外部系统与外部组织编码查询映射 + */ + DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode); + + /** + * 根据外部系统与部门编号查询映射 + */ + DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java new file mode 100644 index 00000000..5ee5384f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptExternalCodeServiceImpl.java @@ -0,0 +1,151 @@ +package com.zt.plat.module.system.service.dept; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodePageReqVO; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.DeptExternalCodeSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; +import com.zt.plat.module.system.dal.dataobject.dept.DeptExternalCodeDO; +import com.zt.plat.module.system.dal.mysql.dept.DeptExternalCodeMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; + +/** + * 部门外部组织编码映射 Service 实现类 + */ +@Service +@Validated +public class DeptExternalCodeServiceImpl implements DeptExternalCodeService { + + @Resource + private DeptExternalCodeMapper deptExternalCodeMapper; + @Resource + private DeptService deptService; + + @Override + public Long createDeptExternalCode(DeptExternalCodeSaveReqVO createReqVO) { + normalizeRequest(createReqVO); + validateForCreateOrUpdate(null, createReqVO.getDeptId(), createReqVO.getSystemCode(), + createReqVO.getExternalDeptCode()); + + DeptExternalCodeDO entity = BeanUtils.toBean(createReqVO, DeptExternalCodeDO.class); + if (entity.getStatus() == null) { + entity.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + deptExternalCodeMapper.insert(entity); + return entity.getId(); + } + + @Override + public void updateDeptExternalCode(DeptExternalCodeSaveReqVO updateReqVO) { + normalizeRequest(updateReqVO); + DeptExternalCodeDO exists = validateExists(updateReqVO.getId()); + validateForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDeptId(), updateReqVO.getSystemCode(), + updateReqVO.getExternalDeptCode()); + + DeptExternalCodeDO updateObj = BeanUtils.toBean(updateReqVO, DeptExternalCodeDO.class); + // 保持原有的状态默认值逻辑 + if (updateObj.getStatus() == null) { + updateObj.setStatus(exists.getStatus() == null ? CommonStatusEnum.ENABLE.getStatus() : exists.getStatus()); + } + deptExternalCodeMapper.updateById(updateObj); + } + + @Override + public void deleteDeptExternalCode(Long id) { + validateExists(id); + deptExternalCodeMapper.deleteById(id); + } + + @Override + public DeptExternalCodeDO getDeptExternalCode(Long id) { + return deptExternalCodeMapper.selectById(id); + } + + @Override + public PageResult getDeptExternalCodePage(DeptExternalCodePageReqVO reqVO) { + return deptExternalCodeMapper.selectPage(reqVO); + } + + @Override + public List getDeptExternalCodeListByDeptId(Long deptId) { + return deptExternalCodeMapper.selectListByDeptId(deptId); + } + + @Override + public DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) { + if (StrUtil.hasEmpty(systemCode, externalDeptCode)) { + return null; + } + return deptExternalCodeMapper.selectBySystemCodeAndExternalCode(systemCode.trim(), externalDeptCode.trim()); + } + + @Override + public DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId) { + if (StrUtil.isBlank(systemCode) || deptId == null) { + return null; + } + return deptExternalCodeMapper.selectBySystemCodeAndDeptId(systemCode.trim(), deptId); + } + + private DeptExternalCodeDO validateExists(Long id) { + if (id == null) { + throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); + } + DeptExternalCodeDO entity = deptExternalCodeMapper.selectById(id); + if (entity == null) { + throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); + } + return entity; + } + + private void validateForCreateOrUpdate(Long id, Long deptId, String systemCode, String externalDeptCode) { + // 校验部门存在 + DeptDO dept = deptService.getDept(deptId); + if (dept == null) { + throw exception(DEPT_NOT_FOUND); + } + String normalizedSystemCode = StrUtil.blankToDefault(systemCode, null); + String normalizedExternalCode = StrUtil.blankToDefault(externalDeptCode, null); + + // 校验同一系统下部门唯一 + if (StrUtil.isNotBlank(normalizedSystemCode)) { + DeptExternalCodeDO sameDept = deptExternalCodeMapper + .selectBySystemCodeAndDeptId(normalizedSystemCode, deptId); + if (sameDept != null && (id == null || !sameDept.getId().equals(id))) { + throw exception(DEPT_EXTERNAL_RELATION_EXISTS, normalizedSystemCode); + } + } + // 校验同一系统下外部编码唯一 + if (StrUtil.isNotBlank(normalizedSystemCode) && StrUtil.isNotBlank(normalizedExternalCode)) { + DeptExternalCodeDO sameExternal = deptExternalCodeMapper + .selectBySystemCodeAndExternalCode(normalizedSystemCode, normalizedExternalCode); + if (sameExternal != null && (id == null || !sameExternal.getId().equals(id))) { + throw exception(DEPT_EXTERNAL_CODE_DUPLICATE, normalizedSystemCode, normalizedExternalCode); + } + } + } + + private void normalizeRequest(DeptExternalCodeSaveReqVO reqVO) { + if (reqVO == null) { + return; + } + if (StrUtil.isNotBlank(reqVO.getSystemCode())) { + reqVO.setSystemCode(reqVO.getSystemCode().trim()); + } + if (StrUtil.isNotBlank(reqVO.getExternalDeptCode())) { + reqVO.setExternalDeptCode(reqVO.getExternalDeptCode().trim()); + } + if (reqVO.getStatus() == null) { + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2Service.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2Service.java index 59e99350..de533eab 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2Service.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2Service.java @@ -36,6 +36,8 @@ public interface EbanOAuth2Service { private String email; private String mobile; private String deptName; + private String uid; + private String rawUserInfoJson; private EbanOAuth2ServiceImpl.EbanTokenInfo tokenInfo; // 添加Token信息 // 构造函数 @@ -65,6 +67,12 @@ public interface EbanOAuth2Service { public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } + public String getUid() { return uid; } + public void setUid(String uid) { this.uid = uid; } + + public String getRawUserInfoJson() { return rawUserInfoJson; } + public void setRawUserInfoJson(String rawUserInfoJson) { this.rawUserInfoJson = rawUserInfoJson; } + public EbanOAuth2ServiceImpl.EbanTokenInfo getTokenInfo() { return tokenInfo; } public void setTokenInfo(EbanOAuth2ServiceImpl.EbanTokenInfo tokenInfo) { this.tokenInfo = tokenInfo; } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java index 5adb6768..6a148f3b 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanOAuth2ServiceImpl.java @@ -6,8 +6,9 @@ import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.zt.plat.framework.common.enums.CommonStatusEnum; -import com.zt.plat.module.system.controller.admin.auth.vo.AuthOAuth2CallbackReqVO; +import com.zt.plat.framework.common.exception.ServiceException; import com.zt.plat.module.system.controller.admin.auth.vo.AuthLoginRespVO; +import com.zt.plat.module.system.controller.admin.auth.vo.AuthOAuth2CallbackReqVO; import com.zt.plat.module.system.convert.auth.AuthConvert; import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; @@ -16,11 +17,11 @@ import com.zt.plat.module.system.enums.logger.LoginResultEnum; import com.zt.plat.module.system.enums.oauth2.OAuth2ClientConstants; import com.zt.plat.module.system.service.logger.LoginLogService; import com.zt.plat.module.system.service.user.AdminUserService; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.HashMap; import java.util.Map; @@ -36,6 +37,8 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; @Service @Slf4j public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { + + private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"; @Resource private AdminUserService userService; @@ -69,89 +72,87 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { log.info("处理E办OAuth2回调: code={}, state={}", reqVO.getCode(), reqVO.getState()); try { - // 1. 通过授权码获取用户信息 EbanUserInfo userInfo = getUserInfo(reqVO.getCode(), reqVO.getState()); - if (userInfo == null || StrUtil.isBlank(userInfo.getUsername())) { - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + if (userInfo == null) { + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } - - // 2. 根据用户名查找系统用户 - AdminUserDO user = userService.getUserByUsername(userInfo.getUsername()); + + String uid = userInfo.getUid(); + if (StrUtil.isBlank(uid)) { + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + Long userId = parseUid(uid); + AdminUserDO user = userService.getUser(userId); if (user == null) { - // 用户不存在,记录登录失败日志 - createLoginLog(null, userInfo.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS); - throw exception(USER_NOT_EXISTS); + createLoginLog(null, uid, LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC); } - - // 3. 校验用户状态 + if (CommonStatusEnum.isDisable(user.getStatus())) { - createLoginLog(user.getId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.USER_DISABLED); + createLoginLog(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL, LoginResultEnum.USER_DISABLED); throw exception(AUTH_LOGIN_USER_DISABLED); } - - // 4. 保存E办token到现有OAuth2表中 + try { EbanTokenInfo tokenInfo = userInfo.getTokenInfo(); - if (tokenInfo != null) { - // 将用户信息转换为JSON字符串 - JSONObject ebanUserInfoJson = new JSONObject(); - ebanUserInfoJson.put("username", userInfo.getUsername()); - ebanUserInfoJson.put("realName", userInfo.getRealName()); - ebanUserInfoJson.put("email", userInfo.getEmail()); - ebanUserInfoJson.put("mobile", userInfo.getMobile()); - ebanUserInfoJson.put("deptName", userInfo.getDeptName()); - + if (tokenInfo != null && StrUtil.isNotBlank(tokenInfo.getAccessToken())) { + String userInfoJson = StrUtil.blankToDefault(userInfo.getRawUserInfoJson(), buildBasicUserInfoJson(userInfo)); + Long tenantId = user.getTenantId() != null ? user.getTenantId() : 0L; ebanTokenService.createEbanToken( - user.getId(), // 使用用户ID - tokenInfo.getAccessToken(), - tokenInfo.getRefreshToken(), - tokenInfo.getExpiresIn(), - tokenInfo.getUid(), - ebanUserInfoJson.toString() + user.getId(), + tenantId, + tokenInfo.getAccessToken(), + tokenInfo.getRefreshToken(), + tokenInfo.getExpiresIn(), + uid, + userInfoJson ); - log.info("成功保存E办token到OAuth2表,用户ID: {}, 用户名: {}, uid: {}", - user.getId(), userInfo.getUsername(), tokenInfo.getUid()); + log.info("成功保存E办token,userId={}, uid={}", user.getId(), uid); } } catch (Exception e) { - log.error("保存E办token失败,用户: " + userInfo.getUsername(), e); - // 保存token失败不影响登录流程,继续执行 + log.error("保存E办token失败,userId={}, uid={}", user.getId(), uid, e); } - - // 5. 创建Token令牌,记录登录日志 - return createTokenAfterLoginSuccess(user.getId(), userInfo.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); - + + return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); + + } catch (ServiceException e) { + throw e; } catch (Exception e) { log.error("E办OAuth2回调处理失败", e); - if (e.getMessage().contains("USER_NOT_EXISTS") || e.getMessage().contains("AUTH_LOGIN_USER_DISABLED")) { - throw e; // 重新抛出业务异常 - } - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + throw exception(AUTH_OAUTH2_CALLBACK_ERROR, e.getMessage()); } } @Override public EbanUserInfo getUserInfo(String code, String state) { - try { - // 1. 使用授权码换取access_token - EbanTokenInfo tokenInfo = exchangeAccessToken(code, state); - if (tokenInfo == null || StrUtil.isBlank(tokenInfo.getAccessToken())) { - log.error("获取access_token失败"); - return null; - } - - // 2. 使用access_token获取用户信息 - EbanUserInfo userInfo = fetchUserInfo(tokenInfo.getAccessToken()); - if (userInfo != null) { - // 将Token信息保存到用户信息中,以便后续使用 - userInfo.setTokenInfo(tokenInfo); - } - - return userInfo; - - } catch (Exception e) { - log.error("获取E办用户信息失败", e); - return null; + EbanTokenInfo tokenInfo = exchangeAccessToken(code, state); + EbanUserInfo userInfo = fetchUserInfo(tokenInfo.getAccessToken()); + userInfo.setTokenInfo(tokenInfo); + if (StrUtil.isBlank(userInfo.getUid()) && StrUtil.isNotBlank(tokenInfo.getUid())) { + userInfo.setUid(tokenInfo.getUid()); } + return userInfo; + } + + private Long parseUid(String uid) { + try { + return Long.parseLong(uid); + } catch (NumberFormatException ex) { + log.warn("E办uid无法解析: {}", uid); + throw exception(AUTH_LOGIN_EBAN_USER_NOT_SYNC); + } + } + + private String buildBasicUserInfoJson(EbanUserInfo userInfo) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("uid", userInfo.getUid()); + jsonObject.put("loginName", userInfo.getUsername()); + jsonObject.put("realName", userInfo.getRealName()); + jsonObject.put("email", userInfo.getEmail()); + jsonObject.put("mobile", userInfo.getMobile()); + jsonObject.put("deptName", userInfo.getDeptName()); + return jsonObject.toString(); } /** @@ -195,121 +196,116 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service { * 使用授权码换取access_token */ private EbanTokenInfo exchangeAccessToken(String code, String state) { - try { - // 根据e办API规范构建请求参数 - Map params = new HashMap<>(); - params.put("client_id", clientId); - params.put("client_secret", clientSecret); - params.put("code", code); - params.put("grant_type", "authorization_code"); - - log.info("请求e办获取token,参数: client_id={}, code={}", clientId, code); - - HttpResponse response = HttpRequest.post(tokenUrl) - .form(params) - .timeout(10000) - .execute(); - - if (!response.isOk()) { - log.error("获取access_token失败,HTTP状态码: {}, 响应: {}", response.getStatus(), response.body()); - return null; - } - - JSONObject jsonResponse = JSONUtil.parseObj(response.body()); - - // 检查是否有错误码 - if (jsonResponse.containsKey("errcode")) { - log.error("获取access_token失败,错误码: {}, 错误信息: {}", - jsonResponse.getStr("errcode"), jsonResponse.getStr("msg")); - return null; - } - - String accessToken = jsonResponse.getStr("access_token"); - String refreshToken = jsonResponse.getStr("refresh_token"); - Integer expiresIn = jsonResponse.getInt("expires_in"); - String uid = jsonResponse.getStr("uid"); - String createDate = jsonResponse.getStr("createDate"); - - log.info("成功获取access_token,uid: {}, expires_in: {}", uid, expiresIn); - - return new EbanTokenInfo(accessToken, refreshToken, expiresIn, uid, createDate); - - } catch (Exception e) { - log.error("调用e办token接口异常", e); - return null; + Map params = new HashMap<>(); + params.put("client_id", clientId); + params.put("client_secret", clientSecret); + params.put("code", code); + params.put("grant_type", GRANT_TYPE_AUTHORIZATION_CODE); + + log.info("请求e办获取token,client_id={}, state={}", clientId, state); + + HttpResponse response = HttpRequest.post(tokenUrl) + .form(params) + .timeout(10000) + .execute(); + + if (!response.isOk()) { + log.error("获取access_token失败,HTTP状态码: {}, 响应: {}", response.getStatus(), response.body()); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } + + JSONObject jsonResponse = parseResponseBody(response.body(), "获取access_token"); + + if (jsonResponse.containsKey("errcode")) { + log.error("获取access_token失败,错误码: {}, 错误信息: {}", jsonResponse.getStr("errcode"), jsonResponse.getStr("msg")); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + String accessToken = jsonResponse.getStr("access_token"); + if (StrUtil.isBlank(accessToken)) { + log.error("获取access_token失败,响应缺少access_token字段: {}", jsonResponse); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + String refreshToken = jsonResponse.getStr("refresh_token"); + Integer expiresIn = jsonResponse.getInt("expires_in"); + String uid = jsonResponse.getStr("uid"); + String createDate = jsonResponse.getStr("createDate"); + + log.info("成功获取E办access_token,uid={}, expires_in={}", uid, expiresIn); + + return new EbanTokenInfo(accessToken, refreshToken, expiresIn, uid, createDate); } /** * 使用access_token获取用户信息 */ private EbanUserInfo fetchUserInfo(String accessToken) { - try { - // 根据e办API规范构建请求参数 - Map params = new HashMap<>(); - params.put("client_id", clientId); - params.put("access_token", accessToken); - - log.info("请求e办获取用户信息,client_id: {}", clientId); - - HttpResponse response = HttpRequest.get(userInfoUrl) - .form(params) - .timeout(10000) - .execute(); - - if (!response.isOk()) { - log.error("获取用户信息失败,HTTP状态码: {}, 响应: {}", response.getStatus(), response.body()); - return null; - } - - JSONObject userJson = JSONUtil.parseObj(response.body()); - - // 检查是否有错误码 - if (userJson.containsKey("errcode")) { - log.error("获取用户信息失败,错误码: {}, 错误信息: {}", - userJson.getStr("errcode"), userJson.getStr("msg")); - return null; - } - - // 解析用户信息(根据e办系统的实际返回格式调整) - EbanUserInfo userInfo = new EbanUserInfo(); - - // 根据API文档,主要字段是loginName和spRoleList - String loginName = userJson.getStr("loginName"); - if (StrUtil.isBlank(loginName)) { - log.error("用户信息中缺少loginName字段"); - return null; - } - - userInfo.setUsername(loginName); // 使用loginName作为用户名 - - // 如果有spRoleList,可以取第一个作为真实姓名或其他用途 - if (userJson.containsKey("spRoleList")) { - Object spRoleListObj = userJson.get("spRoleList"); - if (spRoleListObj instanceof java.util.List) { - @SuppressWarnings("unchecked") - java.util.List spRoleList = (java.util.List) spRoleListObj; - if (!spRoleList.isEmpty()) { - userInfo.setRealName(spRoleList.get(0)); + Map params = new HashMap<>(); + params.put("client_id", clientId); + params.put("access_token", accessToken); + + log.info("请求e办获取用户信息,client_id={}", clientId); + + HttpResponse response = HttpRequest.get(userInfoUrl) + .form(params) + .timeout(10000) + .execute(); + + if (!response.isOk()) { + log.error("获取用户信息失败,HTTP状态码: {}, 响应: {}", response.getStatus(), response.body()); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + JSONObject userJson = parseResponseBody(response.body(), "获取用户信息"); + + if (userJson.containsKey("errcode")) { + log.error("获取用户信息失败,错误码: {}, 错误信息: {}", userJson.getStr("errcode"), userJson.getStr("msg")); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + String loginName = userJson.getStr("loginName"); + if (StrUtil.isBlank(loginName)) { + log.error("获取用户信息失败,响应缺少loginName字段: {}", userJson); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + + EbanUserInfo userInfo = new EbanUserInfo(); + userInfo.setUsername(loginName); + userInfo.setRealName(userJson.getStr("realName")); + userInfo.setEmail(userJson.getStr("email")); + userInfo.setMobile(userJson.getStr("mobile")); + userInfo.setDeptName(userJson.getStr("deptName")); + userInfo.setUid(userJson.getStr("uid")); + userInfo.setRawUserInfoJson(userJson.toString()); + + if (StrUtil.isBlank(userInfo.getRealName()) && userJson.containsKey("spRoleList")) { + Object spRoleListObj = userJson.get("spRoleList"); + if (spRoleListObj instanceof java.util.List) { + java.util.List spRoleList = (java.util.List) spRoleListObj; + if (!spRoleList.isEmpty()) { + Object first = spRoleList.get(0); + if (first != null) { + userInfo.setRealName(String.valueOf(first)); } } } - - // 其他可能的字段(根据实际e办返回的字段调整) - userInfo.setEmail(userJson.getStr("email")); - userInfo.setMobile(userJson.getStr("mobile")); - userInfo.setDeptName(userJson.getStr("deptName")); - - log.info("成功获取用户信息: username={}, realName={}", userInfo.getUsername(), userInfo.getRealName()); - - return userInfo; - - } catch (Exception e) { - log.error("调用e办用户信息接口异常", e); - return null; } + + log.info("成功获取E办用户信息: uid={}, loginName={}", userInfo.getUid(), userInfo.getUsername()); + + return userInfo; } + private JSONObject parseResponseBody(String body, String action) { + try { + return JSONUtil.parseObj(body); + } catch (Exception ex) { + log.error("{}失败,响应内容无法解析为JSON: {}", action, body, ex); + throw exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); + } + } + /** * 创建登录日志 */ diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenService.java index 9e1c77a8..cdb3f429 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenService.java @@ -1,7 +1,6 @@ package com.zt.plat.module.system.service.oauth2; import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; -import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; /** * E办Token管理服务接口(基于现有OAuth2 Token体系) @@ -14,6 +13,7 @@ public interface EbanTokenService { * 创建E办Token信息到现有OAuth2表中 * * @param userId 系统用户ID + * @param tenantId 租户编号 * @param accessToken E办访问令牌 * @param refreshToken E办刷新令牌 * @param expiresIn 过期时间(秒) @@ -21,7 +21,7 @@ public interface EbanTokenService { * @param userInfo E办用户信息(JSON格式) * @return OAuth2AccessTokenDO */ - OAuth2AccessTokenDO createEbanToken(Long userId, String accessToken, String refreshToken, + OAuth2AccessTokenDO createEbanToken(Long userId, Long tenantId, String accessToken, String refreshToken, Integer expiresIn, String uid, String userInfo); /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenServiceImpl.java index a77dbc03..cd9b3fc7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/EbanTokenServiceImpl.java @@ -1,22 +1,29 @@ package com.zt.plat.module.system.service.oauth2; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.zt.plat.framework.common.enums.UserTypeEnum; +import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; -import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; +import com.zt.plat.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_EBAN_TOKEN_INVALID; + /** * E办Token管理服务实现类(基于现有OAuth2 Token体系) * @@ -27,7 +34,7 @@ import java.util.Map; public class EbanTokenServiceImpl implements EbanTokenService { @Resource - private OAuth2TokenService oauth2TokenService; + private OAuth2AccessTokenMapper oauth2AccessTokenMapper; @Value("${eban.oauth2.auth-server.client-id:tyszhjyglxt}") private String clientId; @@ -42,56 +49,148 @@ public class EbanTokenServiceImpl implements EbanTokenService { private String checkTokenUrl; private static final String EBAN_CLIENT_ID = "eban-oauth2-client"; - private static final String EBAN_SCOPES = "user:read"; + private static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; @Override - public OAuth2AccessTokenDO createEbanToken(Long userId, String accessToken, String refreshToken, + public OAuth2AccessTokenDO createEbanToken(Long userId, Long tenantId, String accessToken, String refreshToken, Integer expiresIn, String uid, String userInfo) { - try { - // 使用现有的OAuth2TokenService创建token - // 由于原方法签名不匹配,我们先简单实现 - OAuth2AccessTokenDO token = oauth2TokenService.createAccessToken( - userId, - UserTypeEnum.ADMIN.getValue(), - EBAN_CLIENT_ID, - java.util.Arrays.asList(EBAN_SCOPES) - ); - - log.info("成功创建E办Token: userId={}, uid={}", userId, uid); - return token; - - } catch (Exception e) { - log.error("创建E办Token失败: userId=" + userId + ", uid=" + uid, e); - throw new RuntimeException("创建E办Token失败", e); + if (StrUtil.isBlank(accessToken)) { + throw ServiceExceptionUtil.exception(AUTH_LOGIN_EBAN_TOKEN_INVALID); } + + LocalDateTime expiresTime = calculateExpiresTime(expiresIn); + Map userInfoMap = MapUtil.newHashMap(2, false); + if (StrUtil.isNotBlank(uid)) { + userInfoMap.put("uid", uid); + } + if (StrUtil.isNotBlank(userInfo)) { + userInfoMap.put("rawUserInfo", userInfo); + } + + OAuth2AccessTokenDO tokenDO = oauth2AccessTokenMapper.selectByUserIdAndClientId(userId, EBAN_CLIENT_ID); + if (tokenDO == null) { + tokenDO = new OAuth2AccessTokenDO(); + tokenDO.setUserId(userId); + } + tokenDO.setClientId(EBAN_CLIENT_ID); + tokenDO.setUserType(UserTypeEnum.ADMIN.getValue()); + tokenDO.setTenantId(tenantId != null ? tenantId : 0L); + tokenDO.setAccessToken(accessToken); + tokenDO.setRefreshToken(refreshToken); + tokenDO.setExpiresTime(expiresTime); + tokenDO.setUserInfo(userInfoMap); + tokenDO.setScopes(Collections.singletonList("eban")); + + if (tokenDO.getId() == null) { + oauth2AccessTokenMapper.insert(tokenDO); + } else { + oauth2AccessTokenMapper.updateById(tokenDO); + } + + log.info("保存E办Token成功,userId={}, uid={}, expiresTime={}", userId, uid, expiresTime); + return tokenDO; } @Override public OAuth2AccessTokenDO getEbanTokenByUserId(Long userId) { - // 暂时返回null,需要根据实际的OAuth2TokenService方法实现 - return null; + return oauth2AccessTokenMapper.selectByUserIdAndClientId(userId, EBAN_CLIENT_ID); } @Override public OAuth2AccessTokenDO getEbanTokenByAccessToken(String accessToken) { - return oauth2TokenService.getAccessToken(accessToken); + return oauth2AccessTokenMapper.selectByAccessTokenAndClientId(accessToken, EBAN_CLIENT_ID); } @Override public boolean refreshEbanToken(Long userId) { - // 暂时简单实现 - return false; + OAuth2AccessTokenDO tokenDO = getEbanTokenByUserId(userId); + if (tokenDO == null || StrUtil.isBlank(tokenDO.getRefreshToken())) { + return false; + } + + Map params = new HashMap<>(); + params.put("client_id", clientId); + params.put("client_secret", clientSecret); + params.put("refresh_token", tokenDO.getRefreshToken()); + params.put("grant_type", GRANT_TYPE_REFRESH_TOKEN); + + HttpResponse response = HttpRequest.post(refreshTokenUrl) + .form(params) + .timeout(10000) + .execute(); + if (!response.isOk()) { + log.error("刷新E办Token失败,userId={},响应={}", userId, response.body()); + return false; + } + + JSONObject json = parseJson(response.body()); + if (json.containsKey("errcode")) { + log.error("刷新E办Token失败,userId={},错误码={},信息={}", userId, json.getStr("errcode"), json.getStr("msg")); + return false; + } + + String newAccessToken = json.getStr("access_token"); + if (StrUtil.isBlank(newAccessToken)) { + log.error("刷新E办Token失败,响应缺少access_token字段: {}", json); + return false; + } + + tokenDO.setAccessToken(newAccessToken); + tokenDO.setRefreshToken(StrUtil.blankToDefault(json.getStr("refresh_token"), tokenDO.getRefreshToken())); + tokenDO.setExpiresTime(calculateExpiresTime(json.getInt("expires_in"))); + oauth2AccessTokenMapper.updateById(tokenDO); + log.info("刷新E办Token成功,userId={}", userId); + return true; } @Override public void deleteEbanToken(Long userId) { - // 暂时简单实现 - log.info("删除E办Token: userId={}", userId); + if (oauth2AccessTokenMapper.deleteByUserIdAndClientId(userId, EBAN_CLIENT_ID) > 0) { + log.info("已删除用户{}的E办Token", userId); + } } @Override public boolean isEbanTokenValid(Long userId) { - // 暂时简单实现 - return false; + OAuth2AccessTokenDO tokenDO = getEbanTokenByUserId(userId); + if (tokenDO == null || StrUtil.isBlank(tokenDO.getAccessToken())) { + return false; + } + + if (tokenDO.getExpiresTime() != null && LocalDateTime.now().isAfter(tokenDO.getExpiresTime())) { + return false; + } + + Map params = Collections.singletonMap("access_token", tokenDO.getAccessToken()); + HttpResponse response = HttpRequest.get(checkTokenUrl) + .form(params) + .timeout(10000) + .execute(); + if (!response.isOk()) { + log.error("校验E办Token有效性失败,userId={},响应={}", userId, response.body()); + return false; + } + + JSONObject json = parseJson(response.body()); + if (json.containsKey("errcode")) { + log.warn("校验E办Token返回错误,userId={},错误码={},信息={}", userId, json.getStr("errcode"), json.getStr("msg")); + return false; + } + + return StrUtil.equalsIgnoreCase(json.getStr("result"), "true"); + } + + private LocalDateTime calculateExpiresTime(Integer expiresIn) { + int seconds = expiresIn != null && expiresIn > 0 ? expiresIn : 3600; + return LocalDateTimeUtil.offset(LocalDateTime.now(), seconds, ChronoUnit.SECONDS); + } + + private JSONObject parseJson(String body) { + try { + return JSONUtil.parseObj(body); + } catch (Exception ex) { + log.error("解析E办响应失败: {}", body, ex); + return new JSONObject(); + } } } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml index 8f994a92..ea2d092f 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml @@ -211,7 +211,7 @@ eban: auth-server: base-url: http://10.2.137.42/idp/oauth2 client-id: tyszhjyglxt - client-secret: your_client_secret_here # 需要从e办系统获取 + client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从e办系统获取 callback-uri: http://172.16.46.63:30080/system/oauth2/callback # 用户信息获取配置 diff --git a/zt-server/src/main/resources/application.yaml b/zt-server/src/main/resources/application.yaml index e83a106a..650f7b49 100644 --- a/zt-server/src/main/resources/application.yaml +++ b/zt-server/src/main/resources/application.yaml @@ -352,7 +352,7 @@ eban: # E办OAuth2登录配置 authorize-url: http://10.2.137.42/idp/oauth2/authorize client-id: tyszhjyglxt - client-secret: your_client_secret_here # 需要从e办系统获取 + client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从e办系统获取 redirect-uri: http://172.16.46.63:30080/system/oauth2/callback response-type: code