1.规范增量 SQL 文件命名
2.新增数据总线模块(未完成) 3.新增规则模块(未完成) 4.新增组织编码与外部系统组织编码映射关系表 5.补全 e 办单点登录回调逻辑
This commit is contained in:
31
sql/dm/统一外部网关菜单_20251010.sql
Normal file
31
sql/dm/统一外部网关菜单_20251010.sql
Normal file
@@ -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
|
||||||
241
sql/dm/统一对外网关_20251010.sql
Normal file
241
sql/dm/统一对外网关_20251010.sql
Normal file
@@ -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 '逻辑删除标记';
|
||||||
228
sql/dm/规则引擎核心表结构_20251014.sql
Normal file
228
sql/dm/规则引擎核心表结构_20251014.sql
Normal file
@@ -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);
|
||||||
27
sql/dm/规则引擎菜单初始化_20251014.sql
Normal file
27
sql/dm/规则引擎菜单初始化_20251014.sql
Normal file
@@ -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');
|
||||||
60
sql/dm/部门外部组织编码映射初始化_DM8.sql
Normal file
60
sql/dm/部门外部组织编码映射初始化_DM8.sql
Normal file
@@ -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'
|
||||||
|
);
|
||||||
35
sql/dm/部门外部组织编码映射菜单权限_DM8.sql
Normal file
35
sql/dm/部门外部组织编码映射菜单权限_DM8.sql
Normal file
@@ -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
|
||||||
65
sql/mysql/databus_sample_data.sql
Normal file
65
sql/mysql/databus_sample_data.sql
Normal file
@@ -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;
|
||||||
@@ -837,7 +837,7 @@ def main():
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
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
|
convertor = None
|
||||||
if args.type == "postgres":
|
if args.type == "postgres":
|
||||||
convertor = PostgreSQLConvertor(sql_file)
|
convertor = PostgreSQLConvertor(sql_file)
|
||||||
|
|||||||
@@ -186,6 +186,13 @@ spring:
|
|||||||
- Path=/admin-api/crm/**
|
- Path=/admin-api/crm/**
|
||||||
filters:
|
filters:
|
||||||
- RewritePath=/admin-api/crm/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
- 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 服务
|
## ai-server 服务
|
||||||
- id: ai-admin-api # 路由的编号
|
- id: ai-admin-api # 路由的编号
|
||||||
uri: grayLb://ai-server
|
uri: grayLb://ai-server
|
||||||
@@ -207,6 +214,13 @@ spring:
|
|||||||
- Path=/admin-api/template/**
|
- Path=/admin-api/template/**
|
||||||
filters:
|
filters:
|
||||||
- RewritePath=/admin-api/template/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
- 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:
|
x-forwarded:
|
||||||
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
||||||
|
|
||||||
@@ -258,6 +272,9 @@ knife4j:
|
|||||||
- name: crm-server
|
- name: crm-server
|
||||||
service-name: crm-server
|
service-name: crm-server
|
||||||
url: /admin-api/crm/v3/api-docs
|
url: /admin-api/crm/v3/api-docs
|
||||||
|
- name: rule-server
|
||||||
|
service-name: rule-server
|
||||||
|
url: /admin-api/rule/v3/api-docs
|
||||||
- name: ai-server
|
- name: ai-server
|
||||||
service-name: ai-server
|
service-name: ai-server
|
||||||
url: /admin-api/ai/v3/api-docs
|
url: /admin-api/ai/v3/api-docs
|
||||||
@@ -267,6 +284,9 @@ knife4j:
|
|||||||
- name: template-server
|
- name: template-server
|
||||||
service-name: template-server
|
service-name: template-server
|
||||||
url: /admin-api/template/v3/api-docs
|
url: /admin-api/template/v3/api-docs
|
||||||
|
- name: databus-server
|
||||||
|
service-name: databus-server
|
||||||
|
url: /admin-api/databus/v3/api-docs
|
||||||
|
|
||||||
--- #################### 芋道相关配置 ####################
|
--- #################### 芋道相关配置 ####################
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,63 @@
|
|||||||
<artifactId>zt-spring-boot-starter-biz-business</artifactId>
|
<artifactId>zt-spring-boot-starter-biz-business</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Integration & Flow Orchestration -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-http</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-scripting</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.retry</groupId>
|
||||||
|
<artifactId>spring-retry</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Reactive HTTP client for internal REST orchestration -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Expression evaluation & caching utilities -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ibm.jsonata4java</groupId>
|
||||||
|
<artifactId>JSONata4Java</artifactId>
|
||||||
|
<version>2.5.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mvel</groupId>
|
||||||
|
<artifactId>mvel2</artifactId>
|
||||||
|
<version>2.5.2.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<version>4.12.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -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<String> hello() {
|
|
||||||
return success("Hello, Databus!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<PageResult<ApiDefinitionSummaryRespVO>> getDefinitionPage(@Valid ApiDefinitionPageReqVO reqVO) {
|
||||||
|
PageResult<ApiDefinitionDO> pageResult = apiDefinitionService.getPage(reqVO);
|
||||||
|
return success(ApiDefinitionConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取 API 定义详情")
|
||||||
|
public CommonResult<ApiDefinitionDetailRespVO> 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<Long> 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<Boolean> 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<Boolean> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiGatewayResponse> 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<List<ApiDefinitionDetailRespVO>> listDefinitions() {
|
||||||
|
List<ApiDefinitionDetailRespVO> 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<String, Object> 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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("errorMessage", message);
|
||||||
|
body.put("exception", ex.getClass().getSimpleName());
|
||||||
|
context.setResponseBody(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<PageResult<ApiPolicyRespVO>> getAuthPolicyPage(@Valid ApiPolicyPageReqVO reqVO) {
|
||||||
|
PageResult<ApiPolicyAuthDO> pageResult = authService.getPage(reqVO);
|
||||||
|
return success(ApiPolicyAuthConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "查询认证策略详情")
|
||||||
|
public CommonResult<ApiPolicyRespVO> 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<List<ApiPolicySimpleRespVO>> getAuthPolicySimpleList() {
|
||||||
|
List<ApiPolicyAuthDO> list = authService.getSimpleList();
|
||||||
|
return success(ApiPolicyAuthConvert.INSTANCE.convertSimpleList(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "创建认证策略")
|
||||||
|
public CommonResult<Long> createAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||||
|
Long id = authService.create(reqVO);
|
||||||
|
return success(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "更新认证策略")
|
||||||
|
public CommonResult<Boolean> updateAuthPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||||
|
authService.update(reqVO);
|
||||||
|
return success(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除认证策略")
|
||||||
|
public CommonResult<Boolean> deleteAuthPolicy(@PathVariable("id") Long id) {
|
||||||
|
authService.delete(id);
|
||||||
|
return success(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<PageResult<ApiPolicyRespVO>> getRateLimitPolicyPage(@Valid ApiPolicyPageReqVO reqVO) {
|
||||||
|
PageResult<ApiPolicyRateLimitDO> pageResult = rateLimitService.getPage(reqVO);
|
||||||
|
return success(ApiPolicyRateLimitConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "查询限流策略详情")
|
||||||
|
public CommonResult<ApiPolicyRespVO> 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<List<ApiPolicySimpleRespVO>> getRateLimitPolicySimpleList() {
|
||||||
|
List<ApiPolicyRateLimitDO> list = rateLimitService.getSimpleList();
|
||||||
|
return success(ApiPolicyRateLimitConvert.INSTANCE.convertSimpleList(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "创建限流策略")
|
||||||
|
public CommonResult<Long> createRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||||
|
Long id = rateLimitService.create(reqVO);
|
||||||
|
return success(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
@Operation(summary = "更新限流策略")
|
||||||
|
public CommonResult<Boolean> updateRateLimitPolicy(@Valid @RequestBody ApiPolicySaveReqVO reqVO) {
|
||||||
|
rateLimitService.update(reqVO);
|
||||||
|
return success(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除限流策略")
|
||||||
|
public CommonResult<Boolean> deleteRateLimitPolicy(@PathVariable("id") Long id) {
|
||||||
|
rateLimitService.delete(id);
|
||||||
|
return success(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionSummaryRespVO> convertList(List<ApiDefinitionDO> list);
|
||||||
|
|
||||||
|
default PageResult<ApiDefinitionSummaryRespVO> convertPage(PageResult<ApiDefinitionDO> page) {
|
||||||
|
if (page == null) {
|
||||||
|
return PageResult.empty();
|
||||||
|
}
|
||||||
|
PageResult<ApiDefinitionSummaryRespVO> result = new PageResult<>();
|
||||||
|
List<ApiDefinitionSummaryRespVO> 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<ApiDefinitionStepRespVO> convertSteps(List<ApiStepDefinition> 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<ApiDefinitionTransformRespVO> convertTransforms(Long apiId, Collection<ApiTransformDefinition> 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<ApiDefinitionTransformRespVO> convertStepTransforms(Long apiId, Long stepId, List<ApiTransformDefinition> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiPolicyRespVO> convertList(List<ApiPolicyAuthDO> list);
|
||||||
|
|
||||||
|
PageResult<ApiPolicyRespVO> convertPage(PageResult<ApiPolicyAuthDO> page);
|
||||||
|
|
||||||
|
List<ApiPolicySimpleRespVO> convertSimpleList(List<ApiPolicyAuthDO> list);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiPolicyRespVO> convertList(List<ApiPolicyRateLimitDO> list);
|
||||||
|
|
||||||
|
PageResult<ApiPolicyRespVO> convertPage(PageResult<ApiPolicyRateLimitDO> page);
|
||||||
|
|
||||||
|
List<ApiPolicySimpleRespVO> convertSimpleList(List<ApiPolicyRateLimitDO> list);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, String> headers = new HashMap<>();
|
||||||
|
|
||||||
|
@Schema(description = "请求参数,可选")
|
||||||
|
private Map<String, Object> queryParams = new HashMap<>();
|
||||||
|
|
||||||
|
@Schema(description = "请求体")
|
||||||
|
private Object payload;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionTransformRespVO> apiLevelTransforms = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "步骤列表")
|
||||||
|
private List<ApiDefinitionStepRespVO> steps = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "发布信息")
|
||||||
|
private ApiDefinitionPublicationRespVO publication;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionTransformSaveReqVO> apiLevelTransforms = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "步骤列表")
|
||||||
|
@NotEmpty(message = "编排步骤不能为空")
|
||||||
|
@Valid
|
||||||
|
private List<ApiDefinitionStepSaveReqVO> steps = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionTransformRespVO> transforms = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionTransformSaveReqVO> transforms = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionDO> {
|
||||||
|
|
||||||
|
default Optional<ApiDefinitionDO> selectByCodeAndVersion(String apiCode, String version) {
|
||||||
|
return Optional.ofNullable(selectOne(ApiDefinitionDO::getApiCode, apiCode,
|
||||||
|
ApiDefinitionDO::getVersion, version,
|
||||||
|
ApiDefinitionDO::getDeleted, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<ApiDefinitionDO> selectActiveDefinitions(List<Integer> statusList) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiDefinitionDO>()
|
||||||
|
.inIfPresent(ApiDefinitionDO::getStatus, statusList)
|
||||||
|
.eq(ApiDefinitionDO::getDeleted, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
default PageResult<ApiDefinitionDO> selectPage(ApiDefinitionPageReqVO reqVO) {
|
||||||
|
LambdaQueryWrapperX<ApiDefinitionDO> 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<ApiDefinitionDO>()
|
||||||
|
.eq(ApiDefinitionDO::getAuthPolicyId, policyId)
|
||||||
|
.eq(ApiDefinitionDO::getDeleted, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
default Long selectCountByRateLimitPolicyId(Long policyId) {
|
||||||
|
if (policyId == null) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
return selectCount(new LambdaQueryWrapperX<ApiDefinitionDO>()
|
||||||
|
.eq(ApiDefinitionDO::getRateLimitId, policyId)
|
||||||
|
.eq(ApiDefinitionDO::getDeleted, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiFlowPublishDO> {
|
||||||
|
|
||||||
|
default Optional<ApiFlowPublishDO> selectActiveByApiId(Long apiId) {
|
||||||
|
return Optional.ofNullable(selectOne(new LambdaQueryWrapperX<ApiFlowPublishDO>()
|
||||||
|
.eq(ApiFlowPublishDO::getApiId, apiId)
|
||||||
|
.eq(ApiFlowPublishDO::getActive, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiPolicyAuthDO> {
|
||||||
|
|
||||||
|
default PageResult<ApiPolicyAuthDO> selectPage(ApiPolicyPageReqVO reqVO) {
|
||||||
|
LambdaQueryWrapperX<ApiPolicyAuthDO> 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<ApiPolicyAuthDO> selectSimpleList() {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiPolicyAuthDO>()
|
||||||
|
.eq(ApiPolicyAuthDO::getDeleted, false)
|
||||||
|
.orderByDesc(ApiPolicyAuthDO::getUpdateTime)
|
||||||
|
.orderByDesc(ApiPolicyAuthDO::getId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiPolicyRateLimitDO> {
|
||||||
|
|
||||||
|
default PageResult<ApiPolicyRateLimitDO> selectPage(ApiPolicyPageReqVO reqVO) {
|
||||||
|
LambdaQueryWrapperX<ApiPolicyRateLimitDO> 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<ApiPolicyRateLimitDO> selectSimpleList() {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiPolicyRateLimitDO>()
|
||||||
|
.eq(ApiPolicyRateLimitDO::getDeleted, false)
|
||||||
|
.orderByDesc(ApiPolicyRateLimitDO::getUpdateTime)
|
||||||
|
.orderByDesc(ApiPolicyRateLimitDO::getId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiStepDO> {
|
||||||
|
|
||||||
|
default List<ApiStepDO> selectByApiId(Long apiId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiStepDO>()
|
||||||
|
.eq(ApiStepDO::getApiId, apiId)
|
||||||
|
.orderByAsc(ApiStepDO::getParallelGroup)
|
||||||
|
.orderByAsc(ApiStepDO::getStepOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void deleteByApiId(Long apiId) {
|
||||||
|
delete(new LambdaQueryWrapperX<ApiStepDO>()
|
||||||
|
.eq(ApiStepDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiTransformDO> {
|
||||||
|
|
||||||
|
default List<ApiTransformDO> selectByApiId(Long apiId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||||
|
.eq(ApiTransformDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<ApiTransformDO> selectByStepId(Long stepId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||||
|
.eq(ApiTransformDO::getStepId, stepId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<ApiTransformDO> selectApiLevelTransforms(Long apiId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||||
|
.eq(ApiTransformDO::getApiId, apiId)
|
||||||
|
.isNull(ApiTransformDO::getStepId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void deleteByApiId(Long apiId) {
|
||||||
|
delete(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||||
|
.eq(ApiTransformDO::getApiId, apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void deleteByStepId(Long stepId) {
|
||||||
|
delete(new LambdaQueryWrapperX<ApiTransformDO>()
|
||||||
|
.eq(ApiTransformDO::getStepId, stepId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String> allowedIps = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<String> 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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiFlowDispatcher> 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<GatewaySecurityFilter> gatewaySecurityFilterRegistration(GatewaySecurityFilter filter) {
|
||||||
|
FilterRegistrationBean<GatewaySecurityFilter> 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<ApiInvocationContext> 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<ApiInvocationContext> 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<ApiGatewayResponse> 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<String, Object> 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<String, Object> body = new HashMap<>();
|
||||||
|
body.put("errorMessage", message);
|
||||||
|
body.put("exception", ex.getClass().getSimpleName());
|
||||||
|
context.setResponseBody(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<FlowSegment> 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<ApiInvocationContext> 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<ApiInvocationContext> 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<CompletableFuture<ApiInvocationContext>> futures = new ArrayList<>();
|
||||||
|
for (ApiStepDefinition step : segment.getSteps()) {
|
||||||
|
GenericHandler<ApiInvocationContext> handler = stepHandlerFactory.build(aggregate, step);
|
||||||
|
ApiInvocationContext childContext = context.copy();
|
||||||
|
futures.add(CompletableFuture.supplyAsync(() -> {
|
||||||
|
handler.handle(childContext, headers);
|
||||||
|
return childContext;
|
||||||
|
}, apiPortalTaskExecutor));
|
||||||
|
}
|
||||||
|
for (CompletableFuture<ApiInvocationContext> 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<FlowSegment> segments(List<ApiStepDefinition> 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<FlowSegment> 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<FlowSegment> target, List<FlowSegment> 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<ApiStepDefinition> steps;
|
||||||
|
|
||||||
|
private ParallelSegment(String group, List<ApiStepDefinition> steps) {
|
||||||
|
this.group = group;
|
||||||
|
this.steps = steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroup() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApiStepDefinition> getSteps() {
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiInvocationContext> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> headers) {
|
||||||
|
ApiInvocationContext context = ApiInvocationContext.create();
|
||||||
|
Map<String, Object> uriVariables = (Map<String, Object>) 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<String, Object> requestHeaders = (Map<String, Object>) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, IntegrationFlowContext.IntegrationFlowRegistration> activeRegistrations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void bootstrap() {
|
||||||
|
refreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshAll() {
|
||||||
|
List<ApiDefinitionAggregate> aggregates = apiDefinitionService.loadActiveDefinitions();
|
||||||
|
Map<String, ApiDefinitionAggregate> 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<MessageChannel> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<org.aopalliance.aop.Advice> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiStepDefinition> steps;
|
||||||
|
|
||||||
|
Map<String, ApiTransformDefinition> apiLevelTransforms;
|
||||||
|
|
||||||
|
ApiPolicyAuthDO authPolicy;
|
||||||
|
|
||||||
|
ApiPolicyRateLimitDO rateLimitPolicy;
|
||||||
|
|
||||||
|
ApiFlowPublication publication;
|
||||||
|
|
||||||
|
public List<ApiStepDefinition> getSteps() {
|
||||||
|
return steps == null ? Collections.emptyList() : steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ApiTransformDefinition> getApiLevelTransforms() {
|
||||||
|
return apiLevelTransforms == null ? Collections.emptyMap() : apiLevelTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiTransformDefinition> transforms;
|
||||||
|
|
||||||
|
Map<String, Object> metadata;
|
||||||
|
|
||||||
|
public List<ApiTransformDefinition> getTransforms() {
|
||||||
|
return transforms == null ? Collections.emptyList() : transforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata == null ? Collections.emptyMap() : metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> variables;
|
||||||
|
|
||||||
|
Map<String, Object> headers;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ExpressionTypeEnum, ExpressionEvaluator> evaluators = new EnumMap<>(ExpressionTypeEnum.class);
|
||||||
|
|
||||||
|
public void register(ExpressionTypeEnum type, ExpressionEvaluator evaluator) {
|
||||||
|
evaluators.put(type, evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ExpressionEvaluator> lookup(ExpressionTypeEnum type) {
|
||||||
|
return Optional.ofNullable(evaluators.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> 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<Object> evaluateOptional(ExpressionSpec spec, ApiInvocationContext invocation, Object payload, Map<String, Object> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String, Object> requestHeaders;
|
||||||
|
|
||||||
|
private Object requestBody;
|
||||||
|
|
||||||
|
private Map<String, Object> requestQueryParams;
|
||||||
|
|
||||||
|
private Map<String, Object> variables;
|
||||||
|
|
||||||
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
|
private List<ApiStepResult> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object>> 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<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String> denied = properties.getDeniedIps();
|
||||||
|
if (!CollectionUtils.isEmpty(denied) && denied.contains(remoteIp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiInvocationContext> build(ApiDefinitionAggregate aggregate, ApiStepDefinition stepDefinition);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiStepHandler> stepHandlers;
|
||||||
|
|
||||||
|
public GenericHandler<ApiInvocationContext> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String> 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<String, Object> fallbackQuery) {
|
||||||
|
Map<String, Object> querySnapshot = new LinkedHashMap<>(fallbackQuery);
|
||||||
|
if (evaluated == null) {
|
||||||
|
return HttpRequestPayload.of(fallbackBody, querySnapshot);
|
||||||
|
}
|
||||||
|
if (evaluated instanceof HttpRequestPayload payload) {
|
||||||
|
Map<String, Object> 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<ApiInvocationContext> 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<String, String> headerMap = resolveHeaders(stepDefinition, payload);
|
||||||
|
Duration timeout = resolveTimeout(stepDefinition);
|
||||||
|
WebClient client = webClientBuilder.build();
|
||||||
|
WebClient.RequestHeadersSpec<?> requestSpec = buildRequest(client, callSpec, requestPayload, headerMap, supportsBody);
|
||||||
|
Mono<Object> 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<String, Object> headers) throws Exception {
|
||||||
|
ExpressionSpec spec = ExpressionSpecParser.parse(stepDefinition.getStep().getRequestMappingExpr(), ExpressionTypeEnum.JSON);
|
||||||
|
Map<String, Object> 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<String, Object> 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<String, String> resolveHeaders(ApiStepDefinition stepDefinition, ApiInvocationContext context) throws Exception {
|
||||||
|
Map<String, String> resolved = new LinkedHashMap<>();
|
||||||
|
context.getRequestHeaders().forEach((key, value) -> {
|
||||||
|
if (shouldForwardHeader(key) && value != null) {
|
||||||
|
resolved.put(key, String.valueOf(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> configured = extractConfiguredHeaders(stepDefinition, context);
|
||||||
|
resolved.putAll(configured);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> 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<String, String> toStringMap(Map<?, ?> map) {
|
||||||
|
if (map == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
Map<String, String> 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<String, Object> 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<String, Object> 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<String, String> 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<String, Object> 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<String, Object> queryParams) {
|
||||||
|
|
||||||
|
private HttpRequestPayload {
|
||||||
|
Map<String, Object> safeQuery = queryParams == null
|
||||||
|
? Collections.emptyMap()
|
||||||
|
: Collections.unmodifiableMap(new LinkedHashMap<>(queryParams));
|
||||||
|
queryParams = safeQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HttpRequestPayload of(Object body, Map<String, Object> 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<String, Object> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiInvocationContext> 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiInvocationContext> 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ApiDefinitionAggregate> loadActiveDefinitions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup API definition by code and version.
|
||||||
|
*/
|
||||||
|
Optional<ApiDefinitionAggregate> findByCodeAndVersion(String apiCode, String version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh a specific definition by evicting cache and reloading from DB.
|
||||||
|
*/
|
||||||
|
Optional<ApiDefinitionAggregate> refresh(String apiCode, String version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup API definition aggregate by primary key.
|
||||||
|
*/
|
||||||
|
Optional<ApiDefinitionAggregate> findById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query API definitions with pagination.
|
||||||
|
*/
|
||||||
|
PageResult<ApiDefinitionDO> 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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<ApiPolicyAuthDO> getPage(ApiPolicyPageReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all active policies for dropdowns.
|
||||||
|
*/
|
||||||
|
List<ApiPolicyAuthDO> getSimpleList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find policy detail.
|
||||||
|
*/
|
||||||
|
Optional<ApiPolicyAuthDO> get(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create policy definition.
|
||||||
|
*/
|
||||||
|
Long create(ApiPolicySaveReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update policy definition.
|
||||||
|
*/
|
||||||
|
void update(ApiPolicySaveReqVO reqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete policy definition.
|
||||||
|
*/
|
||||||
|
void delete(Long id);
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user