Compare commits
28 Commits
0c9b62209e
...
5037c741a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5037c741a9 | ||
|
|
b8b8495dfe | ||
|
|
f25ee5091e | ||
|
|
5fb15c31ed | ||
|
|
07e000ae33 | ||
|
|
c1ea3c6754 | ||
|
|
4197bbfbf8 | ||
|
|
a9efa1d84a | ||
|
|
c67debb9cf | ||
|
|
f64b76e703 | ||
|
|
3025c56495 | ||
|
|
2a0e0da08a | ||
|
|
309a3f352b | ||
|
|
00297f65b4 | ||
|
|
270132b7c1 | ||
|
|
b17126485c | ||
|
|
e98615c896 | ||
|
|
ec473a00d4 | ||
|
|
60d6446287 | ||
|
|
79add68d87 | ||
|
|
811270a4c5 | ||
|
|
0b27f9ba0b | ||
|
|
e6fe57533e | ||
|
|
b98f605dfd | ||
|
|
42f61158c6 | ||
|
|
46e38c4ca3 | ||
|
|
005e119ffb | ||
|
|
ddee4da72a |
100
deployment.yaml
100
deployment.yaml
@@ -29,14 +29,14 @@ spec:
|
|||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48080
|
port: 48080
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48080
|
port: 48080
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 5
|
failureThreshold: 5
|
||||||
resources:
|
resources:
|
||||||
@@ -44,7 +44,7 @@ spec:
|
|||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
memory: "1024Mi"
|
memory: "1024Mi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "700m"
|
cpu: "2048m"
|
||||||
memory: "2048Mi"
|
memory: "2048Mi"
|
||||||
terminationGracePeriodSeconds: 30
|
terminationGracePeriodSeconds: 30
|
||||||
---
|
---
|
||||||
@@ -96,14 +96,14 @@ spec:
|
|||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48082
|
port: 48082
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48082
|
port: 48082
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 5
|
failureThreshold: 5
|
||||||
resources:
|
resources:
|
||||||
@@ -111,7 +111,7 @@ spec:
|
|||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
memory: "1024Mi"
|
memory: "1024Mi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "700m"
|
cpu: "2560m"
|
||||||
memory: "2048Mi"
|
memory: "2048Mi"
|
||||||
terminationGracePeriodSeconds: 30
|
terminationGracePeriodSeconds: 30
|
||||||
strategy:
|
strategy:
|
||||||
@@ -168,14 +168,14 @@ spec:
|
|||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48081
|
port: 48081
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48081
|
port: 48081
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 5
|
failureThreshold: 5
|
||||||
resources:
|
resources:
|
||||||
@@ -183,7 +183,7 @@ spec:
|
|||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
memory: "1024Mi"
|
memory: "1024Mi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "700m"
|
cpu: "2048m"
|
||||||
memory: "2048Mi"
|
memory: "2048Mi"
|
||||||
terminationGracePeriodSeconds: 30
|
terminationGracePeriodSeconds: 30
|
||||||
strategy:
|
strategy:
|
||||||
@@ -207,6 +207,78 @@ spec:
|
|||||||
targetPort: 48081
|
targetPort: 48081
|
||||||
nodePort: 30091
|
nodePort: 30091
|
||||||
---
|
---
|
||||||
|
# zt-module-bpm
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
namespace: ns-d6a0e78ebd674c279614498e4c57b133
|
||||||
|
name: zt-module-bpm
|
||||||
|
labels:
|
||||||
|
app: zt-module-bpm
|
||||||
|
annotations:
|
||||||
|
version: "VERSION_PLACEHOLDER"
|
||||||
|
description: DESC_PLACEHOLDER
|
||||||
|
rollout.kubernetes.io/change-cause: "DESC_PLACEHOLDER:VERSION_PLACEHOLDER"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: zt-module-bpm
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: zt-module-bpm
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: zt-module-bpm
|
||||||
|
image: 172.16.46.66:10043/zt/zt-module-bpm:VERSION_PLACEHOLDER
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: Asia/Shanghai
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 48083
|
||||||
|
initialDelaySeconds: 50
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /actuator/health
|
||||||
|
port: 48083
|
||||||
|
initialDelaySeconds: 50
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "1024Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "2048m"
|
||||||
|
memory: "2048Mi"
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
namespace: ns-d6a0e78ebd674c279614498e4c57b133
|
||||||
|
name: zt-module-bpm
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: zt-module-bpm
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 48083
|
||||||
|
targetPort: 48083
|
||||||
|
nodePort: 30093
|
||||||
|
---
|
||||||
# zt-module-report
|
# zt-module-report
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -240,14 +312,14 @@ spec:
|
|||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48084
|
port: 48084
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48084
|
port: 48084
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 5
|
failureThreshold: 5
|
||||||
resources:
|
resources:
|
||||||
@@ -255,7 +327,7 @@ spec:
|
|||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
memory: "1024Mi"
|
memory: "1024Mi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "700m"
|
cpu: "2048m"
|
||||||
memory: "2048Mi"
|
memory: "2048Mi"
|
||||||
terminationGracePeriodSeconds: 30
|
terminationGracePeriodSeconds: 30
|
||||||
strategy:
|
strategy:
|
||||||
@@ -312,14 +384,14 @@ spec:
|
|||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48100
|
port: 48100
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /actuator/health
|
path: /actuator/health
|
||||||
port: 48100
|
port: 48100
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 50
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
failureThreshold: 5
|
failureThreshold: 5
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
9
pom.xml
9
pom.xml
@@ -20,7 +20,7 @@
|
|||||||
<module>zt-module-report</module>
|
<module>zt-module-report</module>
|
||||||
<!-- <module>zt-module-mp</module>-->
|
<!-- <module>zt-module-mp</module>-->
|
||||||
<!-- <module>zt-module-ai</module>-->
|
<!-- <module>zt-module-ai</module>-->
|
||||||
<module>zt-module-template</module>
|
<!-- <module>zt-module-template</module>-->
|
||||||
<!-- <module>zt-module-iot</module>-->
|
<!-- <module>zt-module-iot</module>-->
|
||||||
<module>zt-module-databus</module>
|
<module>zt-module-databus</module>
|
||||||
<!-- <module>zt-module-rule</module>-->
|
<!-- <module>zt-module-rule</module>-->
|
||||||
@@ -205,8 +205,13 @@
|
|||||||
<name>中铜 ZStack 私服</name>
|
<name>中铜 ZStack 私服</name>
|
||||||
<url>http://172.16.46.63:30708/repository/test/</url>
|
<url>http://172.16.46.63:30708/repository/test/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<updatePolicy>always</updatePolicy>
|
||||||
|
<checksumPolicy>warn</checksumPolicy>
|
||||||
</releases>
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<updatePolicy>always</updatePolicy>
|
||||||
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
|
|||||||
15
sql/dm/字典导入菜单权限_DM8.sql
Normal file
15
sql/dm/字典导入菜单权限_DM8.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
-- DM8 字典导入按钮权限脚本
|
||||||
|
-- 幂等处理:清理旧的导入权限按钮,再重新写入
|
||||||
|
|
||||||
|
DELETE FROM system_role_menu WHERE menu_id = 103001;
|
||||||
|
DELETE FROM system_menu WHERE id = 103001;
|
||||||
|
|
||||||
|
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 (
|
||||||
|
103001, '字典导入', 'system:dict:import', 3, 6, 105, '#', '#', '', NULL,
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 如需同步给指定角色,请手工向 system_role_menu 插入对应关系记录
|
||||||
44
sql/dm/工艺工序页面菜单权限_20251030.sql
Normal file
44
sql/dm/工艺工序页面菜单权限_20251030.sql
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
-- 工艺工序页面菜单与权限初始化脚本(DM8)
|
||||||
|
-- 默认挂载在基础数据目录(parent_id = 6200),如需调整请修改 parent_id。
|
||||||
|
|
||||||
|
DELETE FROM system_menu
|
||||||
|
WHERE id IN (6207, 620701, 620702, 620703, 620704, 620705);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
6207, '工艺工序', 'base:processing-infomation-operation:query', 2, 70, 6200,
|
||||||
|
'processing-infomation-operation', 'ep:operation', 'base/processinginfomationoperation/index', 'ProcessingInfomationOperation',
|
||||||
|
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
|
||||||
|
(620701, '工艺工序查询', 'base:processing-infomation-operation:query', 3, 1, 6207,
|
||||||
|
'', '', '', '',
|
||||||
|
0, '1', '1', '1',
|
||||||
|
'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
(620702, '工艺工序创建', 'base:processing-infomation-operation:create', 3, 2, 6207,
|
||||||
|
'', '', '', '',
|
||||||
|
0, '1', '1', '1',
|
||||||
|
'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
(620703, '工艺工序更新', 'base:processing-infomation-operation:update', 3, 3, 6207,
|
||||||
|
'', '', '', '',
|
||||||
|
0, '1', '1', '1',
|
||||||
|
'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
(620704, '工艺工序删除', 'base:processing-infomation-operation:delete', 3, 4, 6207,
|
||||||
|
'', '', '', '',
|
||||||
|
0, '1', '1', '1',
|
||||||
|
'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
(620705, '工艺工序导出', 'base:processing-infomation-operation:export', 3, 5, 6207,
|
||||||
|
'', '', '', '',
|
||||||
|
0, '1', '1', '1',
|
||||||
|
'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');
|
||||||
33
sql/dm/数据总线API版本历史菜单权限_备忘录模式_20251030.sql
Normal file
33
sql/dm/数据总线API版本历史菜单权限_备忘录模式_20251030.sql
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- 数据总线 API 版本历史菜单权限(备忘录模式)
|
||||||
|
-- 功能说明:
|
||||||
|
-- 1. 查看版本历史
|
||||||
|
-- 2. 查看版本详情
|
||||||
|
-- 3. 版本回滚
|
||||||
|
-- 4. 版本对比
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- 删除旧的版本管理菜单权限
|
||||||
|
DELETE FROM system_menu WHERE id IN (650107, 650108, 650109, 650110);
|
||||||
|
|
||||||
|
-- 插入新的版本历史管理权限
|
||||||
|
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
|
||||||
|
-- 查询版本历史列表
|
||||||
|
(650107, 'API版本历史', 'databus:gateway:version:query', 3, 7, 6501, '', '', '', '',
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
-- 查看版本详情
|
||||||
|
(650108, 'API版本详情', 'databus:gateway:version:detail', 3, 8, 6501, '', '', '', '',
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
-- 版本回滚
|
||||||
|
(650109, 'API版本回滚', 'databus:gateway:version:rollback', 3, 9, 6501, '', '', '', '',
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0'),
|
||||||
|
-- 版本对比
|
||||||
|
(650110, 'API版本对比', 'databus:gateway:version:compare', 3, 10, 6501, '', '', '', '',
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');
|
||||||
|
|
||||||
|
-- 说明
|
||||||
|
-- 1. 不再需要"创建版本"权限,因为系统自动创建
|
||||||
|
-- 2. 不再需要"删除版本"权限,版本历史不可删除
|
||||||
|
-- 3. 保留查询、详情、回滚、对比四个核心功能
|
||||||
52
sql/dm/数据总线API版本历史表结构_备忘录模式_20251030.sql
Normal file
52
sql/dm/数据总线API版本历史表结构_备忘录模式_20251030.sql
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- 数据总线 API 版本历史表(备忘录模式)
|
||||||
|
-- 功能说明:
|
||||||
|
-- 1. 每次保存 API 配置时自动创建版本记录
|
||||||
|
-- 2. 版本号自动递增(v1, v2, v3...)
|
||||||
|
-- 3. 保留完整历史链,不可删除
|
||||||
|
-- 4. 支持一键回滚到任意历史版本
|
||||||
|
-- 5. 支持版本对比功能
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
-- 如果表已存在则删除
|
||||||
|
DROP TABLE IF EXISTS databus_api_version;
|
||||||
|
|
||||||
|
-- 创建版本历史表(DM8 语法)
|
||||||
|
CREATE TABLE databus_api_version (
|
||||||
|
id BIGINT NOT NULL,
|
||||||
|
api_id BIGINT NOT NULL,
|
||||||
|
version_number INTEGER NOT NULL,
|
||||||
|
snapshot_data CLOB NOT NULL,
|
||||||
|
description VARCHAR(500),
|
||||||
|
is_current NUMBER(1) DEFAULT 0 NOT NULL,
|
||||||
|
operator VARCHAR(64),
|
||||||
|
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 NUMBER(1) DEFAULT 0 NOT NULL,
|
||||||
|
tenant_id BIGINT DEFAULT 0 NOT NULL,
|
||||||
|
CONSTRAINT pk_databus_api_version PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 创建索引
|
||||||
|
CREATE INDEX idx_databus_api_version_api_id ON databus_api_version (api_id);
|
||||||
|
CREATE INDEX idx_databus_api_version_version_number ON databus_api_version (api_id, version_number);
|
||||||
|
CREATE INDEX idx_databus_api_version_is_current ON databus_api_version (api_id, is_current);
|
||||||
|
CREATE INDEX idx_databus_api_version_create_time ON databus_api_version (create_time);
|
||||||
|
CREATE INDEX idx_databus_api_version_operator ON databus_api_version (operator);
|
||||||
|
|
||||||
|
COMMENT ON TABLE databus_api_version IS '数据总线API版本历史表:采用备忘录模式,每次保存API时自动创建版本快照,支持完整的版本历史追溯和回滚';
|
||||||
|
COMMENT ON COLUMN databus_api_version.id IS '主键ID';
|
||||||
|
COMMENT ON COLUMN databus_api_version.api_id IS 'API定义ID,关联databus_api_definition表';
|
||||||
|
COMMENT ON COLUMN databus_api_version.version_number IS '版本号,同一API下自动递增(1,2,3...)';
|
||||||
|
COMMENT ON COLUMN databus_api_version.snapshot_data IS 'API完整配置快照(JSON格式),包含definition、steps、transforms等所有信息';
|
||||||
|
COMMENT ON COLUMN databus_api_version.description IS '变更说明,记录本次修改的内容';
|
||||||
|
COMMENT ON COLUMN databus_api_version.is_current IS '是否为当前版本(1=是,0=否),同一API只有一个当前版本';
|
||||||
|
COMMENT ON COLUMN databus_api_version.operator IS '操作人,记录谁创建了这个版本';
|
||||||
|
COMMENT ON COLUMN databus_api_version.creator IS '创建者';
|
||||||
|
COMMENT ON COLUMN databus_api_version.create_time IS '创建时间(版本创建时间)';
|
||||||
|
COMMENT ON COLUMN databus_api_version.updater IS '更新者';
|
||||||
|
COMMENT ON COLUMN databus_api_version.update_time IS '更新时间';
|
||||||
|
COMMENT ON COLUMN databus_api_version.deleted IS '是否删除(逻辑删除,实际不删除版本历史)';
|
||||||
|
COMMENT ON COLUMN databus_api_version.tenant_id IS '租户ID';
|
||||||
13
sql/dm/数据总线API访问日志菜单权限_20251028.sql
Normal file
13
sql/dm/数据总线API访问日志菜单权限_20251028.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
-- 数据总线 API 访问日志菜单权限初始化(DM8)
|
||||||
|
-- 创建访问日志页面及查询按钮权限。如已存在将先行移除再新增。
|
||||||
|
DELETE FROM system_menu WHERE id IN (6504, 650401);
|
||||||
|
|
||||||
|
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 (6504, '访问日志', 'databus:gateway:access-log:query', 2, 40, 6500, 'access-log', 'ep:document', 'databus/accesslog/index', 'DatabusAccessLog',
|
||||||
|
0, '1', '1', '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 (650401, '访问日志查询', 'databus:gateway:access-log:query', 3, 1, 6504, '', '', '', '',
|
||||||
|
0, '1', '1', '1', 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, '0');
|
||||||
0
sql/dm/数据总线API访问日志表结构_20251028.sql
Normal file
0
sql/dm/数据总线API访问日志表结构_20251028.sql
Normal file
52
sql/dm/权限监督功能.sql
Normal file
52
sql/dm/权限监督功能.sql
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
-- 权限监督按钮及接口权限(DM8 专用)
|
||||||
|
-- 执行前请确认未占用 1068 主键
|
||||||
|
|
||||||
|
DELETE FROM system_menu WHERE id = 1068;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
1068,
|
||||||
|
'权限监督',
|
||||||
|
'system:permission:user-permission-supervision',
|
||||||
|
3,
|
||||||
|
9,
|
||||||
|
101,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
'1',
|
||||||
|
'1',
|
||||||
|
'1',
|
||||||
|
'admin',
|
||||||
|
'2025-10-29 00:00:00',
|
||||||
|
'',
|
||||||
|
'2025-10-29 00:00:00',
|
||||||
|
'0'
|
||||||
|
FROM dual
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM system_menu
|
||||||
|
WHERE id = 1068
|
||||||
|
);
|
||||||
8
sql/dm/组织物料状态字段补充_20251105.sql
Normal file
8
sql/dm/组织物料状态字段补充_20251105.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- 脚本名称 : 组织物料状态字段补充_20251105.sql
|
||||||
|
-- 适用数据库 : DM8
|
||||||
|
-- 变更目的 : 为 bse_dept_mtrl 表补充状态字段 STS,解决查询时列不存在的问题
|
||||||
|
-- 影响说明 : 仅新增并初始化 STS 字段,默认值为 '1'(有效)
|
||||||
|
-- 执行前请先确认已备份相关数据
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
ALTER TABLE BSE_DEPT_MTRL ADD STS VARCHAR2(5) DEFAULT '0';
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<!-- 服务保障相关 -->
|
<!-- 服务保障相关 -->
|
||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<!-- 监控相关 -->
|
<!-- 监控相关 -->
|
||||||
<skywalking.version>9.0.0</skywalking.version>
|
<skywalking.version>9.5.0</skywalking.version>
|
||||||
<spring-boot-admin.version>3.4.5</spring-boot-admin.version>
|
<spring-boot-admin.version>3.4.5</spring-boot-admin.version>
|
||||||
<opentracing.version>0.33.0</opentracing.version>
|
<opentracing.version>0.33.0</opentracing.version>
|
||||||
<!-- Test 测试相关 -->
|
<!-- Test 测试相关 -->
|
||||||
@@ -86,6 +86,8 @@
|
|||||||
<netty.version>4.1.116.Final</netty.version>
|
<netty.version>4.1.116.Final</netty.version>
|
||||||
<mqtt.version>1.2.5</mqtt.version>
|
<mqtt.version>1.2.5</mqtt.version>
|
||||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||||
|
<!-- 规则引擎 -->
|
||||||
|
<liteflow.version>2.15.1</liteflow.version>
|
||||||
<vertx.version>4.5.13</vertx.version>
|
<vertx.version>4.5.13</vertx.version>
|
||||||
<!-- 三方云服务相关 -->
|
<!-- 三方云服务相关 -->
|
||||||
<commons-io.version>2.17.0</commons-io.version>
|
<commons-io.version>2.17.0</commons-io.version>
|
||||||
@@ -661,6 +663,13 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 规则引擎 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yomahub</groupId>
|
||||||
|
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||||
|
<version>${liteflow.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- PF4J -->
|
<!-- PF4J -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.pf4j</groupId>
|
<groupId>org.pf4j</groupId>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
|
<artifactId>zt-spring-boot-starter-biz-data-permission</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.zt.plat</groupId>
|
||||||
|
<artifactId>zt-spring-boot-starter-biz-tenant</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
<!-- Test 测试相关 -->
|
<!-- Test 测试相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.zt.plat</groupId>
|
<groupId>com.zt.plat</groupId>
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import cn.hutool.json.JSONObject;
|
|||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
|
import com.zt.plat.framework.common.pojo.CompanyDeptInfo;
|
||||||
import com.zt.plat.framework.security.core.LoginUser;
|
import com.zt.plat.framework.security.core.LoginUser;
|
||||||
|
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
|
||||||
|
import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -22,43 +26,85 @@ import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.ge
|
|||||||
public class BusinessDeptHandleUtil {
|
public class BusinessDeptHandleUtil {
|
||||||
public static Set<CompanyDeptInfo> getBelongCompanyAndDept(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
public static Set<CompanyDeptInfo> getBelongCompanyAndDept(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
String companyId = request.getHeader("visit-company-id");
|
String companyIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_COMPANY_ID);
|
||||||
String deptId = request.getHeader("visit-dept-id");
|
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_DEPT_ID);
|
||||||
LoginUser loginUser = Optional.ofNullable(getLoginUser()).orElse(new LoginUser().setInfo(new HashMap<>()));
|
|
||||||
Set<CompanyDeptInfo> companyDeptSet = JSONUtil.parseArray(loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream()
|
LoginUser currentLoginUser = getLoginUser();
|
||||||
|
Map<String, String> extraInfo = Optional.ofNullable(currentLoginUser)
|
||||||
|
.map(LoginUser::getInfo)
|
||||||
|
.orElseGet(HashMap::new);
|
||||||
|
if (currentLoginUser != null && currentLoginUser.getInfo() == null) {
|
||||||
|
currentLoginUser.setInfo(extraInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<CompanyDeptInfo> companyDeptSet = JSONUtil.parseArray(extraInfo.getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream()
|
||||||
.map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class))
|
.map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// 1. 有 companyId
|
// 1. 有 companyId
|
||||||
if (companyId != null && !companyId.isBlank()) {
|
if (companyIdHeader != null && !companyIdHeader.isBlank()) {
|
||||||
// 根据请求头中的公司 ID 过滤出当前用户的公司部门信息
|
// 根据请求头中的公司 ID 过滤出当前用户的公司部门信息
|
||||||
Set<CompanyDeptInfo> companyDeptSetByCompanyId = companyDeptSet.stream().filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyId)).collect(Collectors.toSet());
|
Set<CompanyDeptInfo> companyDeptSetByCompanyId = companyDeptSet.stream()
|
||||||
|
.filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyIdHeader))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
if (companyDeptSetByCompanyId.isEmpty()) {
|
if (companyDeptSetByCompanyId.isEmpty()) {
|
||||||
// 当前公司下没有部门
|
// 当前公司下没有部门
|
||||||
CompanyDeptInfo data = new CompanyDeptInfo();
|
CompanyDeptInfo data = new CompanyDeptInfo();
|
||||||
data.setCompanyId(Long.valueOf(companyId));
|
data.setCompanyId(Long.valueOf(companyIdHeader));
|
||||||
data.setDeptId(0L);
|
data.setDeptId(0L);
|
||||||
return new HashSet<>(singleton(data));
|
return new HashSet<>(singleton(data));
|
||||||
}
|
}
|
||||||
// 如果有 deptId,校验其是否属于该 companyId
|
// 如果有 deptId,校验其是否属于该 companyId
|
||||||
if (deptId != null) {
|
if (deptIdHeader != null) {
|
||||||
boolean valid = companyDeptSetByCompanyId.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptId));
|
boolean valid = companyDeptSetByCompanyId.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptIdHeader));
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return null;
|
return null;
|
||||||
}else{
|
} else {
|
||||||
// 部门存在,放行
|
// 部门存在,放行
|
||||||
return new HashSet<>();
|
return new HashSet<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (companyDeptSetByCompanyId.size() == 1) {
|
||||||
|
CompanyDeptInfo singleCompanyDept = companyDeptSetByCompanyId.iterator().next();
|
||||||
|
if (applyAutoSelection(currentLoginUser, request, singleCompanyDept)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
return companyDeptSetByCompanyId;
|
return companyDeptSetByCompanyId;
|
||||||
}
|
}
|
||||||
// 2. 没有公司信息,尝试唯一性自动推断
|
// 2. 没有公司信息,尝试唯一性自动推断
|
||||||
// 如果当前用户下只有一个公司和部门的对于关系
|
// 如果当前用户下只有一个公司和部门的对于关系
|
||||||
if (companyDeptSet.size() == 1) {
|
if (companyDeptSet.size() == 1) {
|
||||||
CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next();
|
CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next();
|
||||||
|
if (applyAutoSelection(currentLoginUser, request, companyDeptInfo)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
return new HashSet<>(singleton(companyDeptInfo));
|
return new HashSet<>(singleton(companyDeptInfo));
|
||||||
} else {
|
|
||||||
return companyDeptSet;
|
|
||||||
}
|
}
|
||||||
|
return companyDeptSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean applyAutoSelection(LoginUser loginUser, HttpServletRequest request, CompanyDeptInfo info) {
|
||||||
|
if (info == null || info.getCompanyId() == null || info.getCompanyId() <= 0
|
||||||
|
|| info.getDeptId() == null || info.getDeptId() <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (loginUser != null) {
|
||||||
|
loginUser.setVisitCompanyId(info.getCompanyId());
|
||||||
|
loginUser.setVisitCompanyName(info.getCompanyName());
|
||||||
|
loginUser.setVisitDeptId(info.getDeptId());
|
||||||
|
loginUser.setVisitDeptName(info.getDeptName());
|
||||||
|
}
|
||||||
|
request.setAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_ID, info.getCompanyId());
|
||||||
|
if (info.getCompanyName() != null) {
|
||||||
|
request.setAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_NAME, info.getCompanyName());
|
||||||
|
}
|
||||||
|
request.setAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_ID, info.getDeptId());
|
||||||
|
if (info.getDeptName() != null) {
|
||||||
|
request.setAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_NAME, info.getDeptName());
|
||||||
|
}
|
||||||
|
CompanyContextHolder.setIgnore(false);
|
||||||
|
CompanyContextHolder.setCompanyId(info.getCompanyId());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,30 +20,68 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
// 解析 header 并设置 visitCompanyId
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||||
|
|
||||||
Long companyId = WebFrameworkUtils.getCompanyId(request);
|
Long companyId = WebFrameworkUtils.getCompanyId(request);
|
||||||
|
// 优先使用请求头上的公司信息,若缺失则回退到请求属性或当前登录用户已缓存的访问公司
|
||||||
|
if (companyId == null || companyId <= 0L) {
|
||||||
|
Long attrCompanyId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_ID));
|
||||||
|
if (attrCompanyId != null && attrCompanyId > 0L) {
|
||||||
|
companyId = attrCompanyId;
|
||||||
|
} else if (loginUser != null && loginUser.getVisitCompanyId() != null && loginUser.getVisitCompanyId() > 0L) {
|
||||||
|
companyId = loginUser.getVisitCompanyId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String companyName = WebFrameworkUtils.getCompanyName(request);
|
String companyName = WebFrameworkUtils.getCompanyName(request);
|
||||||
if (companyId <= 0L) {
|
if (companyName == null || companyName.isEmpty()) {
|
||||||
// 如果没有设置 companyId,则忽略
|
Object attrCompanyName = request.getAttribute(WebFrameworkUtils.HEADER_VISIT_COMPANY_NAME);
|
||||||
|
if (attrCompanyName instanceof String) {
|
||||||
|
companyName = (String) attrCompanyName;
|
||||||
|
} else if (loginUser != null) {
|
||||||
|
companyName = loginUser.getVisitCompanyName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Long deptId = WebFrameworkUtils.getDeptId(request);
|
||||||
|
// 部门信息同样遵循“请求头 -> 请求属性 -> 登录缓存”的回退顺序
|
||||||
|
if (deptId == null || deptId <= 0L) {
|
||||||
|
Long attrDeptId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_ID));
|
||||||
|
if (attrDeptId != null && attrDeptId > 0L) {
|
||||||
|
deptId = attrDeptId;
|
||||||
|
} else if (loginUser != null && loginUser.getVisitDeptId() != null && loginUser.getVisitDeptId() > 0L) {
|
||||||
|
deptId = loginUser.getVisitDeptId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String deptName = WebFrameworkUtils.getDeptName(request);
|
||||||
|
if (deptName == null || deptName.isEmpty()) {
|
||||||
|
Object attrDeptName = request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_NAME);
|
||||||
|
if (attrDeptName instanceof String) {
|
||||||
|
deptName = (String) attrDeptName;
|
||||||
|
} else if (loginUser != null) {
|
||||||
|
deptName = loginUser.getVisitDeptName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (companyId == null || companyId <= 0L) {
|
||||||
CompanyContextHolder.setIgnore(true);
|
CompanyContextHolder.setIgnore(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Long deptId = WebFrameworkUtils.getDeptId(request);
|
|
||||||
String deptName = WebFrameworkUtils.getDeptName(request);
|
CompanyContextHolder.setIgnore(false);
|
||||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
CompanyContextHolder.setCompanyId(companyId);
|
||||||
if (loginUser == null) {
|
if (loginUser == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (deptId > 0L) {
|
|
||||||
|
// 同步最新的访问公司/部门到登录用户对象,供后续数据权限及上下文读取
|
||||||
|
loginUser.setVisitCompanyId(companyId);
|
||||||
|
loginUser.setVisitCompanyName(companyName);
|
||||||
|
if (deptId != null && deptId > 0L) {
|
||||||
loginUser.setVisitDeptId(deptId);
|
loginUser.setVisitDeptId(deptId);
|
||||||
loginUser.setVisitDeptName(deptName);
|
loginUser.setVisitDeptName(deptName);
|
||||||
}
|
}
|
||||||
// if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
|
|
||||||
// throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换部门");
|
|
||||||
// }
|
|
||||||
loginUser.setVisitCompanyId(companyId);
|
|
||||||
loginUser.setVisitCompanyName(companyName);
|
|
||||||
CompanyContextHolder.setCompanyId(companyId);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,4 +93,18 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
|
|||||||
loginUser.setVisitCompanyId(0L);
|
loginUser.setVisitCompanyId(0L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long resolveLong(Object value) {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(((String) value).trim());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,4 +49,11 @@ public class ExcelUtils {
|
|||||||
.doReadAllSync();
|
.doReadAllSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> read(MultipartFile file, Class<T> head, int sheetNo) throws IOException {
|
||||||
|
return EasyExcel.read(file.getInputStream(), head, null)
|
||||||
|
.autoCloseStream(false)
|
||||||
|
.sheet(sheetNo)
|
||||||
|
.doReadSync();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-gateway
|
RUN mkdir -p /zt-gateway
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-gateway.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-gateway
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48080
|
EXPOSE 48080
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -272,6 +272,13 @@ spring:
|
|||||||
- Path=/admin-api/databus/**
|
- Path=/admin-api/databus/**
|
||||||
filters:
|
filters:
|
||||||
- RewritePath=/admin-api/databus/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
- RewritePath=/admin-api/databus/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
|
||||||
x-forwarded:
|
x-forwarded:
|
||||||
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-ai-server
|
RUN mkdir -p /zt-module-ai-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-ai-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-ai-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48090
|
EXPOSE 48090
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-bpm-server
|
RUN mkdir -p /zt-module-bpm-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-bpm-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-bpm-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48083
|
EXPOSE 48083
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||||
|
|
||||||
|
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.ApiAccessLogConvert;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogRespVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志控制器。
|
||||||
|
*/
|
||||||
|
@Tag(name = "管理后台 - Databus API 访问日志")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/databus/gateway/access-log")
|
||||||
|
@Validated
|
||||||
|
public class ApiAccessLogController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApiAccessLogService apiAccessLogService;
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
@Operation(summary = "获取访问日志详情")
|
||||||
|
@Parameter(name = "id", description = "日志编号", required = true, example = "1024")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
|
||||||
|
public CommonResult<ApiAccessLogRespVO> get(@RequestParam("id") Long id) {
|
||||||
|
ApiAccessLogDO logDO = apiAccessLogService.get(id);
|
||||||
|
return success(ApiAccessLogConvert.INSTANCE.convert(logDO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "分页查询访问日志")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:access-log:query')")
|
||||||
|
public CommonResult<PageResult<ApiAccessLogRespVO>> page(@Valid ApiAccessLogPageReqVO pageReqVO) {
|
||||||
|
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getPage(pageReqVO);
|
||||||
|
return success(ApiAccessLogConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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.ApiVersionConvert;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionCompareRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionDetailRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRollbackReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiVersionService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史控制器。
|
||||||
|
*/
|
||||||
|
@Tag(name = "管理后台 - API 版本历史")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/databus/gateway/version")
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
public class ApiVersionController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApiVersionService apiVersionService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
@Operation(summary = "获取 API 版本详情")
|
||||||
|
@Parameter(name = "id", description = "版本编号", required = true, example = "1024")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||||
|
public CommonResult<ApiVersionDetailRespVO> getVersion(@RequestParam("id") Long id) {
|
||||||
|
ApiVersionDO versionDO = apiVersionService.getVersion(id);
|
||||||
|
ApiVersionDetailRespVO respVO = ApiVersionConvert.INSTANCE.convertDetail(versionDO);
|
||||||
|
|
||||||
|
// 反序列化快照数据
|
||||||
|
if (versionDO.getSnapshotData() != null) {
|
||||||
|
try {
|
||||||
|
ApiDefinitionSaveReqVO snapshot = objectMapper.readValue(versionDO.getSnapshotData(), ApiDefinitionSaveReqVO.class);
|
||||||
|
respVO.setSnapshotData(snapshot);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
log.error("反序列化版本快照失败, versionId={}", id, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "分页查询 API 版本列表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||||
|
public CommonResult<PageResult<ApiVersionRespVO>> getVersionPage(@Valid ApiVersionPageReqVO pageReqVO) {
|
||||||
|
PageResult<ApiVersionDO> pageResult = apiVersionService.getVersionPage(pageReqVO);
|
||||||
|
return success(ApiVersionConvert.INSTANCE.convertPage(pageResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "查询指定 API 的全部版本")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||||
|
public CommonResult<java.util.List<ApiVersionRespVO>> getVersionList(@RequestParam("apiId") Long apiId) {
|
||||||
|
return success(ApiVersionConvert.INSTANCE.convertList(apiVersionService.getVersionListByApiId(apiId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/rollback")
|
||||||
|
@Operation(summary = "回滚到指定版本")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:version:rollback')")
|
||||||
|
public CommonResult<Boolean> rollbackToVersion(@Valid @RequestBody ApiVersionRollbackReqVO reqVO) {
|
||||||
|
apiVersionService.rollbackToVersion(reqVO.getId(), reqVO.getRemark());
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/compare")
|
||||||
|
@Operation(summary = "对比两个版本差异")
|
||||||
|
@PreAuthorize("@ss.hasPermission('databus:gateway:version:query')")
|
||||||
|
public CommonResult<ApiVersionCompareRespVO> compareVersions(
|
||||||
|
@RequestParam("sourceId") Long sourceId,
|
||||||
|
@RequestParam("targetId") Long targetId) {
|
||||||
|
return success(apiVersionService.compareVersions(sourceId, targetId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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.accesslog.ApiAccessLogRespVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ApiAccessLogConvert {
|
||||||
|
|
||||||
|
ApiAccessLogConvert INSTANCE = Mappers.getMapper(ApiAccessLogConvert.class);
|
||||||
|
|
||||||
|
ApiAccessLogRespVO convert(ApiAccessLogDO bean);
|
||||||
|
|
||||||
|
List<ApiAccessLogRespVO> convertList(List<ApiAccessLogDO> list);
|
||||||
|
|
||||||
|
default PageResult<ApiAccessLogRespVO> convertPage(PageResult<ApiAccessLogDO> page) {
|
||||||
|
if (page == null) {
|
||||||
|
return PageResult.empty();
|
||||||
|
}
|
||||||
|
PageResult<ApiAccessLogRespVO> result = new PageResult<>();
|
||||||
|
result.setList(convertList(page.getList()));
|
||||||
|
result.setTotal(page.getTotal());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,10 @@ package com.zt.plat.module.databus.controller.admin.gateway.convert;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
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.*;
|
||||||
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.dal.dataobject.gateway.ApiDefinitionDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
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.ApiFlowPublication;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||||
@@ -101,4 +99,29 @@ public interface ApiDefinitionConvert {
|
|||||||
return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class);
|
return publication == null ? null : BeanUtils.toBean(publication, ApiDefinitionPublicationRespVO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换步骤列表(DO -> SaveReqVO)
|
||||||
|
*/
|
||||||
|
default List<ApiDefinitionStepSaveReqVO> convertStepList(List<ApiStepDO> steps) {
|
||||||
|
if (CollUtil.isEmpty(steps)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return steps.stream()
|
||||||
|
.sorted(Comparator.comparing(step -> step.getStepOrder() == null ? Integer.MAX_VALUE : step.getStepOrder()))
|
||||||
|
.map(step -> BeanUtils.toBean(step, ApiDefinitionStepSaveReqVO.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换变换列表(DO -> SaveReqVO)
|
||||||
|
*/
|
||||||
|
default List<ApiDefinitionTransformSaveReqVO> convertTransformList(List<ApiTransformDO> transforms) {
|
||||||
|
if (CollUtil.isEmpty(transforms)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return transforms.stream()
|
||||||
|
.map(transform -> BeanUtils.toBean(transform, ApiDefinitionTransformSaveReqVO.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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.version.ApiVersionDetailRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionRespVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史 Convert。
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ApiVersionConvert {
|
||||||
|
|
||||||
|
ApiVersionConvert INSTANCE = Mappers.getMapper(ApiVersionConvert.class);
|
||||||
|
|
||||||
|
ApiVersionRespVO convert(ApiVersionDO bean);
|
||||||
|
|
||||||
|
PageResult<ApiVersionRespVO> convertPage(PageResult<ApiVersionDO> page);
|
||||||
|
|
||||||
|
List<ApiVersionRespVO> convertList(List<ApiVersionDO> list);
|
||||||
|
|
||||||
|
@Mapping(target = "snapshotData", ignore = true)
|
||||||
|
ApiVersionDetailRespVO convertDetail(ApiVersionDO bean);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志分页查询 VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - Databus API 访问日志分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class ApiAccessLogPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "追踪 ID", example = "c8a3d52f-42c8-4b5d-9e26-8c2cc89f6bb5")
|
||||||
|
private String traceId;
|
||||||
|
|
||||||
|
@Schema(description = "API 编码", example = "user.query")
|
||||||
|
private String apiCode;
|
||||||
|
|
||||||
|
@Schema(description = "API 版本", example = "v1")
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
@Schema(description = "HTTP 方法", example = "POST")
|
||||||
|
private String requestMethod;
|
||||||
|
|
||||||
|
@Schema(description = "响应 HTTP 状态", example = "200")
|
||||||
|
private Integer responseStatus;
|
||||||
|
|
||||||
|
@Schema(description = "访问状态", example = "0")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "客户端 IP", example = "192.168.0.10")
|
||||||
|
private String clientIp;
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", example = "1")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
||||||
|
private String requestPath;
|
||||||
|
|
||||||
|
@Schema(description = "请求时间区间")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
|
private LocalDateTime[] requestTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志 Response VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - Databus API 访问日志 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ApiAccessLogRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "日志编号", example = "1024")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "追踪 ID", example = "c8a3d52f-42c8-4b5d-9e26-8c2cc89f6bb5")
|
||||||
|
private String traceId;
|
||||||
|
|
||||||
|
@Schema(description = "API 编码", example = "user.query")
|
||||||
|
private String apiCode;
|
||||||
|
|
||||||
|
@Schema(description = "API 版本", example = "v1")
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
@Schema(description = "HTTP 方法", example = "POST")
|
||||||
|
private String requestMethod;
|
||||||
|
|
||||||
|
@Schema(description = "请求路径", example = "/gateway/api/user/query")
|
||||||
|
private String requestPath;
|
||||||
|
|
||||||
|
@Schema(description = "查询参数(JSON)")
|
||||||
|
private String requestQuery;
|
||||||
|
|
||||||
|
@Schema(description = "请求头(JSON)")
|
||||||
|
private String requestHeaders;
|
||||||
|
|
||||||
|
@Schema(description = "请求体(JSON)")
|
||||||
|
private String requestBody;
|
||||||
|
|
||||||
|
@Schema(description = "响应 HTTP 状态", example = "200")
|
||||||
|
private Integer responseStatus;
|
||||||
|
|
||||||
|
@Schema(description = "响应提示", example = "OK")
|
||||||
|
private String responseMessage;
|
||||||
|
|
||||||
|
@Schema(description = "响应体(JSON)")
|
||||||
|
private String responseBody;
|
||||||
|
|
||||||
|
@Schema(description = "访问状态", example = "0")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "错误码", example = "DAT-001")
|
||||||
|
private String errorCode;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息", example = "API 调用失败")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Schema(description = "异常堆栈")
|
||||||
|
private String exceptionStack;
|
||||||
|
|
||||||
|
@Schema(description = "客户端 IP", example = "192.168.0.10")
|
||||||
|
private String clientIp;
|
||||||
|
|
||||||
|
@Schema(description = "User-Agent")
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", example = "1")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "请求耗时(毫秒)", example = "123")
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
|
@Schema(description = "请求时间")
|
||||||
|
private LocalDateTime requestTime;
|
||||||
|
|
||||||
|
@Schema(description = "响应时间")
|
||||||
|
private LocalDateTime responseTime;
|
||||||
|
|
||||||
|
@Schema(description = "执行步骤(JSON)")
|
||||||
|
private String stepResults;
|
||||||
|
|
||||||
|
@Schema(description = "额外调试信息(JSON)")
|
||||||
|
private String extra;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本对比 Response VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本对比 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ApiVersionCompareRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "源版本 ID", example = "1001")
|
||||||
|
private Long sourceVersionId;
|
||||||
|
|
||||||
|
@Schema(description = "源版本号", example = "2")
|
||||||
|
private Integer sourceVersionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "源版本描述")
|
||||||
|
private String sourceDescription;
|
||||||
|
|
||||||
|
@Schema(description = "源版本操作人")
|
||||||
|
private String sourceOperator;
|
||||||
|
|
||||||
|
@Schema(description = "源版本创建时间")
|
||||||
|
private LocalDateTime sourceCreateTime;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本 ID", example = "1002")
|
||||||
|
private Long targetVersionId;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本号", example = "3")
|
||||||
|
private Integer targetVersionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本描述")
|
||||||
|
private String targetDescription;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本操作人")
|
||||||
|
private String targetOperator;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本创建时间")
|
||||||
|
private LocalDateTime targetCreateTime;
|
||||||
|
|
||||||
|
@Schema(description = "源版本快照")
|
||||||
|
private ApiDefinitionSaveReqVO sourceSnapshot;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本快照")
|
||||||
|
private ApiDefinitionSaveReqVO targetSnapshot;
|
||||||
|
|
||||||
|
@Schema(description = "两者是否完全一致")
|
||||||
|
private Boolean same;
|
||||||
|
|
||||||
|
@Schema(description = "字段差异列表")
|
||||||
|
private List<FieldDiff> differences;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FieldDiff {
|
||||||
|
|
||||||
|
@Schema(description = "差异字段路径", example = "/steps[0]/targetEndpoint")
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
@Schema(description = "源版本值")
|
||||||
|
private String sourceValue;
|
||||||
|
|
||||||
|
@Schema(description = "目标版本值")
|
||||||
|
private String targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史创建 Request VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本历史创建 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ApiVersionCreateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "API 定义 ID", required = true, example = "1024")
|
||||||
|
@NotNull(message = "API 定义 ID 不能为空")
|
||||||
|
private Long apiId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号", required = true, example = "v1.0.0")
|
||||||
|
@NotBlank(message = "版本号不能为空")
|
||||||
|
private String versionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "版本描述", example = "初始版本")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本详情 Response VO。
|
||||||
|
* 包含完整的 API 定义快照数据。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本详情 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ApiVersionDetailRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "主键", example = "1024")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "API 定义 ID", example = "1001")
|
||||||
|
private Long apiId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号", example = "1")
|
||||||
|
private Integer versionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "版本描述", example = "初始版本")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "是否为当前版本", example = "true")
|
||||||
|
private Boolean isCurrent;
|
||||||
|
|
||||||
|
@Schema(description = "操作人", example = "admin")
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
@Schema(description = "API 定义快照数据")
|
||||||
|
private ApiDefinitionSaveReqVO snapshotData;
|
||||||
|
|
||||||
|
@Schema(description = "创建者", example = "admin")
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", example = "1")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史分页查询 VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本历史分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
|
public class ApiVersionPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "API 定义 ID", required = true, example = "1024")
|
||||||
|
private Long apiId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号", example = "1")
|
||||||
|
private Integer versionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间区间")
|
||||||
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
|
private LocalDateTime[] createTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史 Response VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本历史 Response VO")
|
||||||
|
@Data
|
||||||
|
public class ApiVersionRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "主键", example = "1024")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "API 定义 ID", example = "1001")
|
||||||
|
private Long apiId;
|
||||||
|
|
||||||
|
@Schema(description = "版本号", example = "1")
|
||||||
|
private Integer versionNumber;
|
||||||
|
|
||||||
|
@Schema(description = "版本描述", example = "初始版本")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Schema(description = "是否为当前版本", example = "true")
|
||||||
|
private Boolean isCurrent;
|
||||||
|
|
||||||
|
@Schema(description = "操作人", example = "admin")
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
@Schema(description = "创建者", example = "admin")
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", example = "1")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.zt.plat.module.databus.controller.admin.gateway.vo.version;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本回滚 Request VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - API 版本回滚 Request VO")
|
||||||
|
@Data
|
||||||
|
public class ApiVersionRollbackReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "待回滚的版本 ID", required = true, example = "1024")
|
||||||
|
@NotNull(message = "版本 ID 不能为空")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "回滚备注", example = "回滚到版本 v5")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志数据对象。
|
||||||
|
*
|
||||||
|
* <p>用于记录 API 编排网关的请求与响应详情,便于审计与问题排查。</p>
|
||||||
|
*/
|
||||||
|
@TableName("databus_api_access_log")
|
||||||
|
@KeySequence("databus_api_access_log_seq")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ApiAccessLogDO extends TenantBaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求追踪标识,对应 {@link com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext#getRequestId()}
|
||||||
|
*/
|
||||||
|
private String traceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 编码
|
||||||
|
*/
|
||||||
|
private String apiCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本
|
||||||
|
*/
|
||||||
|
private String apiVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 方法
|
||||||
|
*/
|
||||||
|
private String requestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求路径
|
||||||
|
*/
|
||||||
|
private String requestPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询参数(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String requestQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头信息(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String requestHeaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String requestBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应 HTTP 状态码
|
||||||
|
*/
|
||||||
|
private Integer responseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应提示信息
|
||||||
|
*/
|
||||||
|
private String responseMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应体(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String responseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务错误码
|
||||||
|
*/
|
||||||
|
private String errorCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息
|
||||||
|
*/
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常堆栈
|
||||||
|
*/
|
||||||
|
private String exceptionStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端 IP
|
||||||
|
*/
|
||||||
|
private String clientIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-Agent
|
||||||
|
*/
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求耗时(毫秒)
|
||||||
|
*/
|
||||||
|
private Long duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime requestTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime responseTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行步骤结果(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String stepResults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 额外调试信息(JSON 字符串)
|
||||||
|
*/
|
||||||
|
private String extra;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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 版本历史数据对象
|
||||||
|
*
|
||||||
|
* <p>每次修改 API 配置时自动创建新版本记录,支持完整的版本历史追溯和回滚。</p>
|
||||||
|
* <p>版本号自动递增,不可删除,保留完整的历史记录链。</p>
|
||||||
|
*/
|
||||||
|
@TableName("databus_api_version")
|
||||||
|
@KeySequence("databus_api_version_seq")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ApiVersionDO extends TenantBaseDO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 定义 ID
|
||||||
|
*/
|
||||||
|
private Long apiId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号(自动递增,从 1 开始)
|
||||||
|
*/
|
||||||
|
private Integer versionNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 完整定义快照(JSON 格式,包含 definition、steps、transforms 等)
|
||||||
|
*/
|
||||||
|
private String snapshotData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本描述/变更说明
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为当前版本(最新使用的版本)
|
||||||
|
*/
|
||||||
|
private Boolean isCurrent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作人
|
||||||
|
*/
|
||||||
|
private String operator;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.zt.plat.module.databus.dal.mapper.gateway;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
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.version.ApiVersionPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史 Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ApiVersionMapper extends BaseMapperX<ApiVersionDO> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询版本历史
|
||||||
|
*/
|
||||||
|
default PageResult<ApiVersionDO> selectPage(ApiVersionPageReqVO reqVO) {
|
||||||
|
return selectPage(reqVO, new LambdaQueryWrapperX<ApiVersionDO>()
|
||||||
|
.eqIfPresent(ApiVersionDO::getApiId, reqVO.getApiId())
|
||||||
|
.eqIfPresent(ApiVersionDO::getVersionNumber, reqVO.getVersionNumber())
|
||||||
|
.betweenIfPresent(ApiVersionDO::getCreateTime, reqVO.getCreateTime())
|
||||||
|
.orderByDesc(ApiVersionDO::getVersionNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定 API 的所有版本历史
|
||||||
|
*/
|
||||||
|
default List<ApiVersionDO> selectListByApiId(Long apiId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||||
|
.eq(ApiVersionDO::getApiId, apiId)
|
||||||
|
.orderByDesc(ApiVersionDO::getVersionNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定 API 的当前版本
|
||||||
|
*/
|
||||||
|
default ApiVersionDO selectCurrentByApiId(Long apiId) {
|
||||||
|
return selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||||
|
.eq(ApiVersionDO::getApiId, apiId)
|
||||||
|
.eq(ApiVersionDO::getIsCurrent, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定 API 的最大版本号
|
||||||
|
*/
|
||||||
|
default Integer selectMaxVersionNumber(Long apiId) {
|
||||||
|
ApiVersionDO maxVersion = selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||||
|
.eq(ApiVersionDO::getApiId, apiId)
|
||||||
|
.orderByDesc(ApiVersionDO::getVersionNumber)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return maxVersion != null ? maxVersion.getVersionNumber() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将所有版本标记为非当前版本
|
||||||
|
*/
|
||||||
|
default void markAllAsNotCurrent(Long apiId) {
|
||||||
|
UpdateWrapper<ApiVersionDO> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("api_id", apiId)
|
||||||
|
.set("is_current", false);
|
||||||
|
update(null, updateWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定版本
|
||||||
|
*/
|
||||||
|
default ApiVersionDO selectByApiIdAndVersionNumber(Long apiId, Integer versionNumber) {
|
||||||
|
return selectOne(new LambdaQueryWrapperX<ApiVersionDO>()
|
||||||
|
.eq(ApiVersionDO::getApiId, apiId)
|
||||||
|
.eq(ApiVersionDO::getVersionNumber, versionNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.zt.plat.module.databus.dal.mysql.gateway;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
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.accesslog.ApiAccessLogPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface ApiAccessLogMapper extends BaseMapperX<ApiAccessLogDO> {
|
||||||
|
|
||||||
|
default PageResult<ApiAccessLogDO> selectPage(ApiAccessLogPageReqVO reqVO) {
|
||||||
|
LambdaQueryWrapperX<ApiAccessLogDO> query = new LambdaQueryWrapperX<ApiAccessLogDO>()
|
||||||
|
.likeIfPresent(ApiAccessLogDO::getTraceId, reqVO.getTraceId())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getApiCode, reqVO.getApiCode())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getApiVersion, reqVO.getApiVersion())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getRequestMethod, reqVO.getRequestMethod())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getResponseStatus, reqVO.getResponseStatus())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getStatus, reqVO.getStatus())
|
||||||
|
.likeIfPresent(ApiAccessLogDO::getClientIp, reqVO.getClientIp())
|
||||||
|
.eqIfPresent(ApiAccessLogDO::getTenantId, reqVO.getTenantId())
|
||||||
|
.likeIfPresent(ApiAccessLogDO::getRequestPath, reqVO.getRequestPath());
|
||||||
|
if (ArrayUtil.isNotEmpty(reqVO.getRequestTime()) && reqVO.getRequestTime().length == 2) {
|
||||||
|
query.between(ApiAccessLogDO::getRequestTime, reqVO.getRequestTime()[0], reqVO.getRequestTime()[1]);
|
||||||
|
}
|
||||||
|
return selectPage(reqVO, query.orderByDesc(ApiAccessLogDO::getRequestTime)
|
||||||
|
.orderByDesc(ApiAccessLogDO::getId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,12 +10,13 @@ import lombok.Getter;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum ApiStatusEnum {
|
public enum ApiStatusEnum {
|
||||||
|
|
||||||
DRAFT(0),
|
DRAFT(0, "草稿"),
|
||||||
ONLINE(1),
|
ONLINE(1, "已上线"),
|
||||||
OFFLINE(2),
|
OFFLINE(2, "已下线"),
|
||||||
DEPRECATED(3);
|
DEPRECATED(3, "已废弃");
|
||||||
|
|
||||||
private final int status;
|
private final int status;
|
||||||
|
private final String label;
|
||||||
|
|
||||||
public static boolean isOnline(Integer status) {
|
public static boolean isOnline(Integer status) {
|
||||||
return status != null && status == ONLINE.status;
|
return status != null && status == ONLINE.status;
|
||||||
@@ -25,4 +26,21 @@ public enum ApiStatusEnum {
|
|||||||
return status != null && status == DEPRECATED.status;
|
return status != null && status == DEPRECATED.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ApiStatusEnum fromStatus(Integer status) {
|
||||||
|
if (status == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (ApiStatusEnum value : values()) {
|
||||||
|
if (value.status == status) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String labelOf(Integer status) {
|
||||||
|
ApiStatusEnum value = fromStatus(status);
|
||||||
|
return value != null ? value.label : "未知";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,46 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
import reactor.netty.resources.ConnectionProvider;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class GatewayWebClientConfiguration {
|
public class GatewayWebClientConfiguration {
|
||||||
|
|
||||||
private final int maxInMemorySize;
|
private final int maxInMemorySize;
|
||||||
|
private final long maxIdleTimeMillis;
|
||||||
|
private final long evictInBackgroundMillis;
|
||||||
|
private final ReactorClientHttpConnector httpConnector;
|
||||||
|
|
||||||
public GatewayWebClientConfiguration(
|
public GatewayWebClientConfiguration(
|
||||||
@Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize) {
|
@Value("${databus.gateway.web-client.max-in-memory-size:20971520}") int maxInMemorySize,
|
||||||
|
@Value("${databus.gateway.web-client.max-idle-time:45000}") long maxIdleTimeMillis,
|
||||||
|
@Value("${databus.gateway.web-client.evict-in-background-interval:20000}") long evictInBackgroundMillis) {
|
||||||
this.maxInMemorySize = maxInMemorySize;
|
this.maxInMemorySize = maxInMemorySize;
|
||||||
|
this.maxIdleTimeMillis = maxIdleTimeMillis > 0 ? maxIdleTimeMillis : 45000L;
|
||||||
|
this.evictInBackgroundMillis = Math.max(evictInBackgroundMillis, 0L);
|
||||||
|
this.httpConnector = buildConnector();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public WebClientCustomizer gatewayWebClientCustomizer() {
|
public WebClientCustomizer gatewayWebClientCustomizer() {
|
||||||
return builder -> builder.codecs(configurer ->
|
return builder -> builder
|
||||||
configurer.defaultCodecs().maxInMemorySize(maxInMemorySize));
|
.clientConnector(httpConnector)
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(maxInMemorySize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReactorClientHttpConnector buildConnector() {
|
||||||
|
ConnectionProvider.Builder providerBuilder = ConnectionProvider.builder("databus-gateway")
|
||||||
|
.maxIdleTime(Duration.ofMillis(maxIdleTimeMillis));
|
||||||
|
if (evictInBackgroundMillis > 0) {
|
||||||
|
providerBuilder.evictInBackground(Duration.ofMillis(evictInBackgroundMillis));
|
||||||
|
}
|
||||||
|
ConnectionProvider provider = providerBuilder.build();
|
||||||
|
HttpClient httpClient = HttpClient.create(provider).compress(true);
|
||||||
|
return new ReactorClientHttpConnector(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||||
|
|
||||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
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.model.ApiInvocationContext;
|
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.integration.core.MessagingTemplate;
|
import org.springframework.integration.core.MessagingTemplate;
|
||||||
@@ -36,6 +37,24 @@ public class ApiFlowDispatcher {
|
|||||||
return (ApiInvocationContext) reply.getPayload();
|
return (ApiInvocationContext) reply.getPayload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ApiInvocationContext dispatchWithAggregate(ApiDefinitionAggregate aggregate, ApiInvocationContext context) {
|
||||||
|
IntegrationFlowManager.DebugFlowHandle handle = integrationFlowManager.obtainDebugHandle(aggregate);
|
||||||
|
MessageChannel channel = handle.channel();
|
||||||
|
Message<ApiInvocationContext> message = MessageBuilder.withPayload(context)
|
||||||
|
.setHeader("apiCode", aggregate.getDefinition().getApiCode())
|
||||||
|
.setHeader("version", aggregate.getDefinition().getVersion())
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
Message<?> reply = messagingTemplate.sendAndReceive(channel, message);
|
||||||
|
if (reply == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(API_FLOW_NO_REPLY, aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||||
|
}
|
||||||
|
return (ApiInvocationContext) reply.getPayload();
|
||||||
|
} finally {
|
||||||
|
integrationFlowManager.releaseDebugHandle(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MessageChannel requireInputChannel(String apiCode, String version) {
|
private MessageChannel requireInputChannel(String apiCode, String version) {
|
||||||
// 未命中时,进行一次兜底补偿查询
|
// 未命中时,进行一次兜底补偿查询
|
||||||
return integrationFlowManager.locateInputChannel(apiCode, version)
|
return integrationFlowManager.locateInputChannel(apiCode, version)
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
package com.zt.plat.module.databus.framework.integration.gateway.core;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiInvocationContext;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 API 调用上下文持久化为访问日志。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ApiGatewayAccessLogger {
|
||||||
|
|
||||||
|
public static final String ATTR_LOG_ID = "ApiAccessLogId";
|
||||||
|
public static final String ATTR_EXCEPTION_STACK = "ApiAccessLogExceptionStack";
|
||||||
|
|
||||||
|
private static final int MAX_TEXT_LENGTH = 4000;
|
||||||
|
|
||||||
|
private final ApiAccessLogService apiAccessLogService;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在分发前记录请求信息。
|
||||||
|
*/
|
||||||
|
public void onRequest(ApiInvocationContext context) {
|
||||||
|
try {
|
||||||
|
ApiAccessLogDO logDO = new ApiAccessLogDO();
|
||||||
|
logDO.setTraceId(context.getRequestId());
|
||||||
|
logDO.setApiCode(context.getApiCode());
|
||||||
|
logDO.setApiVersion(context.getApiVersion());
|
||||||
|
logDO.setRequestMethod(context.getHttpMethod());
|
||||||
|
logDO.setRequestPath(context.getRequestPath());
|
||||||
|
logDO.setRequestQuery(toJson(context.getRequestQueryParams()));
|
||||||
|
logDO.setRequestHeaders(toJson(context.getRequestHeaders()));
|
||||||
|
logDO.setRequestBody(toJson(context.getRequestBody()));
|
||||||
|
logDO.setClientIp(firstNonBlank(context.getClientIp(),
|
||||||
|
GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), "X-Forwarded-For")));
|
||||||
|
logDO.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
||||||
|
logDO.setStatus(3); // 默认未知
|
||||||
|
logDO.setRequestTime(toLocalDateTime(context.getRequestTime()));
|
||||||
|
logDO.setTenantId(parseTenantId(context.getTenantId()));
|
||||||
|
Long logId = apiAccessLogService.create(logDO);
|
||||||
|
context.getAttributes().put(ATTR_LOG_ID, logId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("记录 API 访问日志开始阶段失败, traceId={}", context.getRequestId(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录异常堆栈,便于后续写入日志。
|
||||||
|
*/
|
||||||
|
public void onException(ApiInvocationContext context, Throwable throwable) {
|
||||||
|
if (throwable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.getAttributes().put(ATTR_EXCEPTION_STACK, buildStackTrace(throwable));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在分发完成后补全日志信息。
|
||||||
|
*/
|
||||||
|
public void onResponse(ApiInvocationContext context) {
|
||||||
|
Long logId = getLogId(context);
|
||||||
|
if (logId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ApiAccessLogDO update = new ApiAccessLogDO();
|
||||||
|
update.setId(logId);
|
||||||
|
update.setResponseStatus(context.getResponseStatus());
|
||||||
|
update.setResponseMessage(context.getResponseMessage());
|
||||||
|
update.setResponseBody(toJson(context.getResponseBody()));
|
||||||
|
update.setStatus(resolveStatus(context.getResponseStatus()));
|
||||||
|
update.setErrorCode(extractErrorCode(context.getResponseBody()));
|
||||||
|
update.setErrorMessage(resolveErrorMessage(context));
|
||||||
|
update.setExceptionStack((String) context.getAttributes().get(ATTR_EXCEPTION_STACK));
|
||||||
|
update.setStepResults(toJson(context.getStepResults()));
|
||||||
|
update.setExtra(toJson(buildExtra(context)));
|
||||||
|
update.setResponseTime(LocalDateTime.now());
|
||||||
|
update.setDuration(calculateDuration(context));
|
||||||
|
apiAccessLogService.update(update);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("记录 API 访问日志结束阶段失败, traceId={}, logId={}", context.getRequestId(), logId, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getLogId(ApiInvocationContext context) {
|
||||||
|
Object value = context.getAttributes().get(ATTR_LOG_ID);
|
||||||
|
if (value instanceof Long) {
|
||||||
|
return (Long) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number number) {
|
||||||
|
return number.longValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long calculateDuration(ApiInvocationContext context) {
|
||||||
|
Instant start = context.getRequestTime();
|
||||||
|
if (start == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Duration.between(start, Instant.now()).toMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveStatus(Integer httpStatus) {
|
||||||
|
if (httpStatus == null) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (httpStatus >= 200 && httpStatus < 400) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (httpStatus >= 400 && httpStatus < 500) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (httpStatus >= 500) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveErrorMessage(ApiInvocationContext context) {
|
||||||
|
if (StringUtils.hasText(context.getResponseMessage())) {
|
||||||
|
return truncate(context.getResponseMessage());
|
||||||
|
}
|
||||||
|
Object responseBody = context.getResponseBody();
|
||||||
|
if (responseBody instanceof Map<?, ?> map) {
|
||||||
|
Object message = firstNonNull(map.get("errorMessage"), map.get("message"));
|
||||||
|
if (message != null) {
|
||||||
|
return truncate(String.valueOf(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractErrorCode(Object responseBody) {
|
||||||
|
if (responseBody instanceof Map<?, ?> map) {
|
||||||
|
Object errorCode = firstNonNull(map.get("errorCode"), map.get("code"));
|
||||||
|
return errorCode == null ? null : truncate(String.valueOf(errorCode));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildExtra(ApiInvocationContext context) {
|
||||||
|
Map<String, Object> extra = new HashMap<>();
|
||||||
|
if (!CollectionUtils.isEmpty(context.getVariables())) {
|
||||||
|
extra.put("variables", context.getVariables());
|
||||||
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(context.getAttributes())) {
|
||||||
|
Map<String, Object> attributes = new HashMap<>(context.getAttributes());
|
||||||
|
attributes.remove(ATTR_LOG_ID);
|
||||||
|
attributes.remove(ATTR_EXCEPTION_STACK);
|
||||||
|
if (!attributes.isEmpty()) {
|
||||||
|
extra.put("attributes", attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(extra)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJson(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof String str) {
|
||||||
|
return truncate(str);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return truncate(objectMapper.writeValueAsString(value));
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
return truncate(String.valueOf(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String truncate(String text) {
|
||||||
|
if (!StringUtils.hasText(text)) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
if (text.length() <= MAX_TEXT_LENGTH) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text.substring(0, MAX_TEXT_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long parseTenantId(String tenantId) {
|
||||||
|
if (!StringUtils.hasText(tenantId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(tenantId.trim());
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime toLocalDateTime(Instant instant) {
|
||||||
|
if (instant == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildStackTrace(Throwable throwable) {
|
||||||
|
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
|
||||||
|
throwable.printStackTrace(pw);
|
||||||
|
return truncate(sw.toString());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return throwable.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String value : values) {
|
||||||
|
if (StringUtils.hasText(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object firstNonNull(Object... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Object value : values) {
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,15 @@ package com.zt.plat.module.databus.framework.integration.gateway.core;
|
|||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.zt.plat.framework.common.exception.ServiceException;
|
import com.zt.plat.framework.common.exception.ServiceException;
|
||||||
|
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.ApiGatewayInvokeReqVO;
|
||||||
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
import com.zt.plat.module.databus.framework.integration.config.ApiGatewayProperties;
|
||||||
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.model.ApiGatewayResponse;
|
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.model.ApiInvocationContext;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
|
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewayJwtResolver;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
|
import com.zt.plat.module.databus.framework.integration.gateway.security.GatewaySecurityFilter;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
@@ -23,6 +26,9 @@ import java.lang.reflect.Array;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.API_DEFINITION_NOT_FOUND;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Orchestrates API portal request mapping, dispatch and response building so that
|
* Orchestrates API portal request mapping, dispatch and response building so that
|
||||||
@@ -38,12 +44,17 @@ public class ApiGatewayExecutionService {
|
|||||||
private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri";
|
private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri";
|
||||||
private static final String HEADER_REQUEST_PARAMS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestParams";
|
private static final String HEADER_REQUEST_PARAMS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestParams";
|
||||||
private static final String HEADER_QUERY_STRING = org.springframework.integration.http.HttpHeaders.PREFIX + "queryString";
|
private static final String HEADER_QUERY_STRING = org.springframework.integration.http.HttpHeaders.PREFIX + "queryString";
|
||||||
|
private static final String HEADER_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
|
||||||
|
private static final String LOCAL_DEBUG_REMOTE_ADDRESS = "127.0.0.1";
|
||||||
|
private static final String ATTR_DEBUG_INVOKE = "gatewayDebugInvoke";
|
||||||
|
|
||||||
private final ApiGatewayRequestMapper requestMapper;
|
private final ApiGatewayRequestMapper requestMapper;
|
||||||
private final ApiFlowDispatcher apiFlowDispatcher;
|
private final ApiFlowDispatcher apiFlowDispatcher;
|
||||||
private final ApiGatewayErrorProcessor errorProcessor;
|
private final ApiGatewayErrorProcessor errorProcessor;
|
||||||
private final ApiGatewayProperties properties;
|
private final ApiGatewayProperties properties;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final ApiGatewayAccessLogger accessLogger;
|
||||||
|
private final ApiDefinitionService apiDefinitionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a raw HTTP message (as provided by Spring Integration) into a context message.
|
* Maps a raw HTTP message (as provided by Spring Integration) into a context message.
|
||||||
@@ -62,26 +73,42 @@ public class ApiGatewayExecutionService {
|
|||||||
*/
|
*/
|
||||||
public ApiInvocationContext dispatch(Message<ApiInvocationContext> message) {
|
public ApiInvocationContext dispatch(Message<ApiInvocationContext> message) {
|
||||||
ApiInvocationContext context = message.getPayload();
|
ApiInvocationContext context = message.getPayload();
|
||||||
|
accessLogger.onRequest(context);
|
||||||
|
ApiInvocationContext responseContext;
|
||||||
|
ApiDefinitionAggregate debugAggregate = null;
|
||||||
try {
|
try {
|
||||||
return apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
|
if (Boolean.TRUE.equals(context.getAttributes().get(ATTR_DEBUG_INVOKE))) {
|
||||||
|
debugAggregate = resolveDebugAggregate(context);
|
||||||
|
}
|
||||||
|
if (debugAggregate != null) {
|
||||||
|
responseContext = apiFlowDispatcher.dispatchWithAggregate(debugAggregate, context);
|
||||||
|
} else {
|
||||||
|
responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context);
|
||||||
|
}
|
||||||
} catch (ServiceException ex) {
|
} catch (ServiceException ex) {
|
||||||
errorProcessor.applyServiceException(context, ex);
|
errorProcessor.applyServiceException(context, ex);
|
||||||
|
accessLogger.onException(context, ex);
|
||||||
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage());
|
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException: {}", context.getApiCode(), context.getApiVersion(), ex.getMessage());
|
||||||
return context;
|
responseContext = context;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ServiceException nestedServiceException = errorProcessor.resolveServiceException(ex);
|
ServiceException nestedServiceException = errorProcessor.resolveServiceException(ex);
|
||||||
if (nestedServiceException != null) {
|
if (nestedServiceException != null) {
|
||||||
errorProcessor.applyServiceException(context, nestedServiceException);
|
errorProcessor.applyServiceException(context, nestedServiceException);
|
||||||
|
accessLogger.onException(context, nestedServiceException);
|
||||||
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException(包装异常): {}", context.getApiCode(), context.getApiVersion(), nestedServiceException.getMessage());
|
log.warn("[API-PORTAL] 分发 apiCode={} version={} 时出现 ServiceException(包装异常): {}", context.getApiCode(), context.getApiVersion(), nestedServiceException.getMessage());
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("[API-PORTAL] 包装异常堆栈", ex);
|
log.debug("[API-PORTAL] 包装异常堆栈", ex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorProcessor.applyUnexpectedException(context, ex);
|
errorProcessor.applyUnexpectedException(context, ex);
|
||||||
|
accessLogger.onException(context, ex);
|
||||||
log.error("[API-PORTAL] 分发 apiCode={} version={} 时出现未预期异常", context.getApiCode(), context.getApiVersion(), ex);
|
log.error("[API-PORTAL] 分发 apiCode={} version={} 时出现未预期异常", context.getApiCode(), context.getApiVersion(), ex);
|
||||||
}
|
}
|
||||||
return context;
|
responseContext = context;
|
||||||
|
} finally {
|
||||||
|
accessLogger.onResponse(context);
|
||||||
}
|
}
|
||||||
|
return responseContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,10 +129,20 @@ public class ApiGatewayExecutionService {
|
|||||||
ApiInvocationContext context = mappedMessage.getPayload();
|
ApiInvocationContext context = mappedMessage.getPayload();
|
||||||
// Ensure query parameters & headers from debug payload are reflected after mapping.
|
// Ensure query parameters & headers from debug payload are reflected after mapping.
|
||||||
mergeDebugMetadata(context, reqVO);
|
mergeDebugMetadata(context, reqVO);
|
||||||
|
context.getAttributes().put(ATTR_DEBUG_INVOKE, Boolean.TRUE);
|
||||||
ApiInvocationContext responseContext = dispatch(mappedMessage);
|
ApiInvocationContext responseContext = dispatch(mappedMessage);
|
||||||
return buildResponseEntity(responseContext);
|
return buildResponseEntity(responseContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ApiDefinitionAggregate resolveDebugAggregate(ApiInvocationContext context) {
|
||||||
|
Optional<ApiDefinitionAggregate> activeDefinition = apiDefinitionService.findByCodeAndVersion(context.getApiCode(), context.getApiVersion());
|
||||||
|
if (activeDefinition.isPresent()) {
|
||||||
|
return activeDefinition.get();
|
||||||
|
}
|
||||||
|
return apiDefinitionService.findByCodeAndVersionIncludingInactive(context.getApiCode(), context.getApiVersion())
|
||||||
|
.orElseThrow(() -> ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
private Message<?> buildDebugMessage(ApiGatewayInvokeReqVO reqVO) {
|
private Message<?> buildDebugMessage(ApiGatewayInvokeReqVO reqVO) {
|
||||||
Object payload = preparePayload(reqVO.getPayload());
|
Object payload = preparePayload(reqVO.getPayload());
|
||||||
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload);
|
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload);
|
||||||
@@ -115,7 +152,7 @@ public class ApiGatewayExecutionService {
|
|||||||
"version", reqVO.getVersion()
|
"version", reqVO.getVersion()
|
||||||
);
|
);
|
||||||
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
builder.setHeader(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
||||||
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, HttpMethod.POST.name());
|
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, HttpMethod.POST.name());
|
||||||
|
|
||||||
String basePath = normalizeBasePath(properties.getBasePath());
|
String basePath = normalizeBasePath(properties.getBasePath());
|
||||||
String rawQuery = buildQueryString(reqVO.getQueryParams());
|
String rawQuery = buildQueryString(reqVO.getQueryParams());
|
||||||
@@ -125,10 +162,11 @@ public class ApiGatewayExecutionService {
|
|||||||
}
|
}
|
||||||
builder.setHeader(HEADER_REQUEST_URI, requestUri);
|
builder.setHeader(HEADER_REQUEST_URI, requestUri);
|
||||||
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, requestUri);
|
builder.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, requestUri);
|
||||||
|
builder.setHeader(HEADER_REMOTE_ADDRESS, LOCAL_DEBUG_REMOTE_ADDRESS);
|
||||||
|
|
||||||
Map<String, Object> requestHeaders = new LinkedHashMap<>();
|
Map<String, Object> requestHeaders = new LinkedHashMap<>();
|
||||||
if (reqVO.getHeaders() != null) {
|
if (reqVO.getHeaders() != null) {
|
||||||
reqVO.getHeaders().forEach(requestHeaders::put);
|
requestHeaders.putAll(reqVO.getHeaders());
|
||||||
}
|
}
|
||||||
normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
|
normalizeJwtHeaders(requestHeaders, reqVO.getQueryParams());
|
||||||
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
requestHeaders.putIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class ApiGatewayRequestMapper {
|
|||||||
|
|
||||||
private static final String HEADER_REQUEST_HEADERS = org.springframework.integration.http.HttpHeaders.PREFIX + "requestHeaders";
|
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";
|
private static final String HEADER_REQUEST_URI = org.springframework.integration.http.HttpHeaders.PREFIX + "requestUri";
|
||||||
|
private static final String HEADER_REMOTE_ADDRESS = org.springframework.integration.http.HttpHeaders.PREFIX + "remoteAddress";
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ApiInvocationContext map(Object payload, Map<String, Object> headers) {
|
public ApiInvocationContext map(Object payload, Map<String, Object> headers) {
|
||||||
@@ -87,6 +88,8 @@ public class ApiGatewayRequestMapper {
|
|||||||
context.getRequestHeaders().putIfAbsent(key, normalized);
|
context.getRequestHeaders().putIfAbsent(key, normalized);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
context.setUserAgent(GatewayHeaderUtils.findFirstHeaderValue(context.getRequestHeaders(), HttpHeaders.USER_AGENT));
|
||||||
|
context.setClientIp(resolveClientIp(headers, context.getRequestHeaders()));
|
||||||
populateQueryParams(headers, context, originalRequestUri);
|
populateQueryParams(headers, context, originalRequestUri);
|
||||||
if (properties.isEnableTenantHeader()) {
|
if (properties.isEnableTenantHeader()) {
|
||||||
Object tenantHeaderValue = context.getRequestHeaders().get(properties.getTenantHeader());
|
Object tenantHeaderValue = context.getRequestHeaders().get(properties.getTenantHeader());
|
||||||
@@ -315,4 +318,26 @@ public class ApiGatewayRequestMapper {
|
|||||||
}
|
}
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveClientIp(Map<String, Object> headers, Map<String, Object> requestHeaders) {
|
||||||
|
String forwarded = GatewayHeaderUtils.findFirstHeaderValue(requestHeaders, "X-Forwarded-For");
|
||||||
|
if (StringUtils.hasText(forwarded)) {
|
||||||
|
int idx = forwarded.indexOf(',');
|
||||||
|
return idx >= 0 ? forwarded.substring(0, idx).trim() : forwarded;
|
||||||
|
}
|
||||||
|
String realIp = GatewayHeaderUtils.findFirstHeaderValue(requestHeaders, "X-Real-IP");
|
||||||
|
if (StringUtils.hasText(realIp)) {
|
||||||
|
return realIp;
|
||||||
|
}
|
||||||
|
Object remote = headers.get(HEADER_REMOTE_ADDRESS);
|
||||||
|
if (remote != null) {
|
||||||
|
String candidate = remote.toString();
|
||||||
|
int slash = candidate.indexOf('/') >= 0 ? candidate.indexOf('/') : candidate.indexOf(':');
|
||||||
|
if (slash > 0) {
|
||||||
|
candidate = candidate.substring(0, slash);
|
||||||
|
}
|
||||||
|
return candidate.trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +64,33 @@ public class IntegrationFlowManager {
|
|||||||
return Optional.ofNullable(registration.getInputChannel());
|
return Optional.ofNullable(registration.getInputChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DebugFlowHandle obtainDebugHandle(ApiDefinitionAggregate aggregate) {
|
||||||
|
String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||||
|
IntegrationFlowContext.IntegrationFlowRegistration existing = activeRegistrations.get(key);
|
||||||
|
if (existing != null) {
|
||||||
|
return new DebugFlowHandle(existing.getInputChannel(), existing.getId(), false);
|
||||||
|
}
|
||||||
|
ApiFlowRegistration registration = apiFlowAssembler.assemble(aggregate);
|
||||||
|
String debugId = registration.getFlowId() + "#debug#" + UUID.randomUUID();
|
||||||
|
IntegrationFlowContext.IntegrationFlowRegistration debugRegistration = integrationFlowContext.registration(registration.getFlow())
|
||||||
|
.id(debugId)
|
||||||
|
.register();
|
||||||
|
log.debug("[API-PORTAL] 临时注册调试流程 {} 对应 apiCode={} version={}", debugId, aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||||
|
return new DebugFlowHandle(debugRegistration.getInputChannel(), debugRegistration.getId(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseDebugHandle(DebugFlowHandle handle) {
|
||||||
|
if (handle == null || !handle.temporary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
integrationFlowContext.remove(handle.registrationId);
|
||||||
|
log.debug("[API-PORTAL] 已移除调试流程 {}", handle.registrationId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("移除调试流程 {} 失败", handle.registrationId, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void registerFlow(ApiDefinitionAggregate aggregate) {
|
private void registerFlow(ApiDefinitionAggregate aggregate) {
|
||||||
String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
String key = key(aggregate.getDefinition().getApiCode(), aggregate.getDefinition().getVersion());
|
||||||
deregisterByKey(key);
|
deregisterByKey(key);
|
||||||
@@ -93,4 +121,24 @@ public class IntegrationFlowManager {
|
|||||||
private String key(String apiCode, String version) {
|
private String key(String apiCode, String version) {
|
||||||
return (apiCode + ":" + version).toLowerCase();
|
return (apiCode + ":" + version).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class DebugFlowHandle {
|
||||||
|
private final MessageChannel channel;
|
||||||
|
private final String registrationId;
|
||||||
|
private final boolean temporary;
|
||||||
|
|
||||||
|
private DebugFlowHandle(MessageChannel channel, String registrationId, boolean temporary) {
|
||||||
|
this.channel = channel;
|
||||||
|
this.registrationId = registrationId;
|
||||||
|
this.temporary = temporary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageChannel channel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean temporary() {
|
||||||
|
return temporary;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ public class ApiInvocationContext {
|
|||||||
|
|
||||||
private String tenantId;
|
private String tenantId;
|
||||||
|
|
||||||
|
private String clientIp;
|
||||||
|
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
private String httpMethod;
|
private String httpMethod;
|
||||||
|
|
||||||
private String requestPath;
|
private String requestPath;
|
||||||
@@ -66,6 +70,8 @@ public class ApiInvocationContext {
|
|||||||
copy.apiCode = this.apiCode;
|
copy.apiCode = this.apiCode;
|
||||||
copy.apiVersion = this.apiVersion;
|
copy.apiVersion = this.apiVersion;
|
||||||
copy.tenantId = this.tenantId;
|
copy.tenantId = this.tenantId;
|
||||||
|
copy.clientIp = this.clientIp;
|
||||||
|
copy.userAgent = this.userAgent;
|
||||||
copy.httpMethod = this.httpMethod;
|
copy.httpMethod = this.httpMethod;
|
||||||
copy.requestPath = this.requestPath;
|
copy.requestPath = this.requestPath;
|
||||||
copy.requestBody = this.requestBody;
|
copy.requestBody = this.requestBody;
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import org.springframework.web.reactive.function.BodyInserters;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.netty.http.client.PrematureCloseException;
|
||||||
|
import reactor.util.retry.Retry;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -43,15 +46,18 @@ public class HttpStepHandler implements ApiStepHandler {
|
|||||||
private final WebClient.Builder webClientBuilder;
|
private final WebClient.Builder webClientBuilder;
|
||||||
private final ExpressionExecutor expressionExecutor;
|
private final ExpressionExecutor expressionExecutor;
|
||||||
|
|
||||||
|
private static final Duration RETRY_DELAY = Duration.ofMillis(200);
|
||||||
|
private static final int RETRY_ATTEMPTS = 3;
|
||||||
|
|
||||||
private static final Set<String> DEFAULT_FORWARDED_HEADERS = Set.of(
|
private static final Set<String> DEFAULT_FORWARDED_HEADERS = Set.of(
|
||||||
"authorization",
|
"authorization",
|
||||||
"zt-auth-token",
|
"zt-auth-token",
|
||||||
"tenant-id",
|
"tenant-id",
|
||||||
"visit-tenant-id",
|
"visit-tenant-id",
|
||||||
"visit-company-id",
|
"visit-company-id",
|
||||||
"visit-company-name",
|
"visit-company-name",
|
||||||
"visit-dept-id",
|
"visit-dept-id",
|
||||||
"visit-dept-name"
|
"visit-dept-name"
|
||||||
);
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -106,8 +112,18 @@ public class HttpStepHandler implements ApiStepHandler {
|
|||||||
Map<String, String> headerMap = resolveHeaders(stepDefinition, payload);
|
Map<String, String> headerMap = resolveHeaders(stepDefinition, payload);
|
||||||
Duration timeout = resolveTimeout(stepDefinition);
|
Duration timeout = resolveTimeout(stepDefinition);
|
||||||
WebClient client = webClientBuilder.build();
|
WebClient client = webClientBuilder.build();
|
||||||
WebClient.RequestHeadersSpec<?> requestSpec = buildRequest(client, callSpec, requestPayload, headerMap, supportsBody);
|
final HttpRequestPayload resolvedPayload = requestPayload;
|
||||||
Mono<Object> responseMono = requestSpec.retrieve().bodyToMono(Object.class);
|
boolean finalSupportsBody = supportsBody;
|
||||||
|
Mono<Object> responseMono = Mono.defer(() -> {
|
||||||
|
// 每次订阅(含重试)都会重新构建请求,避免缓存第一次结果
|
||||||
|
HttpRequestPayload payloadForAttempt = resolvedPayload == null
|
||||||
|
? HttpRequestPayload.of(null, Collections.emptyMap())
|
||||||
|
: resolvedPayload;
|
||||||
|
return buildRequest(client, callSpec, payloadForAttempt, headerMap, finalSupportsBody)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(Object.class);
|
||||||
|
});
|
||||||
|
responseMono = applyResilientRetry(responseMono, stepDefinition);
|
||||||
Object response = timeout == null ? responseMono.block() : responseMono.block(timeout);
|
Object response = timeout == null ? responseMono.block() : responseMono.block(timeout);
|
||||||
payload.addStepResult(ApiStepResult.builder()
|
payload.addStepResult(ApiStepResult.builder()
|
||||||
.stepId(stepDefinition.getStep().getId())
|
.stepId(stepDefinition.getStep().getId())
|
||||||
@@ -346,12 +362,12 @@ public class HttpStepHandler implements ApiStepHandler {
|
|||||||
|
|
||||||
private record HttpRequestPayload(Object body, Map<String, Object> queryParams) {
|
private record HttpRequestPayload(Object body, Map<String, Object> queryParams) {
|
||||||
|
|
||||||
private HttpRequestPayload {
|
private HttpRequestPayload {
|
||||||
Map<String, Object> safeQuery = queryParams == null
|
Map<String, Object> safeQuery = queryParams == null
|
||||||
? Collections.emptyMap()
|
? Collections.emptyMap()
|
||||||
: Collections.unmodifiableMap(new LinkedHashMap<>(queryParams));
|
: Collections.unmodifiableMap(new LinkedHashMap<>(queryParams));
|
||||||
queryParams = safeQuery;
|
queryParams = safeQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
static HttpRequestPayload of(Object body, Map<String, Object> queryParams) {
|
static HttpRequestPayload of(Object body, Map<String, Object> queryParams) {
|
||||||
return new HttpRequestPayload(body, queryParams);
|
return new HttpRequestPayload(body, queryParams);
|
||||||
@@ -380,4 +396,37 @@ public class HttpStepHandler implements ApiStepHandler {
|
|||||||
// 所有请求都要传递请求体
|
// 所有请求都要传递请求体
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<Object> applyResilientRetry(Mono<Object> responseMono, ApiStepDefinition stepDefinition) {
|
||||||
|
return responseMono.retryWhen(Retry.fixedDelay(RETRY_ATTEMPTS, RETRY_DELAY)
|
||||||
|
.filter(this::isRetryableException)
|
||||||
|
.doBeforeRetry(signal -> {
|
||||||
|
if (log.isWarnEnabled()) {
|
||||||
|
log.warn("HTTP 步骤 stepId={} 第{}次重试,原因:{}",
|
||||||
|
stepDefinition.getStep().getId(),
|
||||||
|
signal.totalRetriesInARow(),
|
||||||
|
signal.failure() == null ? "未知" : signal.failure().getMessage());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRetryableException(Throwable throwable) {
|
||||||
|
if (throwable == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Throwable cursor = throwable;
|
||||||
|
while (cursor != null) {
|
||||||
|
if (cursor instanceof ServiceException) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cursor instanceof PrematureCloseException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cursor instanceof IOException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cursor = cursor.getCause();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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.accesslog.ApiAccessLogPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志 Service。
|
||||||
|
*/
|
||||||
|
public interface ApiAccessLogService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增访问日志。
|
||||||
|
*
|
||||||
|
* @param logDO 日志信息
|
||||||
|
* @return 日志编号
|
||||||
|
*/
|
||||||
|
Long create(ApiAccessLogDO logDO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新访问日志(仅更新非空字段)。
|
||||||
|
*
|
||||||
|
* @param logDO 日志信息
|
||||||
|
*/
|
||||||
|
void update(ApiAccessLogDO logDO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据编号获取访问日志。
|
||||||
|
*
|
||||||
|
* @param id 日志编号
|
||||||
|
* @return 日志
|
||||||
|
*/
|
||||||
|
ApiAccessLogDO get(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询访问日志。
|
||||||
|
*
|
||||||
|
* @param pageReqVO 查询条件
|
||||||
|
* @return 日志分页
|
||||||
|
*/
|
||||||
|
PageResult<ApiAccessLogDO> getPage(ApiAccessLogPageReqVO pageReqVO);
|
||||||
|
}
|
||||||
@@ -24,6 +24,11 @@ public interface ApiDefinitionService {
|
|||||||
*/
|
*/
|
||||||
Optional<ApiDefinitionAggregate> findByCodeAndVersion(String apiCode, String version);
|
Optional<ApiDefinitionAggregate> findByCodeAndVersion(String apiCode, String version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup API definition regardless of publish status.
|
||||||
|
*/
|
||||||
|
Optional<ApiDefinitionAggregate> findByCodeAndVersionIncludingInactive(String apiCode, String version);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh a specific definition by evicting cache and reloading from DB.
|
* Refresh a specific definition by evicting cache and reloading from DB.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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.version.ApiVersionCompareRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史 Service 接口(备忘录模式)。
|
||||||
|
*/
|
||||||
|
public interface ApiVersionService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获当前 API 配置快照并生成版本记录。
|
||||||
|
*
|
||||||
|
* @param apiId API ID
|
||||||
|
* @param description 版本描述(可为空)
|
||||||
|
* @param operator 操作人
|
||||||
|
* @return 版本 ID
|
||||||
|
*/
|
||||||
|
Long autoCreateVersion(Long apiId, String description, String operator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询版本详情。
|
||||||
|
*
|
||||||
|
* @param id 版本 ID
|
||||||
|
* @return 版本信息
|
||||||
|
*/
|
||||||
|
ApiVersionDO getVersion(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定 API 的版本历史(分页)。
|
||||||
|
*
|
||||||
|
* @param pageReqVO 分页参数
|
||||||
|
* @return 版本列表
|
||||||
|
*/
|
||||||
|
PageResult<ApiVersionDO> getVersionPage(ApiVersionPageReqVO pageReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定 API 的全部版本历史(倒序)。
|
||||||
|
*
|
||||||
|
* @param apiId API ID
|
||||||
|
* @return 版本列表
|
||||||
|
*/
|
||||||
|
List<ApiVersionDO> getVersionListByApiId(Long apiId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回滚至指定版本。
|
||||||
|
*
|
||||||
|
* @param id 版本 ID
|
||||||
|
* @param remark 回滚说明(可为空)
|
||||||
|
*/
|
||||||
|
void rollbackToVersion(Long id, String remark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对比两个版本的差异。
|
||||||
|
*
|
||||||
|
* @param sourceVersionId 源版本 ID
|
||||||
|
* @param targetVersionId 目标版本 ID
|
||||||
|
* @return 差异结果
|
||||||
|
*/
|
||||||
|
ApiVersionCompareRespVO compareVersions(Long sourceVersionId, Long targetVersionId);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.zt.plat.module.databus.service.gateway;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread-local context to control whether API definition updates should capture version snapshots.
|
||||||
|
*/
|
||||||
|
public final class ApiVersionSnapshotContextHolder {
|
||||||
|
|
||||||
|
private static final ThreadLocal<Boolean> SKIP_SNAPSHOT = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private ApiVersionSnapshotContextHolder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark that the current thread should skip automatic version snapshot creation once.
|
||||||
|
*/
|
||||||
|
public static void markSkipOnce() {
|
||||||
|
SKIP_SNAPSHOT.set(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the current thread requested to skip snapshot creation.
|
||||||
|
*/
|
||||||
|
public static boolean shouldSkip() {
|
||||||
|
return Boolean.TRUE.equals(SKIP_SNAPSHOT.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the skip flag from the current thread.
|
||||||
|
*/
|
||||||
|
public static void clear() {
|
||||||
|
SKIP_SNAPSHOT.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.zt.plat.module.databus.service.gateway.impl;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.accesslog.ApiAccessLogPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO;
|
||||||
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiAccessLogMapper;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiAccessLogService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Databus API 访问日志 Service 实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
public class ApiAccessLogServiceImpl implements ApiAccessLogService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApiAccessLogMapper apiAccessLogMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long create(ApiAccessLogDO logDO) {
|
||||||
|
apiAccessLogMapper.insert(logDO);
|
||||||
|
return logDO.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(ApiAccessLogDO logDO) {
|
||||||
|
int rows = apiAccessLogMapper.updateById(logDO);
|
||||||
|
if (rows == 0 && log.isDebugEnabled()) {
|
||||||
|
log.debug("访问日志不存在,无法更新。id={}", logDO.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiAccessLogDO get(Long id) {
|
||||||
|
return apiAccessLogMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<ApiAccessLogDO> getPage(ApiAccessLogPageReqVO pageReqVO) {
|
||||||
|
return apiAccessLogMapper.selectPage(pageReqVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,31 +9,27 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
|
|||||||
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
|
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
|
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
|
||||||
|
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionPageReqVO;
|
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.ApiDefinitionSaveReqVO;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO;
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionStepSaveReqVO;
|
||||||
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO;
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionTransformSaveReqVO;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
import com.zt.plat.module.databus.dal.dataobject.gateway.*;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiFlowPublishDO;
|
import com.zt.plat.module.databus.dal.mysql.gateway.*;
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiPolicyRateLimitDO;
|
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
|
||||||
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
|
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
|
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiFlowPublishMapper;
|
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiPolicyRateLimitMapper;
|
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
|
|
||||||
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
|
|
||||||
import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum;
|
import com.zt.plat.module.databus.enums.gateway.ApiStatusEnum;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiDefinitionAggregate;
|
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.ApiFlowPublication;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiStepDefinition;
|
||||||
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransformDefinition;
|
import com.zt.plat.module.databus.framework.integration.gateway.domain.ApiTransformDefinition;
|
||||||
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||||
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
|
import com.zt.plat.module.databus.service.gateway.ApiVersionService;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiVersionSnapshotContextHolder;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -61,6 +57,7 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
private final ApiFlowPublishMapper apiFlowPublishMapper;
|
private final ApiFlowPublishMapper apiFlowPublishMapper;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
private final ObjectProvider<ApiVersionService> apiVersionServiceProvider;
|
||||||
|
|
||||||
private LoadingCache<String, Optional<ApiDefinitionAggregate>> definitionCache;
|
private LoadingCache<String, Optional<ApiDefinitionAggregate>> definitionCache;
|
||||||
|
|
||||||
@@ -95,6 +92,12 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ApiDefinitionAggregate> findByCodeAndVersionIncludingInactive(String apiCode, String version) {
|
||||||
|
return apiDefinitionMapper.selectByCodeAndVersion(apiCode, version)
|
||||||
|
.map(this::buildAggregate);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ApiDefinitionAggregate> refresh(String apiCode, String version) {
|
public Optional<ApiDefinitionAggregate> refresh(String apiCode, String version) {
|
||||||
String cacheKey = buildCacheKey(apiCode, version);
|
String cacheKey = buildCacheKey(apiCode, version);
|
||||||
@@ -133,6 +136,10 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
|
|
||||||
persistApiLevelTransforms(apiId, reqVO.getApiLevelTransforms());
|
persistApiLevelTransforms(apiId, reqVO.getApiLevelTransforms());
|
||||||
persistSteps(apiId, reqVO.getSteps());
|
persistSteps(apiId, reqVO.getSteps());
|
||||||
|
|
||||||
|
String operator = SecurityFrameworkUtils.getLoginUserNickname();
|
||||||
|
String description = String.format("创建 API (%s)", reqVO.getVersion());
|
||||||
|
apiVersionServiceProvider.getObject().autoCreateVersion(apiId, description, operator);
|
||||||
return apiId;
|
return apiId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,19 +148,35 @@ public class ApiDefinitionServiceImpl implements ApiDefinitionService {
|
|||||||
public void update(ApiDefinitionSaveReqVO reqVO) {
|
public void update(ApiDefinitionSaveReqVO reqVO) {
|
||||||
ApiDefinitionDO existing = ensureExists(reqVO.getId());
|
ApiDefinitionDO existing = ensureExists(reqVO.getId());
|
||||||
|
|
||||||
validateDuplication(reqVO, existing.getId());
|
boolean skipSnapshot = ApiVersionSnapshotContextHolder.shouldSkip();
|
||||||
validateStructure(reqVO);
|
|
||||||
validatePolicies(reqVO);
|
|
||||||
|
|
||||||
ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing);
|
try {
|
||||||
apiDefinitionMapper.updateById(updateObj);
|
validateDuplication(reqVO, existing.getId());
|
||||||
|
validateStructure(reqVO);
|
||||||
|
validatePolicies(reqVO);
|
||||||
|
|
||||||
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
ApiDefinitionDO updateObj = buildDefinitionDO(reqVO, existing);
|
||||||
apiTransformMapper.deleteByApiId(existing.getId());
|
apiDefinitionMapper.updateById(updateObj);
|
||||||
apiStepMapper.deleteByApiId(existing.getId());
|
|
||||||
persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms());
|
invalidateCache(existing.getTenantId(), existing.getApiCode(), existing.getVersion());
|
||||||
persistSteps(existing.getId(), reqVO.getSteps());
|
apiTransformMapper.deleteByApiId(existing.getId());
|
||||||
invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion());
|
apiStepMapper.deleteByApiId(existing.getId());
|
||||||
|
persistApiLevelTransforms(existing.getId(), reqVO.getApiLevelTransforms());
|
||||||
|
persistSteps(existing.getId(), reqVO.getSteps());
|
||||||
|
invalidateCache(updateObj.getTenantId(), updateObj.getApiCode(), updateObj.getVersion());
|
||||||
|
} finally {
|
||||||
|
if (skipSnapshot) {
|
||||||
|
ApiVersionSnapshotContextHolder.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipSnapshot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String operator = SecurityFrameworkUtils.getLoginUserNickname();
|
||||||
|
String description = String.format("更新 API (%s)", reqVO.getVersion());
|
||||||
|
apiVersionServiceProvider.getObject().autoCreateVersion(existing.getId(), description, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package com.zt.plat.module.databus.service.gateway.impl;
|
||||||
|
|
||||||
|
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 com.zt.plat.framework.common.pojo.PageResult;
|
||||||
|
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.convert.ApiDefinitionConvert;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.definition.ApiDefinitionSaveReqVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionCompareRespVO;
|
||||||
|
import com.zt.plat.module.databus.controller.admin.gateway.vo.version.ApiVersionPageReqVO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiDefinitionDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiStepDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiTransformDO;
|
||||||
|
import com.zt.plat.module.databus.dal.dataobject.gateway.ApiVersionDO;
|
||||||
|
import com.zt.plat.module.databus.dal.mapper.gateway.ApiVersionMapper;
|
||||||
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiDefinitionMapper;
|
||||||
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiStepMapper;
|
||||||
|
import com.zt.plat.module.databus.dal.mysql.gateway.ApiTransformMapper;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiDefinitionService;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiVersionService;
|
||||||
|
import com.zt.plat.module.databus.service.gateway.ApiVersionSnapshotContextHolder;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.zt.plat.module.databus.service.gateway.impl.GatewayServiceErrorCodeConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 版本历史 Service 实现类(备忘录模式)。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ApiVersionServiceImpl implements ApiVersionService {
|
||||||
|
|
||||||
|
private final ApiVersionMapper apiVersionMapper;
|
||||||
|
private final ApiDefinitionMapper apiDefinitionMapper;
|
||||||
|
private final ApiStepMapper apiStepMapper;
|
||||||
|
private final ApiTransformMapper apiTransformMapper;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
private final ApiDefinitionService apiDefinitionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long autoCreateVersion(Long apiId, String description, String operator) {
|
||||||
|
ApiDefinitionDO definition = ensureApiExists(apiId);
|
||||||
|
|
||||||
|
ApiDefinitionSaveReqVO snapshot = buildApiSnapshot(definition);
|
||||||
|
String snapshotJson = serializeSnapshot(apiId, snapshot);
|
||||||
|
|
||||||
|
ApiVersionDO currentVersion = apiVersionMapper.selectCurrentByApiId(apiId);
|
||||||
|
if (currentVersion != null && snapshotEquals(currentVersion.getSnapshotData(), snapshotJson)) {
|
||||||
|
log.debug("[API-VERSION] Skip creating snapshot, apiId={} current version remains {}", apiId, currentVersion.getVersionNumber());
|
||||||
|
return currentVersion.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer maxVersionNumber = apiVersionMapper.selectMaxVersionNumber(apiId);
|
||||||
|
int nextVersionNumber = (maxVersionNumber == null ? 0 : maxVersionNumber) + 1;
|
||||||
|
|
||||||
|
apiVersionMapper.markAllAsNotCurrent(apiId);
|
||||||
|
|
||||||
|
ApiVersionDO version = new ApiVersionDO();
|
||||||
|
version.setApiId(apiId);
|
||||||
|
version.setVersionNumber(nextVersionNumber);
|
||||||
|
version.setDescription(StringUtils.hasText(description) ? description : defaultDescription(nextVersionNumber));
|
||||||
|
version.setIsCurrent(Boolean.TRUE);
|
||||||
|
version.setOperator(StringUtils.hasText(operator) ? operator : "system");
|
||||||
|
version.setSnapshotData(snapshotJson);
|
||||||
|
apiVersionMapper.insert(version);
|
||||||
|
|
||||||
|
log.info("[API-VERSION] Created snapshot apiId={} version=v{} versionId={}", apiId, nextVersionNumber, version.getId());
|
||||||
|
return version.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiVersionDO getVersion(Long id) {
|
||||||
|
ApiVersionDO version = apiVersionMapper.selectById(id);
|
||||||
|
if (version == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(API_VERSION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<ApiVersionDO> getVersionPage(ApiVersionPageReqVO pageReqVO) {
|
||||||
|
return apiVersionMapper.selectPage(pageReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ApiVersionDO> getVersionListByApiId(Long apiId) {
|
||||||
|
ensureApiExists(apiId);
|
||||||
|
return apiVersionMapper.selectListByApiId(apiId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void rollbackToVersion(Long id, String remark) {
|
||||||
|
ApiVersionDO targetVersion = getVersion(id);
|
||||||
|
ApiDefinitionSaveReqVO snapshot = deserializeSnapshot(targetVersion);
|
||||||
|
|
||||||
|
ensureApiExists(targetVersion.getApiId());
|
||||||
|
|
||||||
|
ApiVersionSnapshotContextHolder.markSkipOnce();
|
||||||
|
try {
|
||||||
|
apiDefinitionService.update(snapshot);
|
||||||
|
} finally {
|
||||||
|
ApiVersionSnapshotContextHolder.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
String operator = SecurityFrameworkUtils.getLoginUserNickname();
|
||||||
|
String description = StringUtils.hasText(remark)
|
||||||
|
? remark
|
||||||
|
: String.format("回滚到 v%d", targetVersion.getVersionNumber());
|
||||||
|
autoCreateVersion(targetVersion.getApiId(), description, operator);
|
||||||
|
log.info("[API-VERSION] Rolled back apiId={} to version v{} and created new snapshot", targetVersion.getApiId(), targetVersion.getVersionNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApiVersionCompareRespVO compareVersions(Long sourceVersionId, Long targetVersionId) {
|
||||||
|
ApiVersionDO source = getVersion(sourceVersionId);
|
||||||
|
ApiVersionDO target = getVersion(targetVersionId);
|
||||||
|
|
||||||
|
if (!Objects.equals(source.getApiId(), target.getApiId())) {
|
||||||
|
throw ServiceExceptionUtil.exception(API_VERSION_API_MISMATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiDefinitionSaveReqVO sourceSnapshot = deserializeSnapshot(source);
|
||||||
|
ApiDefinitionSaveReqVO targetSnapshot = deserializeSnapshot(target);
|
||||||
|
|
||||||
|
JsonNode sourceNode = readTree(source.getSnapshotData());
|
||||||
|
JsonNode targetNode = readTree(target.getSnapshotData());
|
||||||
|
|
||||||
|
List<ApiVersionCompareRespVO.FieldDiff> differences = new ArrayList<>();
|
||||||
|
collectDifferences(sourceNode, targetNode, "", differences);
|
||||||
|
|
||||||
|
ApiVersionCompareRespVO respVO = new ApiVersionCompareRespVO();
|
||||||
|
respVO.setSourceVersionId(source.getId());
|
||||||
|
respVO.setSourceVersionNumber(source.getVersionNumber());
|
||||||
|
respVO.setSourceDescription(source.getDescription());
|
||||||
|
respVO.setSourceOperator(source.getOperator());
|
||||||
|
respVO.setSourceCreateTime(source.getCreateTime());
|
||||||
|
respVO.setTargetVersionId(target.getId());
|
||||||
|
respVO.setTargetVersionNumber(target.getVersionNumber());
|
||||||
|
respVO.setTargetDescription(target.getDescription());
|
||||||
|
respVO.setTargetOperator(target.getOperator());
|
||||||
|
respVO.setTargetCreateTime(target.getCreateTime());
|
||||||
|
respVO.setSourceSnapshot(sourceSnapshot);
|
||||||
|
respVO.setTargetSnapshot(targetSnapshot);
|
||||||
|
respVO.setSame(differences.isEmpty());
|
||||||
|
respVO.setDifferences(differences);
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiDefinitionDO ensureApiExists(Long apiId) {
|
||||||
|
ApiDefinitionDO definition = apiDefinitionMapper.selectById(apiId);
|
||||||
|
if (definition == null) {
|
||||||
|
throw ServiceExceptionUtil.exception(API_DEFINITION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiDefinitionSaveReqVO buildApiSnapshot(ApiDefinitionDO definition) {
|
||||||
|
Long apiId = definition.getId();
|
||||||
|
ApiDefinitionSaveReqVO snapshot = new ApiDefinitionSaveReqVO();
|
||||||
|
snapshot.setId(definition.getId());
|
||||||
|
snapshot.setApiCode(definition.getApiCode());
|
||||||
|
snapshot.setVersion(definition.getVersion());
|
||||||
|
snapshot.setHttpMethod(definition.getHttpMethod());
|
||||||
|
snapshot.setStatus(definition.getStatus());
|
||||||
|
snapshot.setDescription(definition.getDescription());
|
||||||
|
snapshot.setRateLimitId(definition.getRateLimitId());
|
||||||
|
snapshot.setResponseTemplate(definition.getResponseTemplate());
|
||||||
|
|
||||||
|
List<ApiStepDO> steps = apiStepMapper.selectByApiId(apiId);
|
||||||
|
if (steps != null && !steps.isEmpty()) {
|
||||||
|
snapshot.setSteps(ApiDefinitionConvert.INSTANCE.convertStepList(steps));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApiTransformDO> apiTransforms = apiTransformMapper.selectApiLevelTransforms(apiId);
|
||||||
|
if (apiTransforms != null && !apiTransforms.isEmpty()) {
|
||||||
|
snapshot.setApiLevelTransforms(ApiDefinitionConvert.INSTANCE.convertTransformList(apiTransforms));
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String serializeSnapshot(Long apiId, ApiDefinitionSaveReqVO snapshot) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(snapshot);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
log.error("[API-VERSION] Failed to serialize snapshot, apiId={}", apiId, ex);
|
||||||
|
throw ServiceExceptionUtil.exception(API_VERSION_SNAPSHOT_SERIALIZE_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiDefinitionSaveReqVO deserializeSnapshot(ApiVersionDO version) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(version.getSnapshotData(), ApiDefinitionSaveReqVO.class);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
log.error("[API-VERSION] Failed to deserialize snapshot, versionId={}", version.getId(), ex);
|
||||||
|
throw ServiceExceptionUtil.exception(API_VERSION_SNAPSHOT_DESERIALIZE_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean snapshotEquals(String left, String right) {
|
||||||
|
if (Objects.equals(left, right)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (left == null || right == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JsonNode leftNode = objectMapper.readTree(left);
|
||||||
|
JsonNode rightNode = objectMapper.readTree(right);
|
||||||
|
return leftNode.equals(rightNode);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
log.warn("[API-VERSION] Snapshot comparison failed, treat as different", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode readTree(String content) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readTree(content);
|
||||||
|
} catch (JsonProcessingException ex) {
|
||||||
|
log.error("[API-VERSION] Failed to parse snapshot content", ex);
|
||||||
|
throw ServiceExceptionUtil.exception(API_VERSION_SNAPSHOT_DESERIALIZE_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectDifferences(JsonNode source, JsonNode target, String path, List<ApiVersionCompareRespVO.FieldDiff> differences) {
|
||||||
|
if (Objects.equals(source, target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source == null || source.isNull()) {
|
||||||
|
addDifference(path, null, target, differences);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target == null || target.isNull()) {
|
||||||
|
addDifference(path, source, null, differences);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.isValueNode() && target.isValueNode()) {
|
||||||
|
if (!Objects.equals(source, target)) {
|
||||||
|
addDifference(path, source, target, differences);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.isObject() && target.isObject()) {
|
||||||
|
Set<String> fieldNames = new LinkedHashSet<>();
|
||||||
|
source.fieldNames().forEachRemaining(fieldNames::add);
|
||||||
|
target.fieldNames().forEachRemaining(fieldNames::add);
|
||||||
|
for (String name : fieldNames) {
|
||||||
|
String childPath = path + "/" + name;
|
||||||
|
collectDifferences(source.get(name), target.get(name), childPath, differences);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.isArray() && target.isArray()) {
|
||||||
|
int max = Math.max(source.size(), target.size());
|
||||||
|
for (int i = 0; i < max; i++) {
|
||||||
|
String childPath = path + "[" + i + "]";
|
||||||
|
JsonNode leftNode = i < source.size() ? source.get(i) : null;
|
||||||
|
JsonNode rightNode = i < target.size() ? target.get(i) : null;
|
||||||
|
collectDifferences(leftNode, rightNode, childPath, differences);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDifference(path, source, target, differences);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDifference(String path, JsonNode source, JsonNode target, List<ApiVersionCompareRespVO.FieldDiff> differences) {
|
||||||
|
ApiVersionCompareRespVO.FieldDiff diff = new ApiVersionCompareRespVO.FieldDiff();
|
||||||
|
diff.setPath(StringUtils.hasText(path) ? path : "/");
|
||||||
|
diff.setSourceValue(source == null || source.isNull() ? "null" : source.toString());
|
||||||
|
diff.setTargetValue(target == null || target.isNull() ? "null" : target.toString());
|
||||||
|
differences.add(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultDescription(int versionNumber) {
|
||||||
|
return String.format("自动版本 v%d", versionNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -55,5 +55,11 @@ public interface GatewayServiceErrorCodeConstants {
|
|||||||
ErrorCode API_STEP_MAPPING_CONFIG_INVALID = new ErrorCode(1_010_000_046, "步骤映射配置 JSON 非法");
|
ErrorCode API_STEP_MAPPING_CONFIG_INVALID = new ErrorCode(1_010_000_046, "步骤映射配置 JSON 非法");
|
||||||
ErrorCode API_CREDENTIAL_ANONYMOUS_USER_REQUIRED = new ErrorCode(1_010_000_047, "启用匿名访问时必须指定固定用户");
|
ErrorCode API_CREDENTIAL_ANONYMOUS_USER_REQUIRED = new ErrorCode(1_010_000_047, "启用匿名访问时必须指定固定用户");
|
||||||
ErrorCode API_CREDENTIAL_ANONYMOUS_USER_INVALID = new ErrorCode(1_010_000_048, "匿名访问固定用户不存在或已被禁用");
|
ErrorCode API_CREDENTIAL_ANONYMOUS_USER_INVALID = new ErrorCode(1_010_000_048, "匿名访问固定用户不存在或已被禁用");
|
||||||
|
ErrorCode API_VERSION_NOT_FOUND = new ErrorCode(1_010_000_049, "API 版本不存在");
|
||||||
|
ErrorCode API_VERSION_DUPLICATE = new ErrorCode(1_010_000_050, "API 版本号已存在");
|
||||||
|
ErrorCode API_VERSION_SNAPSHOT_SERIALIZE_FAILED = new ErrorCode(1_010_000_051, "API 版本快照序列化失败");
|
||||||
|
ErrorCode API_VERSION_SNAPSHOT_DESERIALIZE_FAILED = new ErrorCode(1_010_000_052, "API 版本快照反序列化失败");
|
||||||
|
ErrorCode API_VERSION_ACTIVE_CANNOT_DELETE = new ErrorCode(1_010_000_053, "当前激活版本不允许删除");
|
||||||
|
ErrorCode API_VERSION_API_MISMATCH = new ErrorCode(1_010_000_054, "两个版本不属于同一 API");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,15 @@ import java.util.UUID;
|
|||||||
public final class DatabusApiInvocationExample {
|
public final class DatabusApiInvocationExample {
|
||||||
|
|
||||||
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
public static final String TIMESTAMP = Long.toString(System.currentTimeMillis());
|
||||||
private static final String APP_ID = "ztmy";
|
// private static final String APP_ID = "ztmy";
|
||||||
private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
// private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ=";
|
||||||
// private static final String APP_ID = "test";
|
// private static final String APP_ID = "test";
|
||||||
// private static final String APP_SECRET = "RSYtKXrXPLMy3oeh0cOro6QCioRUgqfnKCkDkNq78sI=";
|
// private static final String APP_SECRET = "RSYtKXrXPLMy3oeh0cOro6QCioRUgqfnKCkDkNq78sI=";
|
||||||
// private static final String APP_ID = "testAnnoy";
|
private static final String APP_ID = "testAnnoy";
|
||||||
// private static final String APP_SECRET = "jyGCymUjCFL2i3a4Tm3qBIkUrUl4ZgKPYvOU/47ZWcM=";
|
private static final String APP_SECRET = "jyGCymUjCFL2i3a4Tm3qBIkUrUl4ZgKPYvOU/47ZWcM=";
|
||||||
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES;
|
||||||
private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
|
private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/test/1";
|
||||||
// private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
// private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
|
||||||
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
||||||
.connectTimeout(Duration.ofSeconds(5))
|
.connectTimeout(Duration.ofSeconds(5))
|
||||||
|
|||||||
@@ -11,32 +11,57 @@ import org.junit.jupiter.api.AfterEach;
|
|||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
import org.springframework.messaging.MessageHeaders;
|
import org.springframework.messaging.MessageHeaders;
|
||||||
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
|
import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
import reactor.netty.resources.ConnectionProvider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
|
|
||||||
class HttpStepHandlerTest {
|
class HttpStepHandlerTest {
|
||||||
|
|
||||||
private MockWebServer server;
|
private MockWebServer server;
|
||||||
private ExpressionExecutor expressionExecutor;
|
private ExpressionExecutor expressionExecutor;
|
||||||
private HttpStepHandler handler;
|
private HttpStepHandler handler;
|
||||||
|
private ConnectionProvider connectionProvider;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws IOException {
|
void setUp() throws IOException {
|
||||||
server = new MockWebServer();
|
server = new MockWebServer();
|
||||||
server.start();
|
server.start();
|
||||||
expressionExecutor = Mockito.mock(ExpressionExecutor.class);
|
expressionExecutor = Mockito.mock(ExpressionExecutor.class);
|
||||||
handler = new HttpStepHandler(WebClient.builder(), expressionExecutor);
|
connectionProvider = ConnectionProvider.builder("http-step-handler-test")
|
||||||
|
.maxConnections(1)
|
||||||
|
.maxIdleTime(Duration.ofMinutes(2))
|
||||||
|
.pendingAcquireMaxCount(-1)
|
||||||
|
.build();
|
||||||
|
HttpClient httpClient = HttpClient.create(connectionProvider);
|
||||||
|
WebClient.Builder builder = WebClient.builder()
|
||||||
|
.clientConnector(new ReactorClientHttpConnector(httpClient));
|
||||||
|
handler = new HttpStepHandler(builder, expressionExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void tearDown() throws IOException {
|
void tearDown() throws IOException {
|
||||||
|
if (connectionProvider != null) {
|
||||||
|
connectionProvider.disposeLater().block(Duration.ofSeconds(5));
|
||||||
|
}
|
||||||
server.shutdown();
|
server.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,4 +127,44 @@ class HttpStepHandlerTest {
|
|||||||
assertThat(request.getHeader("Content-Type")).contains("application/json");
|
assertThat(request.getHeader("Content-Type")).contains("application/json");
|
||||||
assertThat(request.getBody().readUtf8()).contains("\"amount\":100");
|
assertThat(request.getBody().readUtf8()).contains("\"amount\":100");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRecoverWhenReusingStaleConnectionAfterIdle() {
|
||||||
|
AtomicInteger attemptCounter = new AtomicInteger();
|
||||||
|
ExchangeFunction exchangeFunction = request -> {
|
||||||
|
int attempt = attemptCounter.incrementAndGet();
|
||||||
|
if (attempt <= 2) {
|
||||||
|
return Mono.error(new IOException("Simulated connection reset", new SocketException("Connection reset")));
|
||||||
|
}
|
||||||
|
return Mono.just(ClientResponse.create(HttpStatus.OK)
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.body("{\"ok\":true}")
|
||||||
|
.build());
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpStepHandler simulatedHandler = new HttpStepHandler(WebClient.builder().exchangeFunction(exchangeFunction), expressionExecutor);
|
||||||
|
|
||||||
|
ApiStepDO stepDO = new ApiStepDO();
|
||||||
|
stepDO.setId(3L);
|
||||||
|
stepDO.setType("HTTP");
|
||||||
|
stepDO.setTargetEndpoint("POST http://idle-reset.test/stale-connection");
|
||||||
|
|
||||||
|
ApiStepDefinition stepDefinition = ApiStepDefinition.builder()
|
||||||
|
.step(stepDO)
|
||||||
|
.metadata(Collections.emptyMap())
|
||||||
|
.transforms(Collections.emptyList())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
var stepHandler = simulatedHandler.build(null, stepDefinition);
|
||||||
|
|
||||||
|
ApiInvocationContext context = ApiInvocationContext.create();
|
||||||
|
context.setRequestBody(Collections.singletonMap("foo", "bar"));
|
||||||
|
|
||||||
|
assertThatCode(() -> stepHandler.handle(context, new MessageHeaders(Collections.emptyMap())))
|
||||||
|
.doesNotThrowAnyException();
|
||||||
|
|
||||||
|
assertThat(attemptCounter.get()).isEqualTo(3);
|
||||||
|
assertThat(context.getStepResults()).isNotEmpty();
|
||||||
|
assertThat(context.getStepResults().get(0).isSuccess()).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-infra-server
|
RUN mkdir -p /zt-module-infra-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-infra-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-infra-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48082
|
EXPOSE 48082
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
## 感谢复旦核博士的建议!灰子哥,牛皮!
|
## 感谢复旦核博士的建议!灰子哥,牛皮!
|
||||||
FROM eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-mp-server
|
RUN mkdir -p /zt-module-mp-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-mp-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-mp-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48086
|
EXPOSE 48086
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-report-server
|
RUN mkdir -p /zt-module-report-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-report-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-report-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48084
|
EXPOSE 48084
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
|
ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
|
||||||
|
|
||||||
// ========== 部门模块 1-002-004-000 ==========
|
// ========== 部门模块 1-002-004-000 ==========
|
||||||
ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");
|
ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "当前上级部门已存在同名子部门");
|
||||||
ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1_002_004_001,"父级部门不存在");
|
ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1_002_004_001,"父级部门不存在");
|
||||||
ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "机构不存在或当前账号无权限修改");
|
ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "机构不存在或当前账号无权限修改");
|
||||||
ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除");
|
ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除");
|
||||||
@@ -86,6 +86,7 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1_002_006_003, "已经存在该名字的字典类型");
|
ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1_002_006_003, "已经存在该名字的字典类型");
|
||||||
ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1_002_006_004, "已经存在该类型的字典类型");
|
ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1_002_006_004, "已经存在该类型的字典类型");
|
||||||
ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1_002_006_005, "无法删除,该字典类型还有字典数据");
|
ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1_002_006_005, "无法删除,该字典类型还有字典数据");
|
||||||
|
ErrorCode DICT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_002_006_100, "导入字典数据不能为空!");
|
||||||
|
|
||||||
// ========== 字典数据 1-002-007-000 ==========
|
// ========== 字典数据 1-002-007-000 ==========
|
||||||
ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, "当前字典数据不存在");
|
ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, "当前字典数据不存在");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-system-server
|
RUN mkdir -p /zt-module-system-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-system-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-system-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48081
|
EXPOSE 48081
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ public class AuthLoginRespVO {
|
|||||||
@Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
|
@Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
|
|
||||||
|
@Schema(description = "客户端编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "eban-oauth2-client")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
|
||||||
|
private Integer userType;
|
||||||
|
|
||||||
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private LocalDateTime expiresTime;
|
private LocalDateTime expiresTime;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.dict;
|
package com.zt.plat.module.system.controller.admin.dict;
|
||||||
|
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
|
import com.alibaba.excel.ExcelWriter;
|
||||||
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||||
import com.zt.plat.framework.apilog.core.annotation.ApiAccessLog;
|
import com.zt.plat.framework.apilog.core.annotation.ApiAccessLog;
|
||||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
import com.zt.plat.framework.common.pojo.PageParam;
|
import com.zt.plat.framework.common.pojo.PageParam;
|
||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
|
import com.zt.plat.framework.common.util.http.HttpUtils;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
|
import com.zt.plat.framework.excel.core.handler.SelectSheetWriteHandler;
|
||||||
import com.zt.plat.framework.excel.core.util.ExcelUtils;
|
import com.zt.plat.framework.excel.core.util.ExcelUtils;
|
||||||
|
import com.alibaba.excel.converters.longconverter.LongStringConverter;
|
||||||
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeRespVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||||
@@ -14,6 +23,7 @@ import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
|||||||
import com.zt.plat.module.system.service.dict.DictTypeService;
|
import com.zt.plat.module.system.service.dict.DictTypeService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameters;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -21,8 +31,10 @@ import jakarta.validation.Valid;
|
|||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||||
@@ -99,4 +111,45 @@ public class DictTypeController {
|
|||||||
BeanUtils.toBean(list, DictTypeRespVO.class));
|
BeanUtils.toBean(list, DictTypeRespVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get-import-template")
|
||||||
|
@Operation(summary = "获得字典导入模板")
|
||||||
|
public void importTemplate(HttpServletResponse response) throws IOException {
|
||||||
|
List<DictImportExcelVO> samples = Arrays.asList(
|
||||||
|
DictImportExcelVO.builder()
|
||||||
|
.dictTypeName("性别").dictType("system_user_sex").dictTypeRemark("系统内置示例")
|
||||||
|
.label("男").value("1").sort(1).colorType("primary").dataRemark("示例数据").build(),
|
||||||
|
DictImportExcelVO.builder()
|
||||||
|
.dictTypeName("证件类型").dictType("system_id_card_type").dictTypeRemark("自定义示例")
|
||||||
|
.label("身份证").value("ID").sort(1).dataRemark("示例数据").build()
|
||||||
|
);
|
||||||
|
|
||||||
|
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream())
|
||||||
|
.autoCloseStream(false)
|
||||||
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
|
.registerConverter(new LongStringConverter())
|
||||||
|
.build()) {
|
||||||
|
WriteSheet sheet = EasyExcel.writerSheet(0, "字典导入")
|
||||||
|
.head(DictImportExcelVO.class)
|
||||||
|
.registerWriteHandler(new SelectSheetWriteHandler(DictImportExcelVO.class))
|
||||||
|
.build();
|
||||||
|
writer.write(samples, sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.addHeader("Content-Disposition",
|
||||||
|
"attachment;filename=" + HttpUtils.encodeUtf8("字典导入模板.xls"));
|
||||||
|
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/import")
|
||||||
|
@Operation(summary = "导入字典")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = "file", description = "Excel 文件", required = true)
|
||||||
|
})
|
||||||
|
@PreAuthorize("@ss.hasPermission('system:dict:import')")
|
||||||
|
public CommonResult<DictImportRespVO> importDict(@RequestParam("file") MultipartFile file) throws IOException {
|
||||||
|
List<DictImportExcelVO> importList = ExcelUtils.read(file, DictImportExcelVO.class, 0);
|
||||||
|
DictImportRespVO respVO = dictTypeService.importDictList(importList);
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.dict.vo.data;
|
||||||
|
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 迁移到单工作表导入模型 {@link DictImportExcelVO}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public final class DictDataImportExcelVO {
|
||||||
|
|
||||||
|
private DictDataImportExcelVO() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典导入 Excel VO(单行同时包含字典类型与字典数据)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = false)
|
||||||
|
public class DictImportExcelVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典名称
|
||||||
|
*/
|
||||||
|
@ExcelProperty("字典名称")
|
||||||
|
private String dictTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型
|
||||||
|
*/
|
||||||
|
@ExcelProperty("字典类型")
|
||||||
|
private String dictType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型备注
|
||||||
|
*/
|
||||||
|
@ExcelProperty("类型备注")
|
||||||
|
private String dictTypeRemark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典标签
|
||||||
|
*/
|
||||||
|
@ExcelProperty("字典标签")
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典键值
|
||||||
|
*/
|
||||||
|
@ExcelProperty("字典键值")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
@ExcelProperty("排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色类型
|
||||||
|
*/
|
||||||
|
@ExcelProperty("颜色类型")
|
||||||
|
private String colorType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS 样式
|
||||||
|
*/
|
||||||
|
@ExcelProperty("CSS 样式")
|
||||||
|
private String cssClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据备注
|
||||||
|
*/
|
||||||
|
@ExcelProperty("数据备注")
|
||||||
|
private String dataRemark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典导入响应 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@Schema(description = "管理后台 - 字典导入 Response VO")
|
||||||
|
public class DictImportRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "创建成功的字典类型名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<String> createDictTypeNames;
|
||||||
|
|
||||||
|
@Schema(description = "更新成功的字典类型名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<String> updateDictTypeNames;
|
||||||
|
|
||||||
|
@Schema(description = "导入失败的字典类型集合,key 为字典名称,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Map<String, String> failureDictTypeNames;
|
||||||
|
|
||||||
|
@Schema(description = "创建成功的字典数据标识列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<String> createDictDataKeys;
|
||||||
|
|
||||||
|
@Schema(description = "更新成功的字典数据标识列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private List<String> updateDictDataKeys;
|
||||||
|
|
||||||
|
@Schema(description = "导入失败的字典数据集合,key 为字典数据标识,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Map<String, String> failureDictDataKeys;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.dict.vo.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 保留空壳文件以兼容历史引用,新的导入请使用 {@link DictImportExcelVO}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public final class DictTypeImportExcelVO {
|
||||||
|
|
||||||
|
private DictTypeImportExcelVO() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,23 @@
|
|||||||
package com.zt.plat.module.system.controller.admin.permission;
|
package com.zt.plat.module.system.controller.admin.permission;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
import com.zt.plat.framework.common.util.collection.CollectionUtils;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionUserSupervisionRespVO;
|
||||||
|
import com.zt.plat.module.system.dal.dataobject.permission.MenuDO;
|
||||||
|
import com.zt.plat.module.system.dal.dataobject.permission.RoleDO;
|
||||||
|
import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO;
|
||||||
|
import com.zt.plat.module.system.enums.permission.MenuTypeEnum;
|
||||||
|
import com.zt.plat.module.system.service.permission.MenuService;
|
||||||
import com.zt.plat.module.system.service.permission.PermissionService;
|
import com.zt.plat.module.system.service.permission.PermissionService;
|
||||||
|
import com.zt.plat.module.system.service.permission.RoleService;
|
||||||
import com.zt.plat.module.system.service.tenant.TenantService;
|
import com.zt.plat.module.system.service.tenant.TenantService;
|
||||||
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -16,7 +27,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
@@ -34,6 +46,12 @@ public class PermissionController {
|
|||||||
private PermissionService permissionService;
|
private PermissionService permissionService;
|
||||||
@Resource
|
@Resource
|
||||||
private TenantService tenantService;
|
private TenantService tenantService;
|
||||||
|
@Resource
|
||||||
|
private AdminUserService userService;
|
||||||
|
@Resource
|
||||||
|
private RoleService roleService;
|
||||||
|
@Resource
|
||||||
|
private MenuService menuService;
|
||||||
|
|
||||||
@Operation(summary = "获得角色拥有的菜单编号")
|
@Operation(summary = "获得角色拥有的菜单编号")
|
||||||
@Parameter(name = "roleId", description = "角色编号", required = true)
|
@Parameter(name = "roleId", description = "角色编号", required = true)
|
||||||
@@ -79,4 +97,285 @@ public class PermissionController {
|
|||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获得用户权限监督图")
|
||||||
|
@GetMapping("/user-permission-supervision")
|
||||||
|
@PreAuthorize("@ss.hasPermission('system:permission:user-permission-supervision')")
|
||||||
|
public CommonResult<PermissionUserSupervisionRespVO> getUserPermissionSupervision(@RequestParam("userId") Long userId) {
|
||||||
|
AdminUserDO user = userService.getUser(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return success(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionUserSupervisionRespVO respVO = new PermissionUserSupervisionRespVO();
|
||||||
|
PermissionUserSupervisionRespVO.Node root = new PermissionUserSupervisionRespVO.Node();
|
||||||
|
root.setId("user-" + user.getId());
|
||||||
|
root.setLabel(Optional.ofNullable(user.getNickname()).map(nickname -> nickname + "(" + user.getUsername() + ")").orElse(user.getUsername()));
|
||||||
|
root.setType("user");
|
||||||
|
root.setStatus(user.getStatus());
|
||||||
|
respVO.setRoot(root);
|
||||||
|
|
||||||
|
Set<Long> assignedRoleIds = permissionService.getUserRoleIdListByUserId(userId);
|
||||||
|
if (CollUtil.isEmpty(assignedRoleIds)) {
|
||||||
|
root.setTotalPermissionCount(0);
|
||||||
|
root.setIncrementalPermissionCount(0);
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> allRoleIds = roleService.getAllParentAndSelfRoleIds(assignedRoleIds);
|
||||||
|
List<RoleDO> roles = roleService.getRoleList(allRoleIds);
|
||||||
|
if (CollUtil.isEmpty(roles)) {
|
||||||
|
root.setTotalPermissionCount(0);
|
||||||
|
root.setIncrementalPermissionCount(0);
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, RoleDO> roleMap = CollectionUtils.convertMap(roles, RoleDO::getId);
|
||||||
|
List<RoleDO> rootRoles = roles.stream()
|
||||||
|
.filter(role -> role.getParentId() == null || role.getParentId() <= 0 || !roleMap.containsKey(role.getParentId()))
|
||||||
|
.sorted(getRoleComparator())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<Long, List<RoleDO>> childrenMap = new HashMap<>();
|
||||||
|
for (RoleDO role : roles) {
|
||||||
|
Long parentId = role.getParentId();
|
||||||
|
if (parentId != null && parentId > 0 && roleMap.containsKey(parentId)) {
|
||||||
|
childrenMap.computeIfAbsent(parentId, id -> new ArrayList<>()).add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childrenMap.values().forEach(list -> list.sort(getRoleComparator()));
|
||||||
|
|
||||||
|
Map<Long, Set<Long>> roleMenuMap = new HashMap<>();
|
||||||
|
Set<Long> aggregatedMenuIds = new LinkedHashSet<>();
|
||||||
|
for (RoleDO role : roles) {
|
||||||
|
Set<Long> menuIds = new LinkedHashSet<>(permissionService.getRoleMenuListByRoleId(Collections.singleton(role.getId())));
|
||||||
|
roleMenuMap.put(role.getId(), menuIds);
|
||||||
|
aggregatedMenuIds.addAll(menuIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MenuDO> menuList = menuService.getMenuList(aggregatedMenuIds);
|
||||||
|
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus()));
|
||||||
|
Map<Long, MenuDO> menuMap = CollectionUtils.convertMap(menuList, MenuDO::getId);
|
||||||
|
|
||||||
|
List<PermissionUserSupervisionRespVO.Node> roleNodes = rootRoles.stream()
|
||||||
|
.map(role -> buildRoleNode(role, childrenMap, roleMenuMap, menuMap, assignedRoleIds, new LinkedHashSet<>()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
root.setChildren(roleNodes);
|
||||||
|
|
||||||
|
Set<Long> aggregatedEffectiveIds = aggregatedMenuIds.stream()
|
||||||
|
.map(menuMap::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))
|
||||||
|
.map(MenuDO::getId)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
root.setTotalPermissionCount(aggregatedEffectiveIds.size());
|
||||||
|
root.setIncrementalPermissionCount(aggregatedEffectiveIds.size());
|
||||||
|
return success(respVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionUserSupervisionRespVO.Node buildRoleNode(RoleDO role,
|
||||||
|
Map<Long, List<RoleDO>> childrenMap,
|
||||||
|
Map<Long, Set<Long>> roleMenuMap,
|
||||||
|
Map<Long, MenuDO> menuMap,
|
||||||
|
Set<Long> assignedRoleIds,
|
||||||
|
Set<Long> inheritedMenuIds) {
|
||||||
|
PermissionUserSupervisionRespVO.Node node = new PermissionUserSupervisionRespVO.Node();
|
||||||
|
node.setId("role-" + role.getId());
|
||||||
|
node.setLabel(role.getName());
|
||||||
|
node.setType("role");
|
||||||
|
node.setStatus(role.getStatus());
|
||||||
|
node.setCode(role.getCode());
|
||||||
|
node.setAssigned(assignedRoleIds.contains(role.getId()));
|
||||||
|
|
||||||
|
Set<Long> roleMenuIds = new LinkedHashSet<>(roleMenuMap.getOrDefault(role.getId(), Collections.emptySet()));
|
||||||
|
Set<Long> effectiveRoleMenuIds = roleMenuIds.stream()
|
||||||
|
.map(menuMap::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(menu -> StrUtil.isNotEmpty(menu.getPermission()))
|
||||||
|
.map(MenuDO::getId)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
node.setTotalPermissionCount(effectiveRoleMenuIds.size());
|
||||||
|
|
||||||
|
Set<Long> incrementalMenuIds = new LinkedHashSet<>(effectiveRoleMenuIds);
|
||||||
|
incrementalMenuIds.removeAll(inheritedMenuIds);
|
||||||
|
node.setIncrementalPermissionCount(incrementalMenuIds.size());
|
||||||
|
|
||||||
|
Set<Long> nextInheritedMenuIds = new LinkedHashSet<>(inheritedMenuIds);
|
||||||
|
nextInheritedMenuIds.addAll(effectiveRoleMenuIds);
|
||||||
|
|
||||||
|
List<RoleDO> childRoles = childrenMap.getOrDefault(role.getId(), Collections.emptyList());
|
||||||
|
for (RoleDO childRole : childRoles) {
|
||||||
|
node.getChildren().add(buildRoleNode(childRole, childrenMap, roleMenuMap, menuMap, assignedRoleIds,
|
||||||
|
new LinkedHashSet<>(nextInheritedMenuIds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MenuDO> orphanButtons = new ArrayList<>();
|
||||||
|
List<PermissionUserSupervisionRespVO.Node> menuNodes = buildMenuChildren(role, incrementalMenuIds, menuMap, orphanButtons);
|
||||||
|
node.getChildren().addAll(menuNodes);
|
||||||
|
|
||||||
|
if (!orphanButtons.isEmpty()) {
|
||||||
|
orphanButtons.stream()
|
||||||
|
.sorted(getMenuComparator())
|
||||||
|
.map(button -> buildPermissionNode(role, button))
|
||||||
|
.forEach(node.getChildren()::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comparator<RoleDO> getRoleComparator() {
|
||||||
|
return Comparator
|
||||||
|
.comparing((RoleDO role) -> role.getSort() == null ? Integer.MAX_VALUE : role.getSort())
|
||||||
|
.thenComparing(RoleDO::getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comparator<MenuDO> getMenuComparator() {
|
||||||
|
return Comparator
|
||||||
|
.comparing((MenuDO menu) -> menu.getSort() == null ? Integer.MAX_VALUE : menu.getSort())
|
||||||
|
.thenComparing(MenuDO::getId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PermissionUserSupervisionRespVO.Node> buildMenuChildren(RoleDO role,
|
||||||
|
Set<Long> incrementalButtonIds,
|
||||||
|
Map<Long, MenuDO> menuMap,
|
||||||
|
List<MenuDO> orphanButtons) {
|
||||||
|
if (CollUtil.isEmpty(incrementalButtonIds)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, MenuTreeNode> menuNodeCache = new HashMap<>();
|
||||||
|
Map<Long, MenuTreeNode> rootMenuNodes = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (Long buttonId : incrementalButtonIds) {
|
||||||
|
MenuDO button = menuMap.get(buttonId);
|
||||||
|
if (button == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MenuDO parentMenu = findNearestNonButtonMenu(button, menuMap);
|
||||||
|
if (parentMenu == null) {
|
||||||
|
orphanButtons.add(button);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MenuTreeNode parentNode = ensureMenuPath(parentMenu, menuMap, menuNodeCache, rootMenuNodes);
|
||||||
|
if (parentNode == null) {
|
||||||
|
orphanButtons.add(button);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parentNode.getButtons().add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootMenuNodes.values().stream()
|
||||||
|
.sorted((left, right) -> getMenuComparator().compare(left.getMenu(), right.getMenu()))
|
||||||
|
.map(menuTreeNode -> convertMenuTreeNode(role, menuTreeNode))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuTreeNode ensureMenuPath(MenuDO menu,
|
||||||
|
Map<Long, MenuDO> menuMap,
|
||||||
|
Map<Long, MenuTreeNode> menuNodeCache,
|
||||||
|
Map<Long, MenuTreeNode> rootMenuNodes) {
|
||||||
|
if (menu == null || MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MenuTreeNode existing = menuNodeCache.get(menu.getId());
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuTreeNode current = new MenuTreeNode(menu);
|
||||||
|
menuNodeCache.put(menu.getId(), current);
|
||||||
|
|
||||||
|
Long parentId = menu.getParentId();
|
||||||
|
if (parentId == null || parentId <= 0) {
|
||||||
|
rootMenuNodes.putIfAbsent(menu.getId(), current);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuDO parentMenu = menuMap.get(parentId);
|
||||||
|
MenuTreeNode parentNode = ensureMenuPath(parentMenu, menuMap, menuNodeCache, rootMenuNodes);
|
||||||
|
if (parentNode != null) {
|
||||||
|
parentNode.getChildren().putIfAbsent(menu.getId(), current);
|
||||||
|
} else {
|
||||||
|
rootMenuNodes.putIfAbsent(menu.getId(), current);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuDO findNearestNonButtonMenu(MenuDO menu, Map<Long, MenuDO> menuMap) {
|
||||||
|
MenuDO current = menu;
|
||||||
|
while (current != null && MenuTypeEnum.BUTTON.getType().equals(current.getType())) {
|
||||||
|
current = menuMap.get(current.getParentId());
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionUserSupervisionRespVO.Node convertMenuTreeNode(RoleDO role, MenuTreeNode treeNode) {
|
||||||
|
PermissionUserSupervisionRespVO.Node menuNode = new PermissionUserSupervisionRespVO.Node();
|
||||||
|
MenuDO menu = treeNode.getMenu();
|
||||||
|
menuNode.setId("menu-" + role.getId() + '-' + menu.getId());
|
||||||
|
menuNode.setLabel(menu.getName());
|
||||||
|
menuNode.setType("menu");
|
||||||
|
menuNode.setStatus(menu.getStatus());
|
||||||
|
|
||||||
|
List<PermissionUserSupervisionRespVO.Node> childMenuNodes = treeNode.getChildren().values().stream()
|
||||||
|
.sorted((left, right) -> getMenuComparator().compare(left.getMenu(), right.getMenu()))
|
||||||
|
.map(child -> convertMenuTreeNode(role, child))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
menuNode.getChildren().addAll(childMenuNodes);
|
||||||
|
|
||||||
|
List<PermissionUserSupervisionRespVO.Node> buttonNodes = treeNode.getButtons().stream()
|
||||||
|
.sorted(getMenuComparator())
|
||||||
|
.map(button -> buildPermissionNode(role, button))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
menuNode.getButtonChildren().addAll(buttonNodes);
|
||||||
|
|
||||||
|
int totalButtonCount = treeNode.getTotalButtonCount();
|
||||||
|
menuNode.setTotalPermissionCount(totalButtonCount);
|
||||||
|
menuNode.setIncrementalPermissionCount(totalButtonCount);
|
||||||
|
|
||||||
|
return menuNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionUserSupervisionRespVO.Node buildPermissionNode(RoleDO role, MenuDO button) {
|
||||||
|
PermissionUserSupervisionRespVO.Node permissionNode = new PermissionUserSupervisionRespVO.Node();
|
||||||
|
permissionNode.setId("permission-" + role.getId() + '-' + button.getId());
|
||||||
|
permissionNode.setLabel(button.getName());
|
||||||
|
permissionNode.setType("permission");
|
||||||
|
permissionNode.setStatus(button.getStatus());
|
||||||
|
permissionNode.setPermission(button.getPermission());
|
||||||
|
permissionNode.setTotalPermissionCount(1);
|
||||||
|
permissionNode.setIncrementalPermissionCount(1);
|
||||||
|
return permissionNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MenuTreeNode {
|
||||||
|
|
||||||
|
private final MenuDO menu;
|
||||||
|
private final LinkedHashMap<Long, MenuTreeNode> children = new LinkedHashMap<>();
|
||||||
|
private final List<MenuDO> buttons = new ArrayList<>();
|
||||||
|
|
||||||
|
private MenuTreeNode(MenuDO menu) {
|
||||||
|
this.menu = menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuDO getMenu() {
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinkedHashMap<Long, MenuTreeNode> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MenuDO> getButtons() {
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTotalButtonCount() {
|
||||||
|
int total = buttons.size();
|
||||||
|
for (MenuTreeNode child : children.values()) {
|
||||||
|
total += child.getTotalButtonCount();
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.zt.plat.module.system.controller.admin.permission.vo.permission;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户权限监督图响应 VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PermissionUserSupervisionRespVO {
|
||||||
|
|
||||||
|
private Node root;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Node {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private Boolean assigned;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private String permission;
|
||||||
|
|
||||||
|
private Integer totalPermissionCount;
|
||||||
|
|
||||||
|
private Integer incrementalPermissionCount;
|
||||||
|
|
||||||
|
private List<Node> children = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<Node> buttonChildren = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
|
|||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||||
|
|
||||||
@@ -38,4 +39,7 @@ public class UserPageReqVO extends PageParam {
|
|||||||
@Schema(description = "角色编号", example = "1024")
|
@Schema(description = "角色编号", example = "1024")
|
||||||
private Long roleId;
|
private Long roleId;
|
||||||
|
|
||||||
|
@Schema(description = "用户编号集合", example = "[1, 2, 3]")
|
||||||
|
private List<Long> ids;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.zt.plat.module.system.enums.user.UserSourceEnum;
|
|||||||
import com.zt.plat.module.system.service.logger.LoginLogService;
|
import com.zt.plat.module.system.service.logger.LoginLogService;
|
||||||
import com.zt.plat.module.system.service.member.MemberService;
|
import com.zt.plat.module.system.service.member.MemberService;
|
||||||
import com.zt.plat.module.system.service.oauth2.EbanOAuth2Service;
|
import com.zt.plat.module.system.service.oauth2.EbanOAuth2Service;
|
||||||
|
import com.zt.plat.module.system.service.oauth2.EbanTokenService;
|
||||||
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
|
import com.zt.plat.module.system.service.oauth2.OAuth2TokenService;
|
||||||
import com.zt.plat.module.system.service.social.SocialUserService;
|
import com.zt.plat.module.system.service.social.SocialUserService;
|
||||||
import com.zt.plat.module.system.service.user.AdminUserService;
|
import com.zt.plat.module.system.service.user.AdminUserService;
|
||||||
@@ -72,20 +73,22 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
private SmsCodeApi smsCodeApi;
|
private SmsCodeApi smsCodeApi;
|
||||||
@Resource
|
@Resource
|
||||||
private EbanOAuth2Service ebanOAuth2Service;
|
private EbanOAuth2Service ebanOAuth2Service;
|
||||||
|
@Resource
|
||||||
|
private EbanTokenService ebanTokenService;
|
||||||
@Value("${sync.encrypt-key}")
|
@Value("${sync.encrypt-key}")
|
||||||
private String encryptKey;
|
private String encryptKey;
|
||||||
|
|
||||||
// E办OAuth2配置
|
// E办OAuth2配置
|
||||||
@Value("${eban.oauth2.authorize-url:http://10.2.137.42/idp/oauth2/authorize}")
|
@Value("${eban.oauth2.authorize-url}")
|
||||||
private String ebanAuthorizeUrl;
|
private String ebanAuthorizeUrl;
|
||||||
|
|
||||||
@Value("${eban.oauth2.client-id:tyszhjyglxt}")
|
@Value("${eban.oauth2.client-id}")
|
||||||
private String ebanClientId;
|
private String ebanClientId;
|
||||||
|
|
||||||
@Value("${eban.oauth2.redirect-uri:http://172.16.46.63:30080/system/oauth2/callback}")
|
@Value("${eban.oauth2.redirect-uri}")
|
||||||
private String ebanRedirectUri;
|
private String ebanRedirectUri;
|
||||||
|
|
||||||
@Value("${eban.oauth2.response-type:code}")
|
@Value("${eban.oauth2.response-type}")
|
||||||
private String ebanResponseType;
|
private String ebanResponseType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -308,6 +311,9 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
if (accessTokenDO == null) {
|
if (accessTokenDO == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (EbanTokenService.EBAN_CLIENT_ID.equals(accessTokenDO.getClientId())) {
|
||||||
|
ebanTokenService.globalLogout(accessTokenDO.getAccessToken(), accessTokenDO.getUserId());
|
||||||
|
}
|
||||||
// 删除成功,则记录登出日志
|
// 删除成功,则记录登出日志
|
||||||
createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
|
createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
|
||||||
}
|
}
|
||||||
@@ -460,7 +466,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
|||||||
*/
|
*/
|
||||||
private String buildEbanLoginUrl() {
|
private String buildEbanLoginUrl() {
|
||||||
String state = "login_" + System.currentTimeMillis();
|
String state = "login_" + System.currentTimeMillis();
|
||||||
|
|
||||||
return String.format("%s?client_id=%s&redirect_uri=%s&response_type=%s&state=%s",
|
return String.format("%s?client_id=%s&redirect_uri=%s&response_type=%s&state=%s",
|
||||||
ebanAuthorizeUrl, ebanClientId, ebanRedirectUri, ebanResponseType, state);
|
ebanAuthorizeUrl, ebanClientId, ebanRedirectUri, ebanResponseType, state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,16 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
// 校验部门名的唯一性
|
// 校验部门名的唯一性
|
||||||
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
|
||||||
// 生成并校验部门编码
|
// 生成并校验部门编码
|
||||||
String generatedCode = generateDeptCode(createReqVO.getParentId());
|
Long effectiveParentId = normalizeParentId(createReqVO.getParentId());
|
||||||
createReqVO.setCode(generatedCode);
|
boolean isTopLevel = Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT);
|
||||||
validateDeptCodeUnique(null, generatedCode);
|
String resolvedCode;
|
||||||
|
if (isTopLevel) {
|
||||||
|
resolvedCode = resolveTopLevelCode(null, createReqVO.getCode());
|
||||||
|
} else {
|
||||||
|
resolvedCode = generateDeptCode(effectiveParentId);
|
||||||
|
validateDeptCodeUnique(null, resolvedCode);
|
||||||
|
}
|
||||||
|
createReqVO.setCode(resolvedCode);
|
||||||
|
|
||||||
// 插入部门
|
// 插入部门
|
||||||
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
|
||||||
@@ -104,10 +111,25 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
Long oldParentId = normalizeParentId(originalDept.getParentId());
|
Long oldParentId = normalizeParentId(originalDept.getParentId());
|
||||||
boolean parentChanged = !Objects.equals(newParentId, oldParentId);
|
boolean parentChanged = !Objects.equals(newParentId, oldParentId);
|
||||||
if (parentChanged) {
|
if (parentChanged) {
|
||||||
String newCode = generateDeptCode(updateReqVO.getParentId());
|
String newCode;
|
||||||
|
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||||
|
newCode = resolveTopLevelCode(updateReqVO.getId(), updateReqVO.getCode());
|
||||||
|
} else {
|
||||||
|
newCode = generateDeptCode(updateReqVO.getParentId());
|
||||||
|
validateDeptCodeUnique(updateReqVO.getId(), newCode);
|
||||||
|
}
|
||||||
updateReqVO.setCode(newCode);
|
updateReqVO.setCode(newCode);
|
||||||
} else {
|
} else {
|
||||||
updateReqVO.setCode(originalDept.getCode());
|
if (Objects.equals(newParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||||
|
String requestedCode = updateReqVO.getCode();
|
||||||
|
if (StrUtil.isNotBlank(requestedCode) && !StrUtil.equals(requestedCode.trim(), originalDept.getCode())) {
|
||||||
|
updateReqVO.setCode(resolveTopLevelCode(updateReqVO.getId(), requestedCode));
|
||||||
|
} else {
|
||||||
|
updateReqVO.setCode(originalDept.getCode());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateReqVO.setCode(originalDept.getCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新部门
|
// 更新部门
|
||||||
@@ -189,7 +211,11 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void validateDeptNameUnique(Long id, Long parentId, String name) {
|
void validateDeptNameUnique(Long id, Long parentId, String name) {
|
||||||
DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
|
Long effectiveParentId = normalizeParentId(parentId);
|
||||||
|
if (Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DeptDO dept = deptMapper.selectByParentIdAndName(effectiveParentId, name);
|
||||||
if (dept == null) {
|
if (dept == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -312,6 +338,18 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
deptMapper.updateById(update);
|
deptMapper.updateById(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveTopLevelCode(Long currentDeptId, String requestedCode) {
|
||||||
|
String candidate = requestedCode;
|
||||||
|
if (candidate != null) {
|
||||||
|
candidate = candidate.trim();
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(candidate)) {
|
||||||
|
candidate = generateDeptCode(DeptDO.PARENT_ID_ROOT);
|
||||||
|
}
|
||||||
|
validateDeptCodeUnique(currentDeptId, candidate);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DeptDO getDept(Long id) {
|
public DeptDO getDept(Long id) {
|
||||||
return deptMapper.selectById(id);
|
return deptMapper.selectById(id);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.zt.plat.module.system.service.dict;
|
|||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -67,4 +69,12 @@ public interface DictTypeService {
|
|||||||
*/
|
*/
|
||||||
List<DictTypeDO> getDictTypeList();
|
List<DictTypeDO> getDictTypeList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入字典类型与字典数据(单工作表)
|
||||||
|
*
|
||||||
|
* @param importList 导入行列表
|
||||||
|
* @return 导入结果
|
||||||
|
*/
|
||||||
|
DictImportRespVO importDictList(List<DictImportExcelVO> importList);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,36 @@
|
|||||||
package com.zt.plat.module.system.service.dict;
|
package com.zt.plat.module.system.service.dict;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
|
import com.zt.plat.framework.common.util.collection.CollectionUtils;
|
||||||
import com.zt.plat.framework.common.util.date.LocalDateTimeUtils;
|
import com.zt.plat.framework.common.util.date.LocalDateTimeUtils;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportExcelVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictImportRespVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||||
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
import com.zt.plat.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
|
||||||
|
import com.zt.plat.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
|
||||||
|
import com.zt.plat.module.system.dal.dataobject.dict.DictDataDO;
|
||||||
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
import com.zt.plat.module.system.dal.dataobject.dict.DictTypeDO;
|
||||||
import com.zt.plat.module.system.dal.mysql.dict.DictTypeMapper;
|
import com.zt.plat.module.system.dal.mysql.dict.DictTypeMapper;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
||||||
@@ -24,6 +41,7 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.*;
|
|||||||
* @author ZT
|
* @author ZT
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class DictTypeServiceImpl implements DictTypeService {
|
public class DictTypeServiceImpl implements DictTypeService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -92,6 +110,128 @@ public class DictTypeServiceImpl implements DictTypeService {
|
|||||||
return dictTypeMapper.selectList();
|
return dictTypeMapper.selectList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public DictImportRespVO importDictList(List<DictImportExcelVO> importList) {
|
||||||
|
if (CollUtil.isEmpty(importList)) {
|
||||||
|
throw exception(DICT_IMPORT_LIST_IS_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, DictTypeDO> typeByType = new HashMap<>();
|
||||||
|
Map<String, DictTypeDO> typeByName = new HashMap<>();
|
||||||
|
List<DictTypeDO> existingTypes = dictTypeMapper.selectList();
|
||||||
|
existingTypes.forEach(item -> {
|
||||||
|
typeByType.put(item.getType(), item);
|
||||||
|
typeByName.put(item.getName(), item);
|
||||||
|
});
|
||||||
|
|
||||||
|
List<String> createTypeNames = new ArrayList<>();
|
||||||
|
Map<String, String> failureTypeNames = new LinkedHashMap<>();
|
||||||
|
List<String> createDataKeys = new ArrayList<>();
|
||||||
|
Map<String, String> failureDataKeys = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Map<String, Map<String, DictDataDO>> dataCacheByType = new HashMap<>();
|
||||||
|
Set<String> seenRowKeys = new HashSet<>();
|
||||||
|
|
||||||
|
for (DictImportExcelVO row : importList) {
|
||||||
|
String dictTypeName = trimToNull(row.getDictTypeName());
|
||||||
|
String dictTypeCode = trimToNull(row.getDictType());
|
||||||
|
String label = trimToNull(row.getLabel());
|
||||||
|
String value = trimToNull(row.getValue());
|
||||||
|
String displayTypeKey = StrUtil.nullToDefault(dictTypeName, StrUtil.nullToDefault(dictTypeCode, "未知字典"));
|
||||||
|
String displayDataKey = String.format("%s-%s", displayTypeKey, StrUtil.nullToDefault(label, "未知标签"));
|
||||||
|
|
||||||
|
if (StrUtil.isEmpty(dictTypeName) || StrUtil.isEmpty(dictTypeCode)) {
|
||||||
|
failureTypeNames.put(displayTypeKey, "字典名称与字典类型均不能为空");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (StrUtil.isEmpty(label) || StrUtil.isEmpty(value)) {
|
||||||
|
failureDataKeys.put(displayDataKey, "字典标签和值不能为空");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String rowKey = dictTypeCode.toLowerCase(Locale.ROOT) + "::" + value.toLowerCase(Locale.ROOT);
|
||||||
|
if (!seenRowKeys.add(rowKey)) {
|
||||||
|
failureDataKeys.put(displayDataKey, "Excel 中存在重复的字典数据");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DictTypeDO dictType = typeByType.get(dictTypeCode);
|
||||||
|
if (dictType == null) {
|
||||||
|
dictType = typeByName.get(dictTypeName);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (dictType == null) {
|
||||||
|
DictTypeSaveReqVO typeReq = new DictTypeSaveReqVO();
|
||||||
|
typeReq.setName(dictTypeName);
|
||||||
|
typeReq.setType(dictTypeCode);
|
||||||
|
typeReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
typeReq.setRemark(trimToNull(row.getDictTypeRemark()));
|
||||||
|
Long id = createDictType(typeReq);
|
||||||
|
dictType = dictTypeMapper.selectById(id);
|
||||||
|
if (dictType == null) {
|
||||||
|
dictType = new DictTypeDO();
|
||||||
|
dictType.setId(id);
|
||||||
|
dictType.setName(typeReq.getName());
|
||||||
|
dictType.setType(typeReq.getType());
|
||||||
|
dictType.setStatus(typeReq.getStatus());
|
||||||
|
dictType.setRemark(typeReq.getRemark());
|
||||||
|
}
|
||||||
|
typeByType.put(dictType.getType(), dictType);
|
||||||
|
typeByName.put(dictType.getName(), dictType);
|
||||||
|
createTypeNames.add(dictType.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String message = ex.getMessage();
|
||||||
|
failureTypeNames.put(displayTypeKey, StrUtil.blankToDefault(message, "导入失败"));
|
||||||
|
log.warn("Import dict type failed, key={}, message=", displayTypeKey, ex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, DictDataDO> cache = dataCacheByType.computeIfAbsent(dictType.getType(),
|
||||||
|
key -> CollectionUtils.convertMap(dictDataService.getDictDataListByDictType(key), DictDataDO::getValue));
|
||||||
|
DictDataDO existsData = cache.get(value);
|
||||||
|
if (existsData != null) {
|
||||||
|
failureDataKeys.put(displayDataKey, "字典数据已存在,不允许重复导入");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DictDataSaveReqVO dataReq = new DictDataSaveReqVO();
|
||||||
|
dataReq.setDictType(dictType.getType());
|
||||||
|
dataReq.setLabel(label);
|
||||||
|
dataReq.setValue(value);
|
||||||
|
dataReq.setSort(ObjectUtil.defaultIfNull(row.getSort(), 0));
|
||||||
|
dataReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
dataReq.setColorType(trimToNull(row.getColorType()));
|
||||||
|
dataReq.setCssClass(trimToNull(row.getCssClass()));
|
||||||
|
dataReq.setRemark(trimToNull(row.getDataRemark()));
|
||||||
|
Long dataId = dictDataService.createDictData(dataReq);
|
||||||
|
DictDataDO created = dictDataService.getDictData(dataId);
|
||||||
|
if (created == null) {
|
||||||
|
created = new DictDataDO();
|
||||||
|
created.setId(dataId);
|
||||||
|
created.setDictType(dataReq.getDictType());
|
||||||
|
created.setLabel(dataReq.getLabel());
|
||||||
|
created.setValue(dataReq.getValue());
|
||||||
|
}
|
||||||
|
cache.put(created.getValue(), created);
|
||||||
|
createDataKeys.add(displayDataKey);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String message = ex.getMessage();
|
||||||
|
failureDataKeys.put(displayDataKey, StrUtil.blankToDefault(message, "导入失败"));
|
||||||
|
log.warn("Import dict data failed, key={}, message=", displayDataKey, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DictImportRespVO.builder()
|
||||||
|
.createDictTypeNames(createTypeNames)
|
||||||
|
.updateDictTypeNames(List.of())
|
||||||
|
.failureDictTypeNames(failureTypeNames)
|
||||||
|
.createDictDataKeys(createDataKeys)
|
||||||
|
.updateDictDataKeys(List.of())
|
||||||
|
.failureDictDataKeys(failureDataKeys)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void validateDictTypeNameUnique(Long id, String name) {
|
void validateDictTypeNameUnique(Long id, String name) {
|
||||||
DictTypeDO dictType = dictTypeMapper.selectByName(name);
|
DictTypeDO dictType = dictTypeMapper.selectByName(name);
|
||||||
@@ -137,4 +277,12 @@ public class DictTypeServiceImpl implements DictTypeService {
|
|||||||
return dictType;
|
return dictType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trim = StrUtil.trim(value);
|
||||||
|
return StrUtil.isEmpty(trim) ? null : trim;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,19 +47,19 @@ public class EbanOAuth2ServiceImpl implements EbanOAuth2Service {
|
|||||||
@Resource
|
@Resource
|
||||||
private EbanTokenService ebanTokenService;
|
private EbanTokenService ebanTokenService;
|
||||||
|
|
||||||
@Value("${eban.oauth2.auth-server.base-url:http://10.2.137.42/idp/oauth2}")
|
@Value("${eban.oauth2.auth-server.base-url}")
|
||||||
private String authServerBaseUrl;
|
private String authServerBaseUrl;
|
||||||
|
|
||||||
@Value("${eban.oauth2.auth-server.client-id:tyszhjyglxt}")
|
@Value("${eban.oauth2.auth-server.client-id}")
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
@Value("${eban.oauth2.auth-server.client-secret:}")
|
@Value("${eban.oauth2.auth-server.client-secret}")
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
|
|
||||||
@Value("${eban.oauth2.user-info.url:http://10.2.137.42/idp/oauth2/getUserInfo}")
|
@Value("${eban.oauth2.user-info.url}")
|
||||||
private String userInfoUrl;
|
private String userInfoUrl;
|
||||||
|
|
||||||
@Value("${eban.oauth2.token.url:http://10.2.137.42/idp/oauth2/getToken}")
|
@Value("${eban.oauth2.token.url}")
|
||||||
private String tokenUrl;
|
private String tokenUrl;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import com.zt.plat.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
|||||||
*/
|
*/
|
||||||
public interface EbanTokenService {
|
public interface EbanTokenService {
|
||||||
|
|
||||||
|
String EBAN_CLIENT_ID = "eban-oauth2-client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建E办Token信息到现有OAuth2表中
|
* 创建E办Token信息到现有OAuth2表中
|
||||||
*
|
*
|
||||||
@@ -61,4 +63,13 @@ public interface EbanTokenService {
|
|||||||
* @return OAuth2AccessTokenDO
|
* @return OAuth2AccessTokenDO
|
||||||
*/
|
*/
|
||||||
OAuth2AccessTokenDO getEbanTokenByAccessToken(String accessToken);
|
OAuth2AccessTokenDO getEbanTokenByAccessToken(String accessToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用E办全局登出接口
|
||||||
|
*
|
||||||
|
* @param accessToken E办访问令牌
|
||||||
|
* @param userId 系统用户ID
|
||||||
|
* @return 是否登出成功
|
||||||
|
*/
|
||||||
|
boolean globalLogout(String accessToken, Long userId);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.zt.plat.module.system.service.oauth2;
|
package com.zt.plat.module.system.service.oauth2;
|
||||||
|
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpResponse;
|
||||||
@@ -36,19 +35,30 @@ public class EbanTokenServiceImpl implements EbanTokenService {
|
|||||||
@Resource
|
@Resource
|
||||||
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
|
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
|
||||||
|
|
||||||
@Value("${eban.oauth2.auth-server.client-id:tyszhjyglxt}")
|
@Value("${eban.oauth2.auth-server.client-id}")
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
@Value("${eban.oauth2.auth-server.client-secret:}")
|
@Value("${eban.oauth2.auth-server.client-secret}")
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
|
|
||||||
@Value("${eban.oauth2.token.refresh-url:http://10.2.137.42/idp/oauth2/refreshToken}")
|
@Value("${eban.oauth2.token.refresh-url}")
|
||||||
private String refreshTokenUrl;
|
private String refreshTokenUrl;
|
||||||
|
|
||||||
@Value("${eban.oauth2.token.check-url:http://10.2.137.42/idp/oauth2/checkTokenValid}")
|
@Value("${eban.oauth2.token.check-url}")
|
||||||
private String checkTokenUrl;
|
private String checkTokenUrl;
|
||||||
|
|
||||||
private static final String EBAN_CLIENT_ID = "eban-oauth2-client";
|
@Value("${eban.oauth2.logout.url}")
|
||||||
|
private String globalLogoutUrl;
|
||||||
|
|
||||||
|
@Value("${eban.oauth2.logout.entity-id}")
|
||||||
|
private String globalLogoutEntityId;
|
||||||
|
|
||||||
|
@Value("${eban.oauth2.logout.protocol}")
|
||||||
|
private String globalLogoutProtocol;
|
||||||
|
|
||||||
|
@Value("${eban.oauth2.logout.token-type}")
|
||||||
|
private String globalLogoutTokenType;
|
||||||
|
|
||||||
private static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
private static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,7 +72,7 @@ public class EbanTokenServiceImpl implements EbanTokenService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalDateTime expiresTime = calculateExpiresTime(expiresIn);
|
LocalDateTime expiresTime = calculateExpiresTime(expiresIn);
|
||||||
Map<String, String> userInfoMap = MapUtil.newHashMap(6, false);
|
Map<String, String> userInfoMap = new HashMap<>(6);
|
||||||
if (StrUtil.isNotBlank(userInfo.getLoginName())) {
|
if (StrUtil.isNotBlank(userInfo.getLoginName())) {
|
||||||
userInfoMap.put("username", userInfo.getLoginName());
|
userInfoMap.put("username", userInfo.getLoginName());
|
||||||
}
|
}
|
||||||
@@ -152,7 +162,17 @@ public class EbanTokenServiceImpl implements EbanTokenService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteEbanToken(Long userId) {
|
public void deleteEbanToken(Long userId) {
|
||||||
if (oauth2AccessTokenMapper.deleteByUserIdAndClientId(userId, EBAN_CLIENT_ID) > 0) {
|
OAuth2AccessTokenDO tokenDO = getEbanTokenByUserId(userId);
|
||||||
|
if (tokenDO == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalLogout(tokenDO.getAccessToken(), userId);
|
||||||
|
|
||||||
|
int rows = tokenDO.getId() != null
|
||||||
|
? oauth2AccessTokenMapper.deleteById(tokenDO.getId())
|
||||||
|
: oauth2AccessTokenMapper.deleteByUserIdAndClientId(userId, EBAN_CLIENT_ID);
|
||||||
|
if (rows > 0) {
|
||||||
log.info("已删除用户{}的E办Token", userId);
|
log.info("已删除用户{}的E办Token", userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +207,72 @@ public class EbanTokenServiceImpl implements EbanTokenService {
|
|||||||
return StrUtil.equalsIgnoreCase(json.getStr("result"), "true");
|
return StrUtil.equalsIgnoreCase(json.getStr("result"), "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean globalLogout(String accessToken, Long userId) {
|
||||||
|
String entityId = StrUtil.blankToDefault(globalLogoutEntityId, clientId);
|
||||||
|
String protocol = StrUtil.blankToDefault(globalLogoutProtocol, "oauth");
|
||||||
|
String tokenType = StrUtil.blankToDefault(globalLogoutTokenType, "token");
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(entityId)) {
|
||||||
|
log.warn("调用E办全局登出时缺少entityId配置,userId={}", userId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(accessToken) && !StrUtil.equalsIgnoreCase(tokenType, "fromWeb")) {
|
||||||
|
log.warn("调用E办全局登出时缺少accessToken,userId={},tokenType={}", userId, tokenType);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> params = new HashMap<>(4);
|
||||||
|
params.put("entityId", entityId);
|
||||||
|
params.put("protocol", protocol);
|
||||||
|
params.put("tokenType", tokenType);
|
||||||
|
if (StrUtil.isNotBlank(accessToken)) {
|
||||||
|
params.put("token", accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse response = HttpRequest.get(globalLogoutUrl)
|
||||||
|
.form(params)
|
||||||
|
.timeout(10000)
|
||||||
|
.execute();
|
||||||
|
if (!response.isOk()) {
|
||||||
|
log.error("调用E办全局登出失败,userId={},状态码={},响应={}", userId, response.getStatus(), response.body());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject json = parseJson(response.body());
|
||||||
|
if (json.isEmpty()) {
|
||||||
|
log.warn("调用E办全局登出返回空响应,userId={},body={}", userId, response.body());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (json.containsKey("errcode")) {
|
||||||
|
log.warn("调用E办全局登出返回错误,userId={},错误码={},信息={}", userId, json.getStr("errcode"), json.getStr("msg"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = false;
|
||||||
|
String result = json.getStr("result");
|
||||||
|
if (StrUtil.isNotBlank(result)) {
|
||||||
|
success = StrUtil.equalsIgnoreCase(result, "true")
|
||||||
|
|| StrUtil.equalsIgnoreCase(result, "success")
|
||||||
|
|| StrUtil.equalsIgnoreCase(result, "ok");
|
||||||
|
}
|
||||||
|
if (!success && json.containsKey("success")) {
|
||||||
|
success = Boolean.TRUE.equals(json.getBool("success"));
|
||||||
|
}
|
||||||
|
if (!success && json.containsKey("status")) {
|
||||||
|
success = StrUtil.equalsIgnoreCase(json.getStr("status"), "200")
|
||||||
|
|| StrUtil.equalsIgnoreCase(json.getStr("status"), "success")
|
||||||
|
|| StrUtil.equalsIgnoreCase(json.getStr("status"), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
log.info("调用E办全局登出成功,userId={}", userId);
|
||||||
|
} else {
|
||||||
|
log.warn("调用E办全局登出未返回明确成功标识,userId={},响应={}", userId, json);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
private LocalDateTime calculateExpiresTime(Integer expiresIn) {
|
private LocalDateTime calculateExpiresTime(Integer expiresIn) {
|
||||||
int seconds = expiresIn != null && expiresIn > 0 ? expiresIn : 3600;
|
int seconds = expiresIn != null && expiresIn > 0 ? expiresIn : 3600;
|
||||||
return LocalDateTimeUtil.offset(LocalDateTime.now(), seconds, ChronoUnit.SECONDS);
|
return LocalDateTimeUtil.offset(LocalDateTime.now(), seconds, ChronoUnit.SECONDS);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjUtil;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
import com.zt.plat.framework.common.enums.CommonStatusEnum;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
import com.zt.plat.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
import com.zt.plat.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||||
@@ -24,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList;
|
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList;
|
||||||
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap;
|
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertMap;
|
||||||
@@ -66,11 +68,13 @@ public class MenuServiceImpl implements MenuService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
|
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
|
||||||
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
|
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
|
||||||
public void updateMenu(MenuSaveVO updateReqVO) {
|
public void updateMenu(MenuSaveVO updateReqVO) {
|
||||||
// 校验更新的菜单是否存在
|
// 校验更新的菜单是否存在
|
||||||
if (menuMapper.selectById(updateReqVO.getId()) == null) {
|
MenuDO oldMenu = menuMapper.selectById(updateReqVO.getId());
|
||||||
|
if (oldMenu == null) {
|
||||||
throw exception(MENU_NOT_EXISTS);
|
throw exception(MENU_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
// 校验父菜单存在
|
// 校验父菜单存在
|
||||||
@@ -83,6 +87,12 @@ public class MenuServiceImpl implements MenuService {
|
|||||||
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
|
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
|
||||||
initMenuProperty(updateObj);
|
initMenuProperty(updateObj);
|
||||||
menuMapper.updateById(updateObj);
|
menuMapper.updateById(updateObj);
|
||||||
|
|
||||||
|
// 如果本次更新禁用了当前菜单,则联动禁用所有子菜单
|
||||||
|
if (!CommonStatusEnum.isDisable(oldMenu.getStatus())
|
||||||
|
&& CommonStatusEnum.isDisable(updateObj.getStatus())) {
|
||||||
|
cascadeDisableChildMenus(updateObj.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -190,6 +200,41 @@ public class MenuServiceImpl implements MenuService {
|
|||||||
return menuMapper.selectBatchIds(ids);
|
return menuMapper.selectBatchIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 禁用父级菜单时需级联禁用所有子菜单
|
||||||
|
private void cascadeDisableChildMenus(Long parentId) {
|
||||||
|
List<MenuDO> allMenus = menuMapper.selectList();
|
||||||
|
if (CollUtil.isEmpty(allMenus)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Long, List<MenuDO>> childrenMap = new HashMap<>();
|
||||||
|
for (MenuDO menu : allMenus) {
|
||||||
|
childrenMap.computeIfAbsent(menu.getParentId(), key -> new ArrayList<>()).add(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deque<Long> queue = new ArrayDeque<>();
|
||||||
|
queue.add(parentId);
|
||||||
|
List<Long> toDisableIds = new ArrayList<>();
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
Long current = queue.poll();
|
||||||
|
List<MenuDO> children = childrenMap.get(current);
|
||||||
|
if (CollUtil.isEmpty(children)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (MenuDO child : children) {
|
||||||
|
if (!CommonStatusEnum.isDisable(child.getStatus())) {
|
||||||
|
toDisableIds.add(child.getId());
|
||||||
|
}
|
||||||
|
queue.add(child.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollUtil.isEmpty(toDisableIds)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
menuMapper.update(null, new LambdaUpdateWrapper<MenuDO>()
|
||||||
|
.in(MenuDO::getId, toDisableIds)
|
||||||
|
.set(MenuDO::getStatus, CommonStatusEnum.DISABLE.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验父菜单是否合法
|
* 校验父菜单是否合法
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -338,6 +338,18 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||||||
Set<Long> userIds = reqVO.getRoleId() != null ?
|
Set<Long> userIds = reqVO.getRoleId() != null ?
|
||||||
permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;
|
permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(reqVO.getIds())) {
|
||||||
|
Set<Long> idFilter = new HashSet<>(reqVO.getIds());
|
||||||
|
if (userIds == null) {
|
||||||
|
userIds = idFilter;
|
||||||
|
} else {
|
||||||
|
userIds.retainAll(idFilter);
|
||||||
|
}
|
||||||
|
if (CollUtil.isEmpty(userIds)) {
|
||||||
|
return PageResult.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
PageResult<AdminUserDO> pageResult = userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
|
PageResult<AdminUserDO> pageResult = userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
|
||||||
fillUserDeptInfo(pageResult.getList());
|
fillUserDeptInfo(pageResult.getList());
|
||||||
|
|||||||
@@ -207,20 +207,26 @@ zt:
|
|||||||
# E办OAuth2配置文件
|
# E办OAuth2配置文件
|
||||||
eban:
|
eban:
|
||||||
oauth2:
|
oauth2:
|
||||||
# E办OAuth2服务端配置
|
authorize-url: ${eban.oauth2.auth-server.base-url}/authorize
|
||||||
|
client-id: tyszhjyglxt
|
||||||
|
client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从 e 办系统获取
|
||||||
|
redirect-uri: http://172.16.46.63:30080/system/oauth2/callback
|
||||||
|
response-type: code
|
||||||
auth-server:
|
auth-server:
|
||||||
base-url: http://10.2.137.42/idp/oauth2
|
base-url: http://10.2.137.42/idp/oauth2
|
||||||
client-id: tyszhjyglxt
|
client-id: ${eban.oauth2.client-id}
|
||||||
client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从e办系统获取
|
client-secret: ${eban.oauth2.client-secret}
|
||||||
callback-uri: http://172.16.46.63:30080/system/oauth2/callback
|
|
||||||
|
|
||||||
# 用户信息获取配置
|
|
||||||
user-info:
|
|
||||||
url: http://10.2.137.42/idp/oauth2/getUserInfo
|
|
||||||
|
|
||||||
# 令牌交换配置
|
|
||||||
token:
|
token:
|
||||||
url: http://10.2.137.42/idp/oauth2/getToken
|
url: ${eban.oauth2.auth-server.base-url}/getToken
|
||||||
|
refresh-url: ${eban.oauth2.auth-server.base-url}/refreshToken
|
||||||
|
check-url: ${eban.oauth2.auth-server.base-url}/checkTokenValid
|
||||||
|
logout:
|
||||||
|
url: http://10.2.137.42/idp/profile/AllChannel/Redirect/GLO
|
||||||
|
entity-id: tyszhjyglxt
|
||||||
|
protocol: oauth
|
||||||
|
token-type: token
|
||||||
|
user-info:
|
||||||
|
url: ${eban.oauth2.auth-server.base-url}/getUserInfo
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,45 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
assertEquals(parentDept.getCode() + "001", childDept.getCode());
|
assertEquals(parentDept.getCode() + "001", childDept.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_topLevelAutoCodeAndDuplicates() {
|
||||||
|
DeptSaveReqVO topLevelReq = new DeptSaveReqVO();
|
||||||
|
topLevelReq.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
topLevelReq.setName("总部");
|
||||||
|
topLevelReq.setSort(1);
|
||||||
|
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
topLevelReq.setDeptSource(1);
|
||||||
|
Long topLevelId = deptService.createDept(topLevelReq);
|
||||||
|
DeptDO firstTop = deptMapper.selectById(topLevelId);
|
||||||
|
assertEquals("ZT001", firstTop.getCode());
|
||||||
|
|
||||||
|
DeptSaveReqVO secondTopLevelReq = new DeptSaveReqVO();
|
||||||
|
secondTopLevelReq.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
secondTopLevelReq.setName("总部");
|
||||||
|
secondTopLevelReq.setSort(2);
|
||||||
|
secondTopLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
secondTopLevelReq.setDeptSource(1);
|
||||||
|
Long secondTopId = deptService.createDept(secondTopLevelReq);
|
||||||
|
DeptDO secondTop = deptMapper.selectById(secondTopId);
|
||||||
|
assertEquals("ZT002", secondTop.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateDept_topLevelRespectCustomCode() {
|
||||||
|
String customCode = "ROOT-001";
|
||||||
|
DeptSaveReqVO topLevelReq = new DeptSaveReqVO();
|
||||||
|
topLevelReq.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
topLevelReq.setName("集团");
|
||||||
|
topLevelReq.setSort(1);
|
||||||
|
topLevelReq.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
topLevelReq.setDeptSource(1);
|
||||||
|
topLevelReq.setCode(customCode);
|
||||||
|
|
||||||
|
Long deptId = deptService.createDept(topLevelReq);
|
||||||
|
DeptDO created = deptMapper.selectById(deptId);
|
||||||
|
assertEquals(customCode, created.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateDept() {
|
public void testUpdateDept() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
@@ -205,20 +244,68 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateNameUnique_duplicate() {
|
public void testValidateNameUnique_duplicate() {
|
||||||
// mock 数据
|
// mock 上级部门
|
||||||
DeptDO deptDO = randomPojo(DeptDO.class).setDeptSource(null);
|
DeptDO parentDept = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
|
deptMapper.insert(parentDept);
|
||||||
|
|
||||||
|
// mock 同级重名部门
|
||||||
|
String duplicateName = randomString();
|
||||||
|
DeptDO deptDO = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(parentDept.getId());
|
||||||
|
o.setName(duplicateName);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
deptMapper.insert(deptDO);
|
deptMapper.insert(deptDO);
|
||||||
|
|
||||||
// 准备参数
|
|
||||||
Long id = randomLongId();
|
|
||||||
Long parentId = deptDO.getParentId();
|
|
||||||
String name = deptDO.getName();
|
|
||||||
|
|
||||||
// 调用, 并断言异常
|
// 调用, 并断言异常
|
||||||
assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),
|
assertServiceException(() -> deptService.validateDeptNameUnique(randomLongId(), parentDept.getId(), duplicateName),
|
||||||
DEPT_NAME_DUPLICATE);
|
DEPT_NAME_DUPLICATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateDeptNameUnique_topLevelDuplicateAllowed() {
|
||||||
|
// mock 顶级部门
|
||||||
|
String duplicateName = randomString();
|
||||||
|
DeptDO topLevelDept = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
o.setName(duplicateName);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
|
deptMapper.insert(topLevelDept);
|
||||||
|
|
||||||
|
// 调用, 验证不会抛出异常
|
||||||
|
assertDoesNotThrow(() -> deptService.validateDeptNameUnique(null, DeptDO.PARENT_ID_ROOT, duplicateName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateDeptNameUnique_differentParentAllowed() {
|
||||||
|
// mock 不同上级部门
|
||||||
|
DeptDO parentA = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
|
deptMapper.insert(parentA);
|
||||||
|
DeptDO parentB = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(DeptDO.PARENT_ID_ROOT);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
|
deptMapper.insert(parentB);
|
||||||
|
|
||||||
|
String duplicateName = randomString();
|
||||||
|
DeptDO childUnderA = randomPojo(DeptDO.class, o -> {
|
||||||
|
o.setParentId(parentA.getId());
|
||||||
|
o.setName(duplicateName);
|
||||||
|
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||||
|
}).setDeptSource(null);
|
||||||
|
deptMapper.insert(childUnderA);
|
||||||
|
|
||||||
|
// 调用, 验证不同父级可以重名
|
||||||
|
assertDoesNotThrow(() -> deptService.validateDeptNameUnique(null, parentB.getId(), duplicateName));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetDept() {
|
public void testGetDept() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-module-template-server
|
RUN mkdir -p /zt-module-template-server
|
||||||
@@ -10,10 +11,15 @@ COPY ./target/zt-module-template-server.jar app.jar
|
|||||||
|
|
||||||
## 设置 TZ 时区
|
## 设置 TZ 时区
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
|
ENV TZ=Asia/Shanghai
|
||||||
|
ENV JAVA_OPTS="-Xms512m -Xmx512m"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-module-template-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 暴露后端项目的 48080 端口
|
## 暴露后端项目的 48080 端口
|
||||||
EXPOSE 48100
|
EXPOSE 48100
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
|
||||||
|
|
||||||
FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre
|
ARG BASE_IMAGE=172.16.46.66:10043/base-service/skywalking-agent-jre:9.7.0
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
## 创建目录,并使用它作为工作目录
|
## 创建目录,并使用它作为工作目录
|
||||||
RUN mkdir -p /zt-server
|
RUN mkdir -p /zt-server
|
||||||
@@ -12,6 +13,10 @@ COPY ./target/zt-server.jar app.jar
|
|||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
|
||||||
ENV JAVA_OPTS="-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom"
|
ENV JAVA_OPTS="-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom"
|
||||||
|
ENV SW_AGENT_HOME=/opt/skywalking/agent
|
||||||
|
ENV SW_AGENT_NAME=zt-server
|
||||||
|
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=172.16.46.63:30201
|
||||||
|
ENV AGENT_JAVA_OPTS="-javaagent:${SW_AGENT_HOME}/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES}"
|
||||||
|
|
||||||
## 应用参数
|
## 应用参数
|
||||||
ENV ARGS=""
|
ENV ARGS=""
|
||||||
@@ -20,4 +25,4 @@ ENV ARGS=""
|
|||||||
EXPOSE 48080
|
EXPOSE 48080
|
||||||
|
|
||||||
## 启动后端项目
|
## 启动后端项目
|
||||||
CMD java ${JAVA_OPTS} -jar app.jar $ARGS
|
CMD java ${AGENT_JAVA_OPTS} ${JAVA_OPTS} -jar app.jar $ARGS
|
||||||
|
|||||||
@@ -349,18 +349,26 @@ zt:
|
|||||||
# E办OAuth2配置文件
|
# E办OAuth2配置文件
|
||||||
eban:
|
eban:
|
||||||
oauth2:
|
oauth2:
|
||||||
# E办OAuth2登录配置
|
authorize-url: ${eban.oauth2.auth-server.base-url}/authorize
|
||||||
authorize-url: http://10.2.137.42/idp/oauth2/authorize
|
|
||||||
client-id: tyszhjyglxt
|
client-id: tyszhjyglxt
|
||||||
client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从e办系统获取
|
client-secret: fa821b567e59448e9acea3937529d1b4 # 需要从 e 办系统获取
|
||||||
redirect-uri: http://172.16.46.63:30080/system/oauth2/callback
|
redirect-uri: http://172.16.46.63:30080/system/oauth2/callback
|
||||||
response-type: code
|
response-type: code
|
||||||
|
auth-server:
|
||||||
# 用户信息获取URL
|
base-url: http://10.2.137.42/idp/oauth2
|
||||||
user-info-url: http://10.2.137.42/idp/oauth2/getUserInfo
|
client-id: ${eban.oauth2.client-id}
|
||||||
|
client-secret: ${eban.oauth2.client-secret}
|
||||||
# 令牌交换URL
|
token:
|
||||||
token-url: http://10.2.137.42/idp/oauth2/getToken
|
url: ${eban.oauth2.auth-server.base-url}/getToken
|
||||||
|
refresh-url: ${eban.oauth2.auth-server.base-url}/refreshToken
|
||||||
|
check-url: ${eban.oauth2.auth-server.base-url}/checkTokenValid
|
||||||
|
logout:
|
||||||
|
url: http://10.2.137.42/idp/profile/AllChannel/Redirect/GLO
|
||||||
|
entity-id: tyszhjyglxt
|
||||||
|
protocol: oauth
|
||||||
|
token-type: token
|
||||||
|
user-info:
|
||||||
|
url: ${eban.oauth2.auth-server.base-url}/getUserInfo
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user