Merge branch 'refs/heads/zt-test' into test

# Conflicts:
#	zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml
This commit is contained in:
FCL
2026-01-04 15:19:35 +08:00
119 changed files with 4265 additions and 184 deletions

18
pom.xml
View File

@@ -32,7 +32,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties> <properties>
<revision>3.0.45</revision> <revision>3.0.46</revision>
<!-- Maven 相关 --> <!-- Maven 相关 -->
<java.version>17</java.version> <java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -242,6 +242,19 @@
<config.version>1.0.0</config.version> <config.version>1.0.0</config.version>
</properties> </properties>
</profile> </profile>
<profile>
<id>klw-dev</id>
<properties>
<env.name>dev</env.name>
<!--Nacos 配置-->
<config.server-addr>172.16.46.63:30848</config.server-addr>
<config.namespace>klw</config.namespace>
<config.group>DEFAULT_GROUP</config.group>
<config.username>nacos</config.username>
<config.password>P@ssword25</config.password>
<config.version>1.0.0</config.version>
</properties>
</profile>
<profile> <profile>
<id>env-prod</id> <id>env-prod</id>
<properties> <properties>
@@ -271,7 +284,8 @@
<profile> <profile>
<id>chenbowen</id> <id>chenbowen</id>
<properties> <properties>
<env.name>local</env.name> <!-- <env.name>local</env.name>-->
<env.name>dev</env.name>
<!-- <config.server-addr>localhost:8848</config.server-addr>--> <!-- <config.server-addr>localhost:8848</config.server-addr>-->
<config.server-addr>172.16.46.63:30848</config.server-addr> <config.server-addr>172.16.46.63:30848</config.server-addr>
<config.namespace>chenbowen</config.namespace> <config.namespace>chenbowen</config.namespace>

View File

@@ -120,6 +120,7 @@ CREATE TABLE bpm_process_definition_info (
simple_model text NULL, simple_model text NULL,
sort bigint DEFAULT 0 NULL, sort bigint DEFAULT 0 NULL,
visible bit DEFAULT '1' NOT NULL, visible bit DEFAULT '1' NOT NULL,
restart bit DEFAULT '1' NOT NULL,
start_user_ids varchar(256) DEFAULT NULL NULL, start_user_ids varchar(256) DEFAULT NULL NULL,
start_dept_ids varchar(256) DEFAULT NULL NULL, start_dept_ids varchar(256) DEFAULT NULL NULL,
manager_user_ids varchar(256) DEFAULT NULL NULL, manager_user_ids varchar(256) DEFAULT NULL NULL,
@@ -156,6 +157,7 @@ COMMENT ON COLUMN bpm_process_definition_info.form_custom_view_path IS '自定
COMMENT ON COLUMN bpm_process_definition_info.simple_model IS 'SIMPLE 设计器模型数据 JSON 格式'; COMMENT ON COLUMN bpm_process_definition_info.simple_model IS 'SIMPLE 设计器模型数据 JSON 格式';
COMMENT ON COLUMN bpm_process_definition_info.sort IS '排序值'; COMMENT ON COLUMN bpm_process_definition_info.sort IS '排序值';
COMMENT ON COLUMN bpm_process_definition_info.visible IS '是否可见'; COMMENT ON COLUMN bpm_process_definition_info.visible IS '是否可见';
COMMENT ON COLUMN bpm_process_definition_info.restart IS '是否允许重新发起';
COMMENT ON COLUMN bpm_process_definition_info.start_user_ids IS '可发起用户编号数组'; COMMENT ON COLUMN bpm_process_definition_info.start_user_ids IS '可发起用户编号数组';
COMMENT ON COLUMN bpm_process_definition_info.start_dept_ids IS '可发起部门编号数组'; COMMENT ON COLUMN bpm_process_definition_info.start_dept_ids IS '可发起部门编号数组';
COMMENT ON COLUMN bpm_process_definition_info.manager_user_ids IS '可管理用户编号数组'; COMMENT ON COLUMN bpm_process_definition_info.manager_user_ids IS '可管理用户编号数组';

View File

@@ -331,6 +331,8 @@ CREATE TABLE infra_file (
name varchar(256) DEFAULT NULL NULL, name varchar(256) DEFAULT NULL NULL,
path varchar(512) NOT NULL, path varchar(512) NOT NULL,
url varchar(1024) NOT NULL, url varchar(1024) NOT NULL,
hash varchar(64) DEFAULT NULL NULL,
aes_iv varchar(128) DEFAULT NULL NULL,
type varchar(128) DEFAULT NULL NULL, type varchar(128) DEFAULT NULL NULL,
size int NOT NULL, size int NOT NULL,
creator varchar(64) DEFAULT '' NULL, creator varchar(64) DEFAULT '' NULL,
@@ -345,6 +347,8 @@ COMMENT ON COLUMN infra_file.config_id IS '配置编号';
COMMENT ON COLUMN infra_file.name IS '文件名'; COMMENT ON COLUMN infra_file.name IS '文件名';
COMMENT ON COLUMN infra_file.path IS '文件路径'; COMMENT ON COLUMN infra_file.path IS '文件路径';
COMMENT ON COLUMN infra_file.url IS '文件 URL'; COMMENT ON COLUMN infra_file.url IS '文件 URL';
COMMENT ON COLUMN infra_file.hash IS '文件哈希值SHA-256';
COMMENT ON COLUMN infra_file.aes_iv IS 'AES加密时的随机IVBase64编码';
COMMENT ON COLUMN infra_file.type IS '文件类型'; COMMENT ON COLUMN infra_file.type IS '文件类型';
COMMENT ON COLUMN infra_file.size IS '文件大小'; COMMENT ON COLUMN infra_file.size IS '文件大小';
COMMENT ON COLUMN infra_file.creator IS '创建者'; COMMENT ON COLUMN infra_file.creator IS '创建者';
@@ -354,6 +358,8 @@ COMMENT ON COLUMN infra_file.update_time IS '更新时间';
COMMENT ON COLUMN infra_file.deleted IS '是否删除'; COMMENT ON COLUMN infra_file.deleted IS '是否删除';
COMMENT ON TABLE infra_file IS '文件表'; COMMENT ON TABLE infra_file IS '文件表';
CREATE INDEX idx_infra_file_hash ON infra_file(hash);
-- ---------------------------- -- ----------------------------
-- Table structure for infra_file_config -- Table structure for infra_file_config
-- ---------------------------- -- ----------------------------
@@ -1586,6 +1592,116 @@ INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon
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 (5010, '租户切换', 'system:tenant:visit', 3, 999, 1138, '', '', '', '', 0, '1', '1', '1', '1', '2025-05-05 15:25:32', '1', '2025-05-05 15:25:32', '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 (5010, '租户切换', 'system:tenant:visit', 3, 999, 1138, '', '', '', '', 0, '1', '1', '1', '1', '2025-05-05 15:25:32', '1', '2025-05-05 15:25:32', '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 (5013, '公司切换', 'system:company:visit', 3, 100, 103, '', '', '', '', 0, '1', '1', '1', '1', '2025-12-05 09:00:00', '1', '2025-12-05 09:00:00', '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 (5013, '公司切换', 'system:company:visit', 3, 100, 103, '', '', '', '', 0, '1', '1', '1', '1', '2025-12-05 09:00:00', '1', '2025-12-05 09:00:00', '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 (5014, '部门切换', 'system:dept:visit', 3, 101, 103, '', '', '', '', 0, '1', '1', '1', '1', '2025-12-05 09:00:00', '1', '2025-12-05 09:00:00', '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 (5014, '部门切换', 'system:dept:visit', 3, 101, 103, '', '', '', '', 0, '1', '1', '1', '1', '2025-12-05 09:00:00', '1', '2025-12-05 09:00:00', '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 (1953701540574969857, '系统序列号管理', '', 2, 0, 1, 'sequence', '', 'system/sequence/index', 'Sequence', 0, 1, 1, 1, '', '2025-08-08 14:38:20.455625', '', '2025-08-08 14:38:20.455626', 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 (1953706417225150470, '系统序列号导出', 'system:sequence:export', 3, 5, 1953701540574969857, '', '', '', null, 0, 1, 1, 1, '', '2025-08-08 14:38:20.580376', '', '2025-08-08 14:38:20.580377', 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 (1953706417225150469, '系统序列号删除', 'system:sequence:delete', 3, 4, 1953701540574969857, '', '', '', null, 0, 1, 1, 1, '', '2025-08-08 14:38:20.553819', '', '2025-08-08 14:38:20.553820', 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 (1953706417225150468, '系统序列号更新', 'system:sequence:update', 3, 3, 1953701540574969857, '', '', '', null, 0, 1, 1, 1, '', '2025-08-08 14:38:20.522730', '', '2025-08-08 14:38:20.522731', 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 (1953706417225150467, '系统序列号创建', 'system:sequence:create', 3, 2, 1953701540574969857, '', '', '', null, 0, 1, 1, 1, '', '2025-08-08 14:38:20.499928', '', '2025-08-08 14:38:20.499929', 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 (1953706417225150466, '系统序列号查询', 'system:sequence:query', 3, 1, 1953701540574969857, '', '', '', null, 0, 1, 1, 1, '', '2025-08-08 14:38:20.483702', '', '2025-08-08 14:38:20.483703', 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 (2739, '消息中心', '', 1, 7, 1, 'messages', 'ep:chat-dot-round', '', '', 0, 1, 1, 1, '1', '2024-04-22 23:54:30.000000', '1', '2024-04-23 09:36:35.000000', 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 (2144, '站内信管理', '', 1, 3, 2739, 'notify', 'ep:message-box', null, null, 0, 1, 1, 1, '1', '2023-01-28 10:25:18.000000', '1', '2024-04-22 23:56:12.000000', 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 (2151, '消息记录', '', 2, 0, 2144, 'notify-message', 'fa:edit', 'system/notify/message/index', 'SystemNotifyMessage', 0, 1, 1, 1, '', '2023-01-28 04:28:22.000000', '1', '2024-02-29 08:49:22.000000', 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 (2152, '站内信消息查询', 'system:notify-message:query', 3, 1, 2151, '', '', '', null, 0, 1, 1, 1, '', '2023-01-28 04:28:22.000000', '', '2023-01-28 04:28:22.000000', 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 (2145, '模板管理', '', 2, 0, 2144, 'notify-template', 'fa:archive', 'system/notify/template/index', 'SystemNotifyTemplate', 0, 1, 1, 1, '', '2023-01-28 02:26:42.000000', '1', '2024-02-29 08:49:14.000000', 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 (2150, '发送测试站内信', 'system:notify-template:send-notify', 3, 5, 2145, '', '', '', null, 0, 1, 1, 1, '1', '2023-01-28 10:54:43.000000', '1', '2023-01-28 10:54:43.000000', 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 (2149, '站内信模板删除', 'system:notify-template:delete', 3, 4, 2145, '', '', '', null, 0, 1, 1, 1, '', '2023-01-28 02:26:42.000000', '', '2023-01-28 02:26:42.000000', 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 (2148, '站内信模板更新', 'system:notify-template:update', 3, 3, 2145, '', '', '', null, 0, 1, 1, 1, '', '2023-01-28 02:26:42.000000', '', '2023-01-28 02:26:42.000000', 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 (2147, '站内信模板创建', 'system:notify-template:create', 3, 2, 2145, '', '', '', null, 0, 1, 1, 1, '', '2023-01-28 02:26:42.000000', '', '2023-01-28 02:26:42.000000', 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 (2146, '站内信模板查询', 'system:notify-template:query', 3, 1, 2145, '', '', '', null, 0, 1, 1, 1, '', '2023-01-28 02:26:42.000000', '', '2023-01-28 02:26:42.000000', 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 (2130, '邮箱管理', '', 2, 2, 2739, 'mail', 'fa-solid:mail-bulk', null, null, 0, 1, 1, 1, '1', '2023-01-25 17:27:44.000000', '1', '2024-04-22 23:56:08.000000', 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 (2141, '邮件记录', '', 2, 0, 2130, 'mail-log', 'fa:edit', 'system/mail/log/index', 'SystemMailLog', 0, 1, 1, 1, '', '2023-01-26 02:16:50.000000', '1', '2024-02-29 08:48:51.000000', 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 (2142, '日志查询', 'system:mail-log:query', 3, 1, 2141, '', '', '', null, 0, 1, 1, 1, '', '2023-01-26 02:16:50.000000', '', '2023-01-26 02:16:50.000000', 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 (2136, '邮件模版', '', 2, 0, 2130, 'mail-template', 'fa:tag', 'system/mail/template/index', 'SystemMailTemplate', 0, 1, 1, 1, '', '2023-01-25 12:05:31.000000', '1', '2024-02-29 08:48:41.000000', 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 (2143, '发送测试邮件', 'system:mail-template:send-mail', 3, 5, 2136, '', '', '', null, 0, 1, 1, 1, '1', '2023-01-26 23:29:15.000000', '1', '2023-01-26 23:29:15.000000', 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 (2140, '模版删除', 'system:mail-template:delete', 3, 4, 2136, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 12:05:31.000000', '', '2023-01-25 12:05:31.000000', 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 (2139, '模版更新', 'system:mail-template:update', 3, 3, 2136, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 12:05:31.000000', '', '2023-01-25 12:05:31.000000', 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 (2138, '模版创建', 'system:mail-template:create', 3, 2, 2136, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 12:05:31.000000', '', '2023-01-25 12:05:31.000000', 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 (2137, '模版查询', 'system:mail-template:query', 3, 1, 2136, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 12:05:31.000000', '', '2023-01-25 12:05:31.000000', 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 (2131, '邮箱账号', '', 2, 0, 2130, 'mail-account', 'fa:universal-access', 'system/mail/account/index', 'SystemMailAccount', 0, 1, 1, 1, '', '2023-01-25 09:33:48.000000', '1', '2024-02-29 08:48:16.000000', 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 (2135, '账号删除', 'system:mail-account:delete', 3, 4, 2131, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 09:33:48.000000', '', '2023-01-25 09:33:48.000000', 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 (2134, '账号更新', 'system:mail-account:update', 3, 3, 2131, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 09:33:48.000000', '', '2023-01-25 09:33:48.000000', 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 (2133, '账号创建', 'system:mail-account:create', 3, 2, 2131, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 09:33:48.000000', '', '2023-01-25 09:33:48.000000', 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 (2132, '账号查询', 'system:mail-account:query', 3, 1, 2131, '', '', '', null, 0, 1, 1, 1, '', '2023-01-25 09:33:48.000000', '', '2023-01-25 09:33:48.000000', 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 (1093, '短信管理', '', 1, 1, 2739, 'sms', 'ep:message', null, null, 0, 1, 1, 1, '1', '2021-04-05 01:10:16.000000', '1', '2024-04-22 23:56:03.000000', 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 (1107, '短信日志', '', 2, 2, 1093, 'sms-log', 'fa:edit', 'system/sms/log/index', 'SystemSmsLog', 0, 1, 1, 1, '', '2021-04-11 08:37:05.000000', '1', '2024-02-29 08:49:02.000000', 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 (1109, '短信日志导出', 'system:sms-log:export', 3, 5, 1107, '', '', '', null, 0, 1, 1, 1, '', '2021-04-11 08:37:05.000000', '', '2022-04-20 17:03:10.000000', 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 (1108, '短信日志查询', 'system:sms-log:query', 3, 1, 1107, '', '', '', null, 0, 1, 1, 1, '', '2021-04-11 08:37:05.000000', '', '2022-04-20 17:03:10.000000', 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 (1100, '短信模板', '', 2, 1, 1093, 'sms-template', 'ep:connection', 'system/sms/template/index', 'SystemSmsTemplate', 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '1', '2024-02-29 01:16:18.000000', 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 (1106, '发送测试短信', 'system:sms-template:send-sms', 3, 6, 1100, '', '', '', null, 0, 1, 1, 1, '1', '2021-04-11 00:26:40.000000', '1', '2022-04-20 17:03:10.000000', 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 (1105, '短信模板导出', 'system:sms-template:export', 3, 5, 1100, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '', '2022-04-20 17:03:10.000000', 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 (1104, '短信模板删除', 'system:sms-template:delete', 3, 4, 1100, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '', '2022-04-20 17:03:10.000000', 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 (1103, '短信模板更新', 'system:sms-template:update', 3, 3, 1100, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '', '2022-04-20 17:03:10.000000', 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 (1102, '短信模板创建', 'system:sms-template:create', 3, 2, 1100, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '', '2022-04-20 17:03:10.000000', 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 (1101, '短信模板查询', 'system:sms-template:query', 3, 1, 1100, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 17:35:17.000000', '', '2022-04-20 17:03:10.000000', 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 (1094, '短信渠道', '', 2, 0, 1093, 'sms-channel', 'fa:stack-exchange', 'system/sms/channel/index', 'SystemSmsChannel', 0, 1, 1, 1, '', '2021-04-01 11:07:15.000000', '1', '2024-02-29 01:15:54.000000', 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 (1098, '短信渠道删除', 'system:sms-channel:delete', 3, 4, 1094, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 11:07:15.000000', '', '2022-04-20 17:03:10.000000', 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 (1097, '短信渠道更新', 'system:sms-channel:update', 3, 3, 1094, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 11:07:15.000000', '', '2022-04-20 17:03:10.000000', 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 (1096, '短信渠道创建', 'system:sms-channel:create', 3, 2, 1094, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 11:07:15.000000', '', '2022-04-20 17:03:10.000000', 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 (1095, '短信渠道查询', 'system:sms-channel:query', 3, 1, 1094, '', '', '', null, 0, 1, 1, 1, '', '2021-04-01 11:07:15.000000', '', '2022-04-20 17:03:10.000000', 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 (107, '通知公告', '', 2, 4, 2739, 'notice', 'ep:takeaway-box', 'system/notice/index', 'SystemNotice', 0, 1, 1, 1, 'admin', '2021-01-05 17:03:48.000000', '1', '2024-04-22 23:56:17.000000', 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 (1039, '公告删除', 'system:notice:delete', 3, 4, 107, '', '', '', null, 0, 1, 1, 1, 'admin', '2021-01-05 17:03:48.000000', '1', '2022-04-20 17:03:10.000000', 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 (1038, '公告修改', 'system:notice:update', 3, 3, 107, '', '', '', null, 0, 1, 1, 1, 'admin', '2021-01-05 17:03:48.000000', '1', '2022-04-20 17:03:10.000000', 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 (1037, '公告新增', 'system:notice:create', 3, 2, 107, '', '', '', null, 0, 1, 1, 1, 'admin', '2021-01-05 17:03:48.000000', '1', '2022-04-20 17:03:10.000000', 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 (1036, '公告查询', 'system:notice:query', 3, 1, 107, '#', '#', '', null, 0, 1, 1, 1, 'admin', '2021-01-05 17:03:48.000000', '', '2022-04-20 17:03:10.000000', 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 (6500, '数据总线', '', 1, 20, 1, 'databus', 'ep:data-board', '', 'DatabusRoot', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.690389', 'admin', '2025-10-17 17:14:18.690390', 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 (1861234567890123500, '数据同步', '', 1, 50, 6500, 'sync', 'ep:connection', null, null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.048176', '1', '2025-11-25 17:33:29.370218', 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 (2874, '全量同步任务', '', 2, 6, 1861234567890123500, 'fulltask', 'ep:upload', 'databus/sync/fulltask/index', 'DatabusSyncFullTask', 0, 1, 1, 0, 'admin', '2025-11-28 18:24:33.619358', 'admin', '2025-11-28 18:24:33.619360', 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 (2878, '全量同步任务取消', 'databus:sync:full-task:cancel', 3, 4, 2874, '', '', '', '', 0, 1, 1, 0, 'admin', '2025-11-28 18:24:33.619369', 'admin', '2025-11-28 18:24:33.619369', 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 (2877, '全量同步任务执行', 'databus:sync:full-task:execute', 3, 3, 2874, '', '', '', '', 0, 1, 1, 0, 'admin', '2025-11-28 18:24:33.619368', 'admin', '2025-11-28 18:24:33.619368', 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 (2876, '全量同步任务创建', 'databus:sync:full-task:create', 3, 2, 2874, '', '', '', '', 0, 1, 1, 0, 'admin', '2025-11-28 18:24:33.619367', 'admin', '2025-11-28 18:24:33.619367', 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 (2875, '全量同步任务查询', 'databus:sync:full-task:query', 3, 1, 2874, '', '', '', '', 0, 1, 1, 0, 'admin', '2025-11-28 18:24:33.619366', 'admin', '2025-11-28 18:24:33.619366', 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 (1861234567890123523, '死信队列', '', 2, 5, 1861234567890123500, 'deadletter', 'ep:warning', 'databus/sync/deadletter/index', 'DatabusSyncDeadLetter', 0, 1, 1, 1, '', '2025-11-25 17:28:16.259929', '', '2025-11-25 17:28:16.259932', 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 (1861234567890123527, '忽略死信', 'databus:sync:dead-letter:ignore', 3, 4, 1861234567890123523, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.282800', '', '2025-11-25 17:28:16.282802', 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 (1861234567890123526, '重新投递', 'databus:sync:dead-letter:reprocess', 3, 3, 1861234567890123523, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.277958', '', '2025-11-25 17:28:16.277960', 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 (1861234567890123525, '查看详情', 'databus:sync:dead-letter:detail', 3, 2, 1861234567890123523, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.271126', '', '2025-11-25 17:28:16.271128', 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 (1861234567890123524, '查询死信', 'databus:sync:dead-letter:query', 3, 1, 1861234567890123523, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.267617', '', '2025-11-25 17:28:16.267619', 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 (1861234567890123519, '同步日志', '', 2, 4, 1861234567890123500, 'pushlog', 'ep:document', 'databus/sync/pushlog/index', 'DatabusSyncLog', 0, 1, 1, 1, '', '2025-11-25 17:28:16.210732', '', '2025-11-25 17:28:16.210733', 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 (1861234567890123522, '导出日志', 'databus:sync:log:export', 3, 3, 1861234567890123519, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.252291', '', '2025-11-25 17:28:16.252296', 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 (1861234567890123521, '查看详情', 'databus:sync:log:detail', 3, 2, 1861234567890123519, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.230811', '', '2025-11-25 17:28:16.230816', 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 (1861234567890123520, '查询日志', 'databus:sync:log:query', 3, 1, 1861234567890123519, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.220956', '', '2025-11-25 17:28:16.220959', 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 (1861234567890123513, '订阅管理', '', 2, 3, 1861234567890123500, 'subscription', 'ep:guide', 'databus/sync/subscription/index', 'DatabusSyncSubscription', 0, 1, 1, 1, '', '2025-11-25 17:28:16.163388', '1', '2025-11-28 18:48:54.496674', 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 (1861234567890123518, '导出订阅', 'databus:sync:subscription:export', 3, 5, 1861234567890123513, '', '', '', null, 1, 1, 1, 1, '', '2025-11-25 17:28:16.203898', '', '2025-11-25 17:28:16.203899', 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 (1861234567890123517, '删除订阅', 'databus:sync:subscription:delete', 3, 4, 1861234567890123513, '', '', '', null, 1, 1, 1, 1, '', '2025-11-25 17:28:16.197362', '', '2025-11-25 17:28:16.197363', 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 (1861234567890123516, '更新订阅', 'databus:sync:subscription:update', 3, 3, 1861234567890123513, '', '', '', null, 1, 1, 1, 1, '', '2025-11-25 17:28:16.191555', '', '2025-11-25 17:28:16.191556', 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 (1861234567890123515, '创建订阅', 'databus:sync:subscription:create', 3, 2, 1861234567890123513, '', '', '', null, 1, 1, 1, 1, '', '2025-11-25 17:28:16.185782', '', '2025-11-25 17:28:16.185783', 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 (1861234567890123514, '查询订阅', 'databus:sync:subscription:query', 3, 1, 1861234567890123513, '', '', '', null, 1, 1, 1, 1, '', '2025-11-25 17:28:16.177918', '', '2025-11-25 17:28:16.177920', 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 (1861234567890123507, '客户端管理', '', 2, 2, 1861234567890123500, 'client', 'ep:monitor', 'databus/sync/client/index', 'DatabusSyncClient', 0, 1, 1, 1, '', '2025-11-25 17:28:16.111525', '', '2025-11-25 17:28:16.111526', 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 (1861234567890123512, '导出客户端', 'databus:sync:client:export', 3, 5, 1861234567890123507, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.158423', '', '2025-11-25 17:28:16.158424', 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 (1861234567890123511, '删除客户端', 'databus:sync:client:delete', 3, 4, 1861234567890123507, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.147803', '', '2025-11-25 17:28:16.147804', 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 (1861234567890123510, '更新客户端', 'databus:sync:client:update', 3, 3, 1861234567890123507, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.137440', '', '2025-11-25 17:28:16.137441', 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 (1861234567890123509, '创建客户端', 'databus:sync:client:create', 3, 2, 1861234567890123507, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.125308', '', '2025-11-25 17:28:16.125310', 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 (1861234567890123508, '查询客户端', 'databus:sync:client:query', 3, 1, 1861234567890123507, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.117846', '', '2025-11-25 17:28:16.117847', 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 (1861234567890123501, '事件管理', '', 2, 1, 1861234567890123500, 'event', 'ep:bell', 'databus/sync/event/index', 'DatabusSyncEvent', 0, 1, 1, 1, '', '2025-11-25 17:28:16.052979', '1', '2025-11-25 17:34:34.591416', 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 (1861234567890123506, '导出事件', 'databus:sync:event:export', 3, 5, 1861234567890123501, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.101020', '', '2025-11-25 17:28:16.101037', 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 (1861234567890123505, '删除事件', 'databus:sync:event:delete', 3, 4, 1861234567890123501, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.086845', '', '2025-11-25 17:28:16.086847', 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 (1861234567890123504, '更新事件', 'databus:sync:event:update', 3, 3, 1861234567890123501, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.079252', '', '2025-11-25 17:28:16.079256', 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 (1861234567890123503, '创建事件', 'databus:sync:event:create', 3, 2, 1861234567890123501, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.071706', '', '2025-11-25 17:28:16.071710', 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 (1861234567890123502, '查询事件', 'databus:sync:event:query', 3, 1, 1861234567890123501, '', '', '', null, 0, 1, 1, 1, '', '2025-11-25 17:28:16.060256', '', '2025-11-25 17:28:16.060257', 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 (6504, '访问日志', 'databus:gateway:access-log:query', 2, 40, 6500, 'access-log', 'ep:document', 'databus/accesslog/index', 'DatabusAccessLog', 0, 1, 1, 1, 'admin', '2025-10-29 14:39:35.126020', 'admin', '2025-10-29 14:39:35.126022', 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', '2025-10-29 14:39:35.160454', 'admin', '2025-10-29 14:39:35.160456', 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 (6503, '限流策略', 'databus:policy:query', 2, 30, 6500, 'policy/rate-limit', 'ep:stopwatch', 'databus/policy/RateLimitPolicy', 'DatabusRateLimitPolicy', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.690396', 'admin', '2025-10-17 17:14:18.690396', 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 (650304, '策略删除', 'databus:policy:delete', 3, 4, 6503, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734365', 'admin', '2025-10-17 17:14:18.734365', 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 (650303, '策略修改', 'databus:policy:update', 3, 3, 6503, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734363', 'admin', '2025-10-17 17:14:18.734363', 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 (650302, '策略新增', 'databus:policy:create', 3, 2, 6503, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734362', 'admin', '2025-10-17 17:14:18.734362', 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 (650301, '策略查询', 'databus:policy:query', 3, 1, 6503, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734360', 'admin', '2025-10-17 17:14:18.734360', 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 (6502, '客户端凭证', 'databus:credential:query', 2, 20, 6500, 'credential', 'ep:key', 'databus/credential/index', 'DatabusCredential', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.690395', 'admin', '2025-10-17 17:14:18.690395', 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 (650204, '凭证删除', 'databus:credential:delete', 3, 4, 6502, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734358', 'admin', '2025-10-17 17:14:18.734358', 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 (650203, '凭证修改', 'databus:credential:update', 3, 3, 6502, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734356', 'admin', '2025-10-17 17:14:18.734356', 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 (650202, '凭证新增', 'databus:credential:create', 3, 2, 6502, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734354', 'admin', '2025-10-17 17:14:18.734354', 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 (650201, '凭证查询', 'databus:credential:query', 3, 1, 6502, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734352', 'admin', '2025-10-17 17:14:18.734352', 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 (6501, 'API 定义', 'databus:gateway:query', 2, 10, 6500, 'gateway', 'ep:list', 'databus/gateway/index', 'DatabusGateway', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.690394', 'admin', '2025-10-17 17:14:18.690394', 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 (650110, 'API版本对比', 'databus:gateway:version:compare', 3, 10, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-30 14:37:51.432572', 'admin', '2025-10-30 14:37:51.432572', 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 (650109, 'API版本回滚', 'databus:gateway:version:rollback', 3, 9, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-30 14:37:51.432571', 'admin', '2025-10-30 14:37:51.432571', 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 (650108, 'API版本详情', 'databus:gateway:version:detail', 3, 8, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-30 14:37:51.432570', 'admin', '2025-10-30 14:37:51.432570', 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 (650107, 'API版本历史', 'databus:gateway:version:query', 3, 7, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-30 14:37:51.432564', 'admin', '2025-10-30 14:37:51.432566', 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 (650106, 'API 刷新', 'databus:gateway:refresh', 3, 6, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734350', 'admin', '2025-10-17 17:14:18.734351', 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 (650105, 'API 调试', 'databus:gateway:invoke', 3, 5, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734349', 'admin', '2025-10-17 17:14:18.734349', 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 (650104, 'API 删除', 'databus:gateway:delete', 3, 4, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734347', 'admin', '2025-10-17 17:14:18.734347', 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 (650103, 'API 编辑', 'databus:gateway:update', 3, 3, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734345', 'admin', '2025-10-17 17:14:18.734345', 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 (650102, 'API 新建', 'databus:gateway:create', 3, 2, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734343', 'admin', '2025-10-17 17:14:18.734344', 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 (650101, 'API 查询', 'databus:gateway:query', 3, 1, 6501, '', '', '', '', 0, 1, 1, 1, 'admin', '2025-10-17 17:14:18.734335', 'admin', '2025-10-17 17:14:18.734338', 0);
COMMIT; COMMIT;
-- SET IDENTITY_INSERT system_menu OFF; -- SET IDENTITY_INSERT system_menu OFF;
-- @formatter:on -- @formatter:on
@@ -1944,7 +2060,7 @@ COMMENT ON TABLE system_oauth2_refresh_token IS 'OAuth2 刷新令牌';
CREATE TABLE system_operate_log ( CREATE TABLE system_operate_log (
id bigint NOT NULL PRIMARY KEY, id bigint NOT NULL PRIMARY KEY,
trace_id varchar(64) DEFAULT '' NULL, trace_id varchar(64) DEFAULT '' NULL,
user_id bigint NOT NULL, user_id bigint NULL,
user_type smallint DEFAULT 0 NOT NULL, user_type smallint DEFAULT 0 NOT NULL,
type varchar(50) NOT NULL, type varchar(50) NOT NULL,
sub_type varchar(50) NOT NULL, sub_type varchar(50) NOT NULL,
@@ -3560,7 +3676,7 @@ COMMENT ON TABLE system_users IS '用户信息表';
-- ---------------------------- -- ----------------------------
-- @formatter:off -- @formatter:off
-- SET IDENTITY_INSERT system_users ON; -- SET IDENTITY_INSERT system_users ON;
INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', NULL, '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 1, 'http://test.zt.iocoder.cn/test/20250502/avatar_1746154660449.png', 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '后台管理', NULL, '管理员', '[1,2]', '11aoteman@126.com', '18818260277', 2, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-05-10 18:03:15', 'admin', '2021-01-05 17:03:47', NULL, '2025-05-10 18:03:15', '0', 1);
INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'zt', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', 'ZT', NULL, '不要吓我', '[1]', 'zt@iocoder.cn', '15601691300', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (100, 'zt', '$2a$04$h.aaPKgO.odHepnk5PCsWeEwKdojFWdTItxGKfx1r0e1CSeBzsTJ6', 'ZT', NULL, '不要吓我', '[1]', 'zt@iocoder.cn', '15601691300', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-04-08 09:36:40', '', '2021-01-07 09:07:17', NULL, '2025-04-21 14:23:08', '0', 1);
INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, NULL, NULL, 'yuanma@iocoder.cn', '15601701300', 0, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, NULL, NULL, 'yuanma@iocoder.cn', '15601701300', 0, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2025-04-21 14:23:08', '0', 1);
INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, NULL, '[1,2]', '111@qq.com', '15601691200', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', '0', 1); INSERT INTO system_users (id, username, password, nickname, workcode, remark, post_ids, email, mobile, sex, user_source, avatar, status, login_ip, login_date, creator, create_time, updater, update_time, deleted, tenant_id) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, NULL, '[1,2]', '111@qq.com', '15601691200', 1, 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-03-28 20:01:16', '', '2021-01-21 02:13:53', NULL, '2025-04-21 14:23:08', '0', 1);
@@ -3897,6 +4013,7 @@ CREATE TABLE infra_bsn_file (
file_id bigint NOT NULL, file_id bigint NOT NULL,
file_name varchar(500) DEFAULT '' NULL, file_name varchar(500) DEFAULT '' NULL,
src varchar(100) DEFAULT '' NULL, src varchar(100) DEFAULT '' NULL,
status smallint DEFAULT 1 NOT NULL,
creator varchar(64) DEFAULT '' NULL, creator varchar(64) DEFAULT '' NULL,
create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
updater varchar(64) DEFAULT '' NULL, updater varchar(64) DEFAULT '' NULL,
@@ -3911,13 +4028,14 @@ COMMENT ON COLUMN infra_bsn_file.bsn_cd IS '业务编码';
COMMENT ON COLUMN infra_bsn_file.file_id IS '附件fileId'; COMMENT ON COLUMN infra_bsn_file.file_id IS '附件fileId';
COMMENT ON COLUMN infra_bsn_file.file_name IS '附件名称'; COMMENT ON COLUMN infra_bsn_file.file_name IS '附件名称';
COMMENT ON COLUMN infra_bsn_file.src IS '附件来源'; COMMENT ON COLUMN infra_bsn_file.src IS '附件来源';
COMMENT ON COLUMN infra_bsn_file.status IS '状态1-正常0-禁用)';
COMMENT ON COLUMN infra_bsn_file.creator IS '创建者'; COMMENT ON COLUMN infra_bsn_file.creator IS '创建者';
COMMENT ON COLUMN infra_bsn_file.create_time IS '创建时间'; COMMENT ON COLUMN infra_bsn_file.create_time IS '创建时间';
COMMENT ON COLUMN infra_bsn_file.updater IS '更新者'; COMMENT ON COLUMN infra_bsn_file.updater IS '更新者';
COMMENT ON COLUMN infra_bsn_file.update_time IS '最后更新时间'; COMMENT ON COLUMN infra_bsn_file.update_time IS '最后更新时间';
COMMENT ON COLUMN infra_bsn_file.deleted IS '是否删除'; COMMENT ON COLUMN infra_bsn_file.deleted IS '是否删除';
COMMENT ON COLUMN infra_bsn_file.tenant_id IS '租户编号'; COMMENT ON COLUMN infra_bsn_file.tenant_id IS '租户编号';
COMMENT ON TABLE infra_bsn_file IS '业务附件表'; COMMENT ON TABLE infra_bsn_file IS '业务附件关联';
-- ---------------------------- -- ----------------------------
-- Table structure for system_seq -- Table structure for system_seq
@@ -4004,3 +4122,192 @@ COMMENT ON COLUMN system_seq_rcd.updater IS '更新者';
COMMENT ON COLUMN system_seq_rcd.update_time IS '更新时间'; COMMENT ON COLUMN system_seq_rcd.update_time IS '更新时间';
COMMENT ON COLUMN system_seq_rcd.deleted IS '是否删除'; COMMENT ON COLUMN system_seq_rcd.deleted IS '是否删除';
COMMENT ON TABLE system_seq_rcd IS '系统序列号记录表'; COMMENT ON TABLE system_seq_rcd IS '系统序列号记录表';
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'1948328245618204673', '业务附件关联管理', '', 2, 0, 1243,
'business-file', '', 'infra/businessfile/index', 0, 'BusinessFile'
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1951092724040097793', '业务附件关联查询', 'infra:business-file:query', 3, 1, 1948328245618204673,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1951092724040097794', '业务附件关联创建', 'infra:business-file:create', 3, 2, 1948328245618204673,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1951092724040097795', '业务附件关联更新', 'infra:business-file:update', 3, 3, 1948328245618204673,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1951092724040097796', '业务附件关联删除', 'infra:business-file:delete', 3, 4, 1948328245618204673,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1951092724040097797', '业务附件关联导出', 'infra:business-file:export', 3, 5, 1948328245618204673,
'', '', '', 0
);
-- 数据命名与简写标准菜单
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'1947909810016006146', '数据命名与简写标准管理', '', 2, 0, 2,
'standard-name', '', 'infra/standardname/index', 0, 'StandardName'
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953301310553645058', '数据命名与简写标准查询', 'infra:standard-name:query', 3, 1, 1947909810016006146,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953301310553645059', '数据命名与简写标准创建', 'infra:standard-name:create', 3, 2, 1947909810016006146,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953301310553645060', '数据命名与简写标准更新', 'infra:standard-name:update', 3, 3, 1947909810016006146,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953301310553645061', '数据命名与简写标准删除', 'infra:standard-name:delete', 3, 4, 1947909810016006146,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953301310553645062', '数据命名与简写标准导出', 'infra:standard-name:export', 3, 5, 1947909810016006146,
'', '', '', 0
);
-- 系统序列号菜单
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'1953701540574969857', '系统序列号管理', '', 2, 0, 1,
'sequence', '', 'system/sequence/index', 0, 'Sequence'
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953702581324398594', '系统序列号查询', 'system:sequence:query', 3, 1, 1953701540574969857,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953702581324398595', '系统序列号创建', 'system:sequence:create', 3, 2, 1953701540574969857,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953702581324398596', '系统序列号更新', 'system:sequence:update', 3, 3, 1953701540574969857,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953702581324398597', '系统序列号删除', 'system:sequence:delete', 3, 4, 1953701540574969857,
'', '', '', 0
);
INSERT INTO system_menu(
id, name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'1953702581324398598', '系统序列号导出', 'system:sequence:export', 3, 5, 1953701540574969857,
'', '', '', 0
);
-- 系统序列号相关字典数据初始化
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (500, '系统序列号循环类型', 'system_sequence_cycle_type', 0, '系统序列号循环类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5001, 1, '年循环', 'Y', 'system_sequence_cycle_type', 0, 'primary', '', '年循环。示例: 2025', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5002, 2, '年-月循环', 'Y-M', 'system_sequence_cycle_type', 0, 'success', '', '年-月循环。示例: 2025-08', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5003, 3, '年月紧凑', 'YM', 'system_sequence_cycle_type', 0, 'info', '', '年月紧凑。示例: 202508', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5004, 4, '两位年+月', 'yM', 'system_sequence_cycle_type', 0, 'warning', '', '两位年+月。示例: 2508', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5005, 5, '年-月-日循环', 'Y-M-D', 'system_sequence_cycle_type', 0, 'primary', '', '年-月-日循环。示例: 2025-08-08', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5006, 6, '年月日紧凑', 'YMD', 'system_sequence_cycle_type', 0, 'success', '', '年月日紧凑。示例: 20250808', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5007, 7, '两位年+月日', 'yMD', 'system_sequence_cycle_type', 0, 'info', '', '两位年+月日。示例: 250808', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5008, 8, '自定义循环值', 'CUST', 'system_sequence_cycle_type', 0, 'warning', '', '自定义循环值;若未传 circulationValue则默认用 seqId', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5009, 9, '仅前缀', 'PFX', 'system_sequence_cycle_type', 0, 'danger', '', '仅前缀,不需要时间循环值(不设置则不抛错)', 'admin', SYSDATE, 'admin', SYSDATE, 0);
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (501, '系统序列号分段类型', 'system_sequence_detail_type', 0, '系统序列号分段类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5011, 1, '默认字符分段', 'STR', 'system_sequence_detail_type', 0, 'primary', '', '固定字符串分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5012, 2, '给定字符分段', 'INPUT', 'system_sequence_detail_type', 0, 'success', '', '根据输入参数动态生成的字符分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5013, 3, '日期分段', 'DATE', 'system_sequence_detail_type', 0, 'info', '', '基于日期格式的分段', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5014, 4, '流水号分段', 'SEQ', 'system_sequence_detail_type', 0, 'warning', '', '自增流水号分段', 'admin', SYSDATE, 'admin', SYSDATE, 0);
INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted, deleted_time)
VALUES (502, '系统序列号分段规则类型', 'system_sequence_detail_rule_type', 0, '系统序列号分段规则类型枚举', 'admin', SYSDATE, 'admin', SYSDATE, 0, NULL);
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted)
VALUES
(5021, 1, '固定值', 'FIXED', 'system_sequence_detail_rule_type', 0, 'primary', '', '固定字符串值', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5022, 2, '日期格式', 'DATE', 'system_sequence_detail_rule_type', 0, 'success', '', '日期格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5023, 3, '数字格式', 'NUMBER', 'system_sequence_detail_rule_type', 0, 'info', '', '数字格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0),
(5024, 4, '自定义格式', 'CUSTOM', 'system_sequence_detail_rule_type', 0, 'warning', '', '自定义格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0);

View File

@@ -19,7 +19,8 @@ CREATE TABLE databus_api_definition_credential (
deleted BIT DEFAULT '0' NOT NULL deleted BIT DEFAULT '0' NOT NULL
); );
CREATE UNIQUE INDEX uk_databus_api_definition_credential ON databus_api_definition_credential (api_id, credential_id, deleted); -- 去掉错误的唯一索引逻辑
-- CREATE UNIQUE INDEX uk_databus_api_definition_credential ON databus_api_definition_credential (api_id, credential_id, deleted);
CREATE INDEX idx_databus_api_definition_credential_api ON databus_api_definition_credential (api_id); CREATE INDEX idx_databus_api_definition_credential_api ON databus_api_definition_credential (api_id);
CREATE INDEX idx_databus_api_definition_credential_cred ON databus_api_definition_credential (credential_id); CREATE INDEX idx_databus_api_definition_credential_cred ON databus_api_definition_credential (credential_id);

View File

@@ -0,0 +1,10 @@
-- 达梦8数据库DDL脚本
-- 为 bpm_process_definition_info 表添加 restart 字段
-- 是否允许重新发起
ALTER TABLE bpm_process_definition_info ADD COLUMN restart bit DEFAULT '1' NOT NULL;
-- 添加字段注释
COMMENT ON COLUMN bpm_process_definition_info.restart IS '是否允许重新发起';

View File

@@ -26,12 +26,13 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url> <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties> <properties>
<revision>3.0.45</revision> <revision>3.0.46</revision>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 --> <!-- 统一依赖管理 -->
<spring.boot.version>3.4.5</spring.boot.version> <spring.boot.version>3.4.5</spring.boot.version>
<spring.cloud.version>2024.0.1</spring.cloud.version> <spring.cloud.version>2024.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version> <spring.cloud.alibaba.version>2023.0.3.2</spring.cloud.alibaba.version>
<seata.version>2.4.0</seata.version>
<!-- Web 相关 --> <!-- Web 相关 -->
<springdoc.version>2.8.3</springdoc.version> <springdoc.version>2.8.3</springdoc.version>
<knife4j.version>4.6.0</knife4j.version> <knife4j.version>4.6.0</knife4j.version>
@@ -133,6 +134,24 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- 分布式事务 Seata (覆盖 Spring Cloud Alibaba 中的 2.1.0 版本) -->
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<!-- Seata 达梦数据库补丁 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-seata-dm</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>
<groupId>io.github.mouzt</groupId> <groupId>io.github.mouzt</groupId>

View File

@@ -33,6 +33,7 @@
<module>zt-spring-boot-starter-biz-data-permission</module> <module>zt-spring-boot-starter-biz-data-permission</module>
<module>zt-spring-boot-starter-biz-ip</module> <module>zt-spring-boot-starter-biz-ip</module>
<module>zt-spring-boot-starter-biz-business</module> <module>zt-spring-boot-starter-biz-business</module>
<module>zt-spring-boot-starter-seata-dm</module>
</modules> </modules>
<artifactId>zt-framework</artifactId> <artifactId>zt-framework</artifactId>

View File

@@ -8,6 +8,7 @@ import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.common.util.spring.SpringUtils; import com.zt.plat.framework.common.util.spring.SpringUtils;
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.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import com.zt.plat.framework.web.core.util.WebFrameworkUtils; import com.zt.plat.framework.web.core.util.WebFrameworkUtils;
import com.zt.plat.module.system.api.dept.DeptApi; import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.dto.CompanyDeptInfoRespDTO; import com.zt.plat.module.system.api.dept.dto.CompanyDeptInfoRespDTO;
@@ -197,6 +198,9 @@ public class BusinessDeptHandleUtil {
} }
CompanyContextHolder.setIgnore(false); CompanyContextHolder.setIgnore(false);
CompanyContextHolder.setCompanyId(Long.valueOf(info.getCompanyId())); CompanyContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
DeptContextHolder.setIgnore(false);
DeptContextHolder.setCompanyId(Long.valueOf(info.getCompanyId()));
DeptContextHolder.setDeptId(Long.valueOf(info.getDeptId()));
return true; return true;
} }
} }

View File

@@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
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.core.env.Environment;
import org.springframework.util.StringUtils;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
@@ -18,14 +20,12 @@ import java.util.Set;
public class BusinessDataPermissionConfiguration { public class BusinessDataPermissionConfiguration {
@Bean @Bean
public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext) { public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext, Environment environment) {
Set<String> basePackages = new LinkedHashSet<>(); Set<String> basePackages = new LinkedHashSet<>();
addConfiguredBasePackages(environment, basePackages);
if (AutoConfigurationPackages.has(beanFactory)) { if (AutoConfigurationPackages.has(beanFactory)) {
basePackages.addAll(AutoConfigurationPackages.get(beanFactory)); basePackages.addAll(AutoConfigurationPackages.get(beanFactory));
} }
if (basePackages.isEmpty()) {
basePackages.add("com.zt");
}
ClassLoader classLoader = applicationContext != null ClassLoader classLoader = applicationContext != null
? applicationContext.getClassLoader() ? applicationContext.getClassLoader()
: Thread.currentThread().getContextClassLoader(); : Thread.currentThread().getContextClassLoader();
@@ -35,6 +35,21 @@ public class BusinessDataPermissionConfiguration {
return new BusinessDataPermissionEntityScanner(basePackages, classLoader); return new BusinessDataPermissionEntityScanner(basePackages, classLoader);
} }
private void addConfiguredBasePackages(Environment environment, Set<String> basePackages) {
if (environment == null) {
return;
}
String configured = environment.getProperty("zt.info.base-package");
if (!StringUtils.hasText(configured)) {
return;
}
for (String pkg : configured.split("[,;\\s]+")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg.trim());
}
}
}
@Bean @Bean
public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) { public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) {
return rule -> scanner.getEntityMetadata().forEach(metadata -> { return rule -> scanner.getEntityMetadata().forEach(metadata -> {

View File

@@ -28,6 +28,14 @@ import java.util.*;
@Slf4j @Slf4j
public class BusinessDataPermissionEntityScanner { public class BusinessDataPermissionEntityScanner {
/**
* 临时排除的包前缀(物流模块 DO不参与数据权限扫描
*/
private static final Set<String> EXCLUDED_PACKAGE_PREFIXES = Set.of(
"com.zt.plat.module.backendlogistics",
"com.zt.plat.module.erp",
"com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO");
private final Set<String> basePackages; private final Set<String> basePackages;
private final ClassLoader classLoader; private final ClassLoader classLoader;
@@ -70,6 +78,9 @@ public class BusinessDataPermissionEntityScanner {
if (!StringUtils.hasText(className)) { if (!StringUtils.hasText(className)) {
continue; continue;
} }
if (isExcludedPackage(className)) {
continue;
}
try { try {
Class<?> clazz = ClassUtils.forName(className, classLoader); Class<?> clazz = ClassUtils.forName(className, classLoader);
if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) { if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) {
@@ -92,6 +103,15 @@ public class BusinessDataPermissionEntityScanner {
return new ArrayList<>(metadataMap.values()); return new ArrayList<>(metadataMap.values());
} }
private boolean isExcludedPackage(String className) {
for (String prefix : EXCLUDED_PACKAGE_PREFIXES) {
if (className.startsWith(prefix)) {
return true;
}
}
return false;
}
private EntityMetadata buildMetadata(Class<? extends BusinessBaseDO> entityClass) { private EntityMetadata buildMetadata(Class<? extends BusinessBaseDO> entityClass) {
String tableName = resolveTableName(entityClass); String tableName = resolveTableName(entityClass);
if (!StringUtils.hasText(tableName)) { if (!StringUtils.hasText(tableName)) {

View File

@@ -0,0 +1,42 @@
# 数据权限忽略与上下文覆盖说明
本文说明新增的公司/部门数据权限忽略能力,以及部门上下文对数据权限的覆盖策略。
## 新增注解
- `@CompanyDataPermissionIgnore(enable = "true")`
- `@DeptDataPermissionIgnore(enable = "true")`
用法:
- 可标记在类或方法上。
- `enable` 支持 Spring EL计算结果为 `true` 时生效,默认开启。
- 生效后,在方法执行期间临时设置忽略标记,结束后自动恢复。
## 忽略生效范围
- 公司数据权限:切面在进入方法时将 `CompanyContextHolder.setIgnore(true)`,数据权限规则检测到后直接放行。
- 部门数据权限:切面在进入方法时将 `DeptContextHolder.setIgnore(true)`,部门数据权限规则检测到后直接放行。
## 部门上下文覆盖策略
当未忽略部门数据权限且上下文存在有效部门 ID 时:
- 优先使用上下文中的单一部门作为过滤条件,避免因默认的 `ALL` 或“无部门且不可查看自己”导致放行或误判无权。
- 上下文部门不会修改原有的数据权限 DTO仅在当前计算中使用。
- 若上下文公司与缓存公司不一致,会记录告警日志,但仍按上下文部门过滤。
## 典型场景
1) **任务/全局调用需要暂时关闭数据权限**
- 在方法上标记 `@DeptDataPermissionIgnore``@CompanyDataPermissionIgnore`
2) **带部门上下文的接口调用**
- 请求预先设置 `DeptContextHolder.setContext(deptId, companyId)`
- 即便数据权限声明为 `all=true`,也会按该部门过滤,避免读出全量。
3) **无部门权限但指定了上下文部门**
- 即使 `deptIds` 为空且 `self=false`,只要上下文提供部门,也会使用该部门过滤,而非直接判定无权。
## 注意事项
- 忽略标记只作用于当前线程上下文,切面会在 `finally` 中恢复旧值,嵌套调用安全。
- 若需要同时忽略公司与部门数据权限,可叠加两个注解或在业务代码中分别设置忽略标记。

View File

@@ -1,6 +1,8 @@
package com.zt.plat.framework.datapermission.config; package com.zt.plat.framework.datapermission.config;
import com.zt.plat.framework.datapermission.core.aop.CompanyDataPermissionIgnoreAspect;
import com.zt.plat.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor; import com.zt.plat.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;
import com.zt.plat.framework.datapermission.core.aop.DeptDataPermissionIgnoreAspect;
import com.zt.plat.framework.datapermission.core.db.DataPermissionRuleHandler; import com.zt.plat.framework.datapermission.core.db.DataPermissionRuleHandler;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactory; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactory;
@@ -43,4 +45,14 @@ public class ZtDataPermissionAutoConfiguration {
return new DataPermissionAnnotationAdvisor(); return new DataPermissionAnnotationAdvisor();
} }
@Bean
public DeptDataPermissionIgnoreAspect deptDataPermissionIgnoreAspect() {
return new DeptDataPermissionIgnoreAspect();
}
@Bean
public CompanyDataPermissionIgnoreAspect companyDataPermissionIgnoreAspect() {
return new CompanyDataPermissionIgnoreAspect();
}
} }

View File

@@ -0,0 +1,21 @@
package com.zt.plat.framework.datapermission.core.annotation;
import java.lang.annotation.*;
/**
* 忽略公司数据权限的注解。
* <p>
* 标记在方法或类上时,匹配的调用会临时忽略公司类型的数据权限规则。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CompanyDataPermissionIgnore {
/**
* 是否开启忽略,默认开启。
* 支持 Spring EL 表达式,返回 true 时生效。
*/
String enable() default "true";
}

View File

@@ -0,0 +1,21 @@
package com.zt.plat.framework.datapermission.core.annotation;
import java.lang.annotation.*;
/**
* 忽略部门数据权限的注解。
* <p>
* 标记在方法或类上时,匹配的调用会临时忽略部门类型的数据权限规则。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DeptDataPermissionIgnore {
/**
* 是否开启忽略,默认开启。
* 支持 Spring EL 表达式,返回 true 时生效。
*/
String enable() default "true";
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.framework.datapermission.core.aop;
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
import com.zt.plat.framework.datapermission.core.annotation.CompanyDataPermissionIgnore;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 公司数据权限忽略切面,基于 {@link CompanyDataPermissionIgnore} 注解。
*/
@Aspect
@Slf4j
public class CompanyDataPermissionIgnoreAspect {
@Around("@within(companyDataPermissionIgnore) || @annotation(companyDataPermissionIgnore)")
public Object around(ProceedingJoinPoint joinPoint, CompanyDataPermissionIgnore companyDataPermissionIgnore) throws Throwable {
boolean oldIgnore = CompanyContextHolder.isIgnore();
try {
Object enable = SpringExpressionUtils.parseExpression(companyDataPermissionIgnore.enable());
if (Boolean.TRUE.equals(enable)) {
CompanyContextHolder.setIgnore(true);
}
return joinPoint.proceed();
} finally {
CompanyContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.framework.datapermission.core.aop;
import com.zt.plat.framework.common.util.spring.SpringExpressionUtils;
import com.zt.plat.framework.datapermission.core.annotation.DeptDataPermissionIgnore;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* 部门数据权限忽略切面,基于 {@link DeptDataPermissionIgnore} 注解。
*/
@Aspect
@Slf4j
public class DeptDataPermissionIgnoreAspect {
@Around("@within(deptDataPermissionIgnore) || @annotation(deptDataPermissionIgnore)")
public Object around(ProceedingJoinPoint joinPoint, DeptDataPermissionIgnore deptDataPermissionIgnore) throws Throwable {
boolean oldIgnore = DeptContextHolder.shouldIgnore();
try {
Object enable = SpringExpressionUtils.parseExpression(deptDataPermissionIgnore.enable());
if (Boolean.TRUE.equals(enable)) {
DeptContextHolder.setIgnore(true);
}
return joinPoint.proceed();
} finally {
DeptContextHolder.setIgnore(oldIgnore);
}
}
}

View File

@@ -10,7 +10,9 @@ import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.schema.Table;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck; import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
@@ -41,6 +43,7 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
} }
// 生成条件 // 生成条件
final Set<String> processed = new HashSet<>();
Expression allExpression = null; Expression allExpression = null;
for (DataPermissionRule rule : rules) { for (DataPermissionRule rule : rules) {
// 判断表名是否匹配 // 判断表名是否匹配
@@ -49,6 +52,14 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler {
continue; continue;
} }
// 同一张表 + 同一别名 + 同一规则 在一次 SQL 解析内仅处理一次,避免重复拼接条件
String aliasName = table.getAlias() == null ? "" : table.getAlias().getName();
String key = tableName + "|" + aliasName + "|" + rule.getClass().getName();
if (processed.contains(key)) {
continue;
}
processed.add(key);
// 单条规则的条件 // 单条规则的条件
Expression oneExpress = rule.getExpression(tableName, table.getAlias()); Expression oneExpress = rule.getExpression(tableName, table.getAlias());
if (oneExpress == null) { if (oneExpress == null) {

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.util.collection.CollectionUtils; import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule;
import com.zt.plat.framework.mybatis.core.util.MyBatisUtils; import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
@@ -49,6 +50,10 @@ public class CompanyDataPermissionRule implements DataPermissionRule {
@Override @Override
public Expression getExpression(String tableName, Alias tableAlias) { public Expression getExpression(String tableName, Alias tableAlias) {
// 显式忽略公司数据权限时直接放行
if (CompanyContextHolder.isIgnore()) {
return null;
}
// 业务拼接 Company 的条件 // 业务拼接 Company 的条件
if (getLoginUserCompanyId() == null) { if (getLoginUserCompanyId() == null) {
// 如果没有登录用户的公司编号,则不需要拼接条件 // 如果没有登录用户的公司编号,则不需要拼接条件

View File

@@ -3,6 +3,7 @@ package com.zt.plat.framework.datapermission.core.rule.dept;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi; import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi;
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import com.zt.plat.framework.common.enums.UserTypeEnum; import com.zt.plat.framework.common.enums.UserTypeEnum;
@@ -14,7 +15,7 @@ import com.zt.plat.framework.mybatis.core.util.MyBatisUtils;
import com.zt.plat.framework.security.core.LoginUser; import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder; import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
@@ -108,6 +109,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
return null; return null;
} }
// 显式忽略部门数据权限时直接放行
if (DeptContextHolder.shouldIgnore()) {
return null;
}
// 获得数据权限 // 获得数据权限
DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class); DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
// 从上下文中拿不到,则调用逻辑进行获取 // 从上下文中拿不到,则调用逻辑进行获取
@@ -136,21 +142,42 @@ public class DeptDataPermissionRule implements DataPermissionRule {
} }
} }
// 情况一,如果是 ALL 可查看全部,则无需拼接条件 // 若存在部门上下文,优先使用上下文中的单一部门,必要时校验公司一致性
if (deptDataPermission.getAll()) { Long ctxDeptId = DeptContextHolder.getDeptId();
if (ctxDeptId != null && ctxDeptId > 0L) {
Long currentCompanyId = CompanyContextHolder.getCompanyId();
Long ctxCompanyId = DeptContextHolder.getCompanyId();
Long compareCompanyId = ctxCompanyId != null ? ctxCompanyId : currentCompanyId;
if (currentCompanyId != null && currentCompanyId > 0L
&& compareCompanyId != null && !currentCompanyId.equals(compareCompanyId)) {
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptContextHolder company mismatch: currentCompanyId={}, ctxCompanyId={}, ctxDeptId={}, source=DeptContextHolder]",
JsonUtils.toJsonString(loginUser), tableName, tableAlias == null ? null : tableAlias.getName(),
currentCompanyId, compareCompanyId, ctxDeptId);
}
}
// 计算有效的部门与自查标记:当存在上下文部门且未被忽略时,强制仅使用该部门,以避免默认全量或空权限分支
Set<Long> effectiveDeptIds = deptDataPermission.getDeptIds();
Boolean effectiveSelf = deptDataPermission.getSelf();
if (!DeptContextHolder.shouldIgnore() && ctxDeptId != null && ctxDeptId > 0L) {
effectiveDeptIds = CollUtil.newHashSet(ctxDeptId);
}
// 情况一:仅当不存在上下文部门时,且 ALL 可查看全部,才无需拼接条件;若存在上下文部门则仍需基于该部门过滤
if (ctxDeptId == null && deptDataPermission.getAll()) {
return null; return null;
} }
// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 // 情况二:仅在有效部门集合为空且不可查看自己时,才认为无权限;若上下文提供部门,则跳过该兜底
if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) if (CollUtil.isEmpty(effectiveDeptIds)
&& Boolean.FALSE.equals(deptDataPermission.getSelf())) { && Boolean.FALSE.equals(effectiveSelf)) {
return new EqualsTo(null, null); // WHERE null = null可以保证返回的数据为空 return new EqualsTo(null, null); // WHERE null = null可以保证返回的数据为空
} }
// 情况三,拼接 Dept 和 Company User 的条件,最后组合 // 情况三,拼接 Dept 和 Company User 的条件,最后组合
Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds);
// Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); // Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId()); Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId());
if (deptExpression == null && userExpression == null) { if (deptExpression == null && userExpression == null) {
// TODO ZT获得不到条件的时候暂时不抛出异常而是不返回数据 // TODO ZT获得不到条件的时候暂时不抛出异常而是不返回数据
log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",

View File

@@ -7,10 +7,13 @@ import com.zt.plat.framework.common.enums.UserTypeEnum;
import com.zt.plat.framework.common.util.collection.SetUtils; import com.zt.plat.framework.common.util.collection.SetUtils;
import com.zt.plat.framework.security.core.LoginUser; import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest;
import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
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.InjectMocks; import org.mockito.InjectMocks;
@@ -27,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same; import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
@@ -48,7 +52,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
// 清空 rule // 清空 rule
rule.getTableNames().clear(); rule.getTableNames().clear();
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear(); ((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear();
((Map<String, String>) ReflectUtil.getFieldValue(rule, "deptColumns")).clear(); ((Map<String, String>) ReflectUtil.getFieldValue(rule, "userColumns")).clear();
}
@AfterEach
void tearDown() {
DeptContextHolder.clear();
CompanyContextHolder.clear();
} }
@Test // 无 LoginUser @Test // 无 LoginUser
@@ -236,4 +246,151 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
} }
} }
@Test // 忽略部门数据权限,直接放行
void testGetExpression_ignoreDeptContext() {
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class)) {
String tableName = "t_order";
Alias alias = new Alias("o");
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(true);
Expression expression = rule.getExpression(tableName, alias);
assertNull(expression);
verifyNoInteractions(permissionApi);
}
}
@Test // 上下文部门存在且公司一致时,表达式按上下文 deptId 生效,但不修改原数据权限集合
void testGetExpression_deptContextOverride_companyMatch() {
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
String tableName = "t_user";
Alias tableAlias = new Alias("u");
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setDeptIds(CollUtil.newLinkedHashSet(10L, 20L))
.setCompanyId(1L);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(99L);
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
rule.addDeptColumn(tableName, "dept_id");
Expression expression = rule.getExpression(tableName, tableAlias);
assertEquals("u.dept_id IN (99)", expression.toString());
// 原始权限对象不被修改,只是临时使用上下文 deptId 计算
assertEquals(CollUtil.newLinkedHashSet(10L, 20L), deptDataPermission.getDeptIds());
assertEquals(1L, deptDataPermission.getCompanyId());
}
}
@Test // 上下文部门存在但公司不一致时,仍按上下文 deptId 过滤,原数据权限保持不变
void testGetExpression_deptContextOverride_companyMismatch() {
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
String tableName = "t_user";
Alias tableAlias = new Alias("u");
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setDeptIds(CollUtil.newLinkedHashSet(10L))
.setCompanyId(1L);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(99L);
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(2L);
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
rule.addDeptColumn(tableName, "dept_id");
Expression expression = rule.getExpression(tableName, tableAlias);
assertEquals("u.dept_id IN (99)", expression.toString());
// 原始权限对象不被修改
assertEquals(CollUtil.newLinkedHashSet(10L), deptDataPermission.getDeptIds());
assertEquals(1L, deptDataPermission.getCompanyId());
}
}
@Test // ALL 权限但存在上下文部门时,仍按上下文部门过滤
void testGetExpression_allPermission_withCtxDept() {
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
String tableName = "t_user";
Alias tableAlias = new Alias("u");
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setAll(true)
.setDeptIds(CollUtil.newLinkedHashSet(10L));
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(99L);
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
rule.addDeptColumn(tableName, "dept_id");
Expression expression = rule.getExpression(tableName, tableAlias);
assertEquals("u.dept_id IN (99)", expression.toString());
}
}
@Test // 无部门且不可查看自己,但上下文提供部门时,应使用上下文部门而非判定无权限
void testGetExpression_noDeptNoSelf_withCtxDept() {
try (MockedStatic<SecurityFrameworkUtils> secMock = mockStatic(SecurityFrameworkUtils.class);
MockedStatic<DeptContextHolder> deptCtxMock = mockStatic(DeptContextHolder.class);
MockedStatic<CompanyContextHolder> companyCtxMock = mockStatic(CompanyContextHolder.class)) {
String tableName = "t_user";
Alias tableAlias = new Alias("u");
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
secMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()
.setSelf(false);
when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(success(deptDataPermission));
deptCtxMock.when(DeptContextHolder::shouldIgnore).thenReturn(false);
deptCtxMock.when(DeptContextHolder::getDeptId).thenReturn(88L);
deptCtxMock.when(DeptContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::getCompanyId).thenReturn(1L);
companyCtxMock.when(CompanyContextHolder::isIgnore).thenReturn(false);
rule.addDeptColumn(tableName, "dept_id");
Expression expression = rule.getExpression(tableName, tableAlias);
assertEquals("u.dept_id IN (88)", expression.toString());
}
}
} }

View File

@@ -0,0 +1,61 @@
package com.zt.plat.framework.tenant.core.context;
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* 部门上下文 Holder使用 {@link TransmittableThreadLocal} 支持在线程池/异步场景下的上下文传递。
*
* 包含当前部门编号、所属公司编号以及是否忽略部门数据权限的标识。
*/
public class DeptContextHolder {
/** 当前部门编号 */
private static final ThreadLocal<Long> DEPT_ID = new TransmittableThreadLocal<>();
/** 当前部门所属公司编号(用于一致性校验) */
private static final ThreadLocal<Long> COMPANY_ID = new TransmittableThreadLocal<>();
/** 是否忽略部门数据权限 */
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
public static Long getDeptId() {
return DEPT_ID.get();
}
public static Long getCompanyId() {
return COMPANY_ID.get();
}
/**
* 设置部门与所属公司编号。
*/
public static void setContext(Long deptId, Long companyId) {
DEPT_ID.set(deptId);
COMPANY_ID.set(companyId);
}
public static void setDeptId(Long deptId) {
DEPT_ID.set(deptId);
}
public static void setCompanyId(Long companyId) {
COMPANY_ID.set(companyId);
}
public static boolean hasDeptId() {
Long deptId = DEPT_ID.get();
return deptId != null && deptId > 0L;
}
public static void setIgnore(Boolean ignore) {
IGNORE.set(ignore);
}
public static boolean shouldIgnore() {
return Boolean.TRUE.equals(IGNORE.get());
}
public static void clear() {
DEPT_ID.remove();
COMPANY_ID.remove();
IGNORE.remove();
}
}

View File

@@ -1,7 +1,5 @@
package com.zt.plat.framework.tenant.core.context; package com.zt.plat.framework.tenant.core.context;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.enums.DocumentEnum;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
/** /**
@@ -38,8 +36,10 @@ public class TenantContextHolder {
public static Long getRequiredTenantId() { public static Long getRequiredTenantId() {
Long tenantId = getTenantId(); Long tenantId = getTenantId();
if (tenantId == null) { if (tenantId == null) {
throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:" // throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:"
+ DocumentEnum.TENANT.getUrl()); // + DocumentEnum.TENANT.getUrl());
// 暂定所有获取不到租户的操作,默认都使用主租户进行操作 1l
tenantId = 1L;
} }
return tenantId; return tenantId;
} }

View File

@@ -3,6 +3,7 @@ package com.zt.plat.framework.tenant.core.web;
import com.zt.plat.framework.security.core.LoginUser; import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder; import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import com.zt.plat.framework.web.core.util.WebFrameworkUtils; 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;
@@ -66,11 +67,19 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
if (companyId == null || companyId <= 0L) { if (companyId == null || companyId <= 0L) {
CompanyContextHolder.setIgnore(true); CompanyContextHolder.setIgnore(true);
DeptContextHolder.clear();
return true; return true;
} }
CompanyContextHolder.setIgnore(false); CompanyContextHolder.setIgnore(false);
CompanyContextHolder.setCompanyId(companyId); CompanyContextHolder.setCompanyId(companyId);
// 默认不忽略部门数据权限;如果有有效部门则写入上下文
DeptContextHolder.setIgnore(false);
if (deptId != null && deptId > 0L) {
DeptContextHolder.setContext(deptId, companyId);
} else {
DeptContextHolder.clear();
}
if (loginUser == null) { if (loginUser == null) {
return true; return true;
} }
@@ -91,7 +100,9 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor {
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
if (loginUser != null) { if (loginUser != null) {
loginUser.setVisitCompanyId(0L); loginUser.setVisitCompanyId(0L);
loginUser.setVisitDeptId(0L);
} }
DeptContextHolder.clear();
} }
private Long resolveLong(Object value) { private Long resolveLong(Object value) {

View File

@@ -0,0 +1,88 @@
package com.zt.plat.framework.tenant.core.web;
import com.zt.plat.framework.security.core.LoginUser;
import com.zt.plat.framework.tenant.core.context.CompanyContextHolder;
import com.zt.plat.framework.tenant.core.context.DeptContextHolder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.junit.jupiter.api.Assertions.*;
/**
* CompanyVisitContextInterceptor 单测,覆盖公司/部门上下文写入及清理。
*/
class CompanyVisitContextInterceptorTest {
private final HandlerInterceptor interceptor = new CompanyVisitContextInterceptor();
@AfterEach
void tearDown() {
CompanyContextHolder.clear();
DeptContextHolder.clear();
SecurityContextHolder.clearContext();
}
@Test // 无公司 id应 ignore公司/部门上下文清空
void testPreHandle_noCompanyId_ignore() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
boolean result = interceptor.preHandle(request, response, new Object());
assertTrue(result);
assertTrue(CompanyContextHolder.isIgnore());
assertNull(CompanyContextHolder.getCompanyId());
assertNull(DeptContextHolder.getDeptId());
}
@Test // 有公司无部门:写入公司,部门清空
void testPreHandle_companyOnly() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
LoginUser loginUser = new LoginUser();
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null));
request.addHeader("visit-company-id", "11");
boolean result = interceptor.preHandle(request, response, new Object());
assertTrue(result);
assertFalse(CompanyContextHolder.isIgnore());
assertEquals(11L, CompanyContextHolder.getCompanyId());
assertFalse(DeptContextHolder.shouldIgnore());
assertNull(DeptContextHolder.getDeptId());
assertEquals(11L, loginUser.getVisitCompanyId());
assertNull(loginUser.getVisitDeptId());
}
@Test // 有公司+部门写入公司、部门上下文afterCompletion 清理 visitDeptId & holder
void testPreHandle_withCompanyAndDept_andAfterCompletionClear() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
LoginUser loginUser = new LoginUser();
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null));
request.addHeader("visit-company-id", "22");
request.addHeader("visit-dept-id", "33");
boolean result = interceptor.preHandle(request, response, new Object());
assertTrue(result);
assertFalse(CompanyContextHolder.isIgnore());
assertEquals(22L, CompanyContextHolder.getCompanyId());
assertEquals(33L, DeptContextHolder.getDeptId());
assertEquals(22L, DeptContextHolder.getCompanyId());
assertEquals(22L, loginUser.getVisitCompanyId());
assertEquals(33L, loginUser.getVisitDeptId());
// afterCompletion: 清理 visitCompanyId/visitDeptId 与 holder
interceptor.afterCompletion(request, response, new Object(), null);
assertEquals(0L, loginUser.getVisitCompanyId());
assertEquals(0L, loginUser.getVisitDeptId());
assertNull(DeptContextHolder.getDeptId());
assertNull(DeptContextHolder.getCompanyId());
}
}

View File

@@ -46,14 +46,12 @@
<groupId>com.zt.plat</groupId> <groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-mq</artifactId> <artifactId>zt-spring-boot-starter-mq</artifactId>
</dependency> </dependency>
<!-- Redis 相关 (用于幂等) -->
<dependency> <dependency>
<groupId>com.zt.plat</groupId> <groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-redis</artifactId> <artifactId>zt-spring-boot-starter-biz-tenant</artifactId>
</dependency> </dependency>
<!-- Web 相关 (用于HTTP接收) --> <!-- Redis 相关 (用于幂等) --><!-- Web 相关 (用于HTTP接收) -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

View File

@@ -13,7 +13,7 @@ import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener; import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.zt.plat.framework.tenant.core.context.TenantContextHolder;
/** /**
* DataBus 客户端统一消费者 * DataBus 客户端统一消费者
* <p> * <p>
@@ -33,8 +33,8 @@ import org.springframework.stereotype.Component;
@Component @Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@RocketMQMessageListener( @RocketMQMessageListener(
topic = "${zt.databus.sync.client.mq.topic:databus-sync}-${zt.databus.sync.client.client-code}", topic = "${zt.databus.sync.client.mq.topic-base:databus-sync}-${zt.databus.sync.client.client-code}",
consumerGroup = "${zt.databus.sync.client.mq.consumer-group:databus-client-consumer}-${zt.databus.sync.client.client-code}" consumerGroup = "${zt.databus.sync.client.mq.consumer-group-prefix:databus-client-consumer}-${zt.databus.sync.client.client-code}"
) )
public class DatabusClientConsumer implements RocketMQListener<String> { public class DatabusClientConsumer implements RocketMQListener<String> {
@@ -46,6 +46,7 @@ public class DatabusClientConsumer implements RocketMQListener<String> {
log.debug("[DatabusClient] 收到消息, body={}", body); log.debug("[DatabusClient] 收到消息, body={}", body);
try { try {
TenantContextHolder.setTenantId(1L);
// 1. 解析消息获取 eventType // 1. 解析消息获取 eventType
DatabusEventType eventType = parseEventType(body); DatabusEventType eventType = parseEventType(body);
if (eventType == null) { if (eventType == null) {

View File

@@ -69,21 +69,15 @@ public class DeptSyncServiceImpl implements DeptSyncService {
return; return;
} }
DeptSaveReqDTO dto = buildDeptDTO(data); DeptSaveReqDTO dto = buildDeptDTO(data);
// 使用专用同步接口,跳过业务校验,直接 upsert
try { try {
// 尝试获取,存在则更新,不存在则创建 deptApi.syncDept(dto).checkError();
var existing = deptApi.getDept(dto.getId()); log.info("[DeptSync] 部门全量同步成功, deptId={}, deptName={}", dto.getId(), dto.getName());
if (existing.isSuccess() && existing.getData() != null) {
deptApi.updateDept(dto).checkError();
log.info("[DeptSync] 部门全量同步-更新成功, deptId={}", dto.getId());
} else {
deptApi.createDept(dto).checkError();
log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId());
}
} catch (Exception e) { } catch (Exception e) {
// 获取失败,尝试创建 log.error("[DeptSync] 部门全量同步失败, deptId={}, deptName={}, parentId={}, code={}, error={}",
log.warn("[DeptSync] 部门获取失败,尝试创建, deptId={}", dto.getId()); dto.getId(), dto.getName(), dto.getParentId(), dto.getCode(), e.getMessage());
deptApi.createDept(dto).checkError(); throw e;
log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId());
} }
} }
@@ -93,13 +87,18 @@ public class DeptSyncServiceImpl implements DeptSyncService {
private DeptSaveReqDTO buildDeptDTO(DatabusDeptData data) { private DeptSaveReqDTO buildDeptDTO(DatabusDeptData data) {
DeptSaveReqDTO dto = new DeptSaveReqDTO(); DeptSaveReqDTO dto = new DeptSaveReqDTO();
dto.setId(data.getId()); dto.setId(data.getId());
dto.setCode(data.getCode()); // ⚠️ 重要:传递编码,保持一致
dto.setName(data.getName()); dto.setName(data.getName());
dto.setShortName(data.getShortName());
dto.setParentId(data.getParentId()); dto.setParentId(data.getParentId());
dto.setSort(data.getSort()); dto.setSort(data.getSort());
dto.setLeaderUserId(data.getLeaderUserId()); dto.setLeaderUserId(data.getLeaderUserId());
dto.setPhone(data.getPhone()); dto.setPhone(data.getPhone());
dto.setEmail(data.getEmail()); dto.setEmail(data.getEmail());
dto.setStatus(data.getStatus()); dto.setStatus(data.getStatus());
dto.setIsGroup(data.getIsGroup());
dto.setIsCompany(data.getIsCompany());
dto.setDeptSource(data.getDeptSource());
return dto; return dto;
} }
} }

View File

@@ -68,25 +68,14 @@ public class PostSyncServiceImpl implements PostSyncService {
return; return;
} }
PostSaveReqDTO dto = buildPostDTO(data); PostSaveReqDTO dto = buildPostDTO(data);
try { try {
// 尝试获取,存在则更新,不存在则创建 postApi.syncPost(dto).checkError();
var existing = postApi.getPost(dto.getId()); log.info("[PostSync] 岗位全量同步成功, postId={}, postName={}", dto.getId(), dto.getName());
if (existing.isSuccess() && existing.getData() != null) {
postApi.updatePost(dto).checkError();
log.info("[PostSync] 岗位全量同步-更新成功, postId={}, postName={}", dto.getId(), dto.getName());
} else {
postApi.createPost(dto).checkError();
log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName());
}
} catch (Exception e) { } catch (Exception e) {
// 获取失败,尝试创建 log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}, code={}, error={}",
try { dto.getId(), dto.getName(), dto.getCode(), e.getMessage());
postApi.createPost(dto).checkError(); throw e;
log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName());
} catch (Exception createEx) {
log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}", dto.getId(), dto.getName(), createEx);
throw createEx;
}
} }
} }

View File

@@ -72,21 +72,14 @@ public class AdminUserSyncServiceImpl implements AdminUserSyncService {
return; return;
} }
AdminUserSaveReqDTO dto = buildUserDTO(data); AdminUserSaveReqDTO dto = buildUserDTO(data);
try { try {
// 尝试获取,存在则更新,不存在则创建 adminUserApi.syncUser(dto).checkError();
var existing = adminUserApi.getUser(dto.getId()); log.info("[UserSync] 用户全量同步成功, userId={}, username={}", dto.getId(), dto.getUsername());
if (existing.isSuccess() && existing.getData() != null) {
adminUserApi.updateUser(dto).checkError();
log.info("[UserSync] 用户全量同步-更新成功, userId={}", dto.getId());
} else {
adminUserApi.createUser(dto).checkError();
log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId());
}
} catch (Exception e) { } catch (Exception e) {
// 获取失败,尝试创建 log.error("[UserSync] 用户全量同步失败, userId={}, username={}, error={}",
log.warn("[UserSync] 用户获取失败,尝试创建, userId={}", dto.getId()); dto.getId(), dto.getUsername(), e.getMessage());
adminUserApi.createUser(dto).checkError(); throw e;
log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId());
} }
} }

View File

@@ -1,56 +1,94 @@
package com.zt.plat.framework.databus.client.handler.userdept; package com.zt.plat.framework.databus.client.handler.userdept;
import com.zt.plat.module.databus.api.data.DatabusUserDeptData; import com.zt.plat.module.databus.api.data.DatabusUserDeptData;
import com.zt.plat.module.system.api.userdept.UserDeptApi;
import com.zt.plat.module.system.api.userdept.dto.UserDeptSaveReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* 用户-部门关系同步服务实现 * 用户-部门关系同步服务实现(通过 Feign API 调用远程服务)
* <p> * <p>
* 使用条件: * 使用条件:
* 1. zt.databus.sync.client.enabled=true * 1. zt.databus.sync.client.enabled=true
* 2. 系统中存在 UserDeptApi 接口Feign 客户端)
* <p> * <p>
* 注意:由于用户-部门关系通常集成在用户管理中,此实现为占位符。 * 如果分公司需要自定义实现,可以创建自己的 UserDeptSyncService Bean
* 分公司可以根据实际情况: * 此默认实现会自动失效(@ConditionalOnMissingBean
* 1. 自定义实现此接口,直接操作本地数据库
* 2. 或者通过用户管理 API 间接处理关联关系
* *
* @author ZT * @author ZT
*/ */
@Slf4j @Slf4j
@Service @Service
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnClass(name = "com.zt.plat.module.system.api.userdept.UserDeptApi")
public class UserDeptSyncServiceImpl implements UserDeptSyncService { public class UserDeptSyncServiceImpl implements UserDeptSyncService {
@Autowired(required = false)
private UserDeptApi userDeptApi; // Feign 远程调用接口
@Override @Override
public void create(DatabusUserDeptData data) { public void create(DatabusUserDeptData data) {
log.info("[UserDeptSync] 收到创建用户-部门关系请求, userId={}, deptId={}", if (userDeptApi == null) {
data.getUserId(), data.getDeptId()); log.warn("[UserDeptSync] UserDeptApi未注入跳过创建用户-部门关系, userId={}", data.getUserId());
log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法,通过本地 API 或直接数据库操作完成同步 }
UserDeptSaveReqDTO dto = buildUserDeptDTO(data);
userDeptApi.createUserDept(dto).checkError();
log.info("[UserDeptSync] 用户-部门关系创建成功, userId={}, deptId={}", data.getUserId(), data.getDeptId());
} }
@Override @Override
public void update(DatabusUserDeptData data) { public void update(DatabusUserDeptData data) {
log.info("[UserDeptSync] 收到更新用户-部门关系请求, userId={}, deptId={}", if (userDeptApi == null) {
data.getUserId(), data.getDeptId()); log.warn("[UserDeptSync] UserDeptApi未注入跳过更新用户-部门关系, userId={}", data.getUserId());
log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法 }
UserDeptSaveReqDTO dto = buildUserDeptDTO(data);
userDeptApi.updateUserDept(dto).checkError();
log.info("[UserDeptSync] 用户-部门关系更新成功, userId={}, deptId={}", data.getUserId(), data.getDeptId());
} }
@Override @Override
public void delete(Long id) { public void delete(Long id) {
log.info("[UserDeptSync] 收到删除用户-部门关系请求, id={}", id); if (userDeptApi == null) {
log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); log.warn("[UserDeptSync] UserDeptApi未注入跳过删除用户-部门关系, id={}", id);
// TODO: 分公司需要实现此方法 return;
}
userDeptApi.deleteUserDept(id).checkError();
log.info("[UserDeptSync] 用户-部门关系删除成功, id={}", id);
} }
@Override @Override
public void fullSync(DatabusUserDeptData data) { public void fullSync(DatabusUserDeptData data) {
log.info("[UserDeptSync] 收到全量同步用户-部门关系请求, userId={}, deptId={}", if (userDeptApi == null) {
data.getUserId(), data.getDeptId()); log.warn("[UserDeptSync] UserDeptApi未注入跳过全量同步用户-部门关系, userId={}", data.getUserId());
log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法,逻辑:存在则更新,不存在则插入 }
UserDeptSaveReqDTO dto = buildUserDeptDTO(data);
try {
userDeptApi.syncUserDept(dto).checkError();
log.info("[UserDeptSync] 用户-部门关系全量同步成功, id={}, userId={}, deptId={}",
dto.getId(), dto.getUserId(), dto.getDeptId());
} catch (Exception e) {
log.error("[UserDeptSync] 用户-部门关系全量同步失败, id={}, userId={}, deptId={}, error={}",
dto.getId(), dto.getUserId(), dto.getDeptId(), e.getMessage());
throw e;
}
}
/**
* 构建用户部门关系 DTO用于 Feign 调用)
*/
private UserDeptSaveReqDTO buildUserDeptDTO(DatabusUserDeptData data) {
UserDeptSaveReqDTO dto = new UserDeptSaveReqDTO();
dto.setId(data.getId());
dto.setUserId(data.getUserId());
dto.setDeptId(data.getDeptId());
dto.setRemark(data.getRemark());
return dto;
} }
} }

View File

@@ -1,56 +1,93 @@
package com.zt.plat.framework.databus.client.handler.userpost; package com.zt.plat.framework.databus.client.handler.userpost;
import com.zt.plat.module.databus.api.data.DatabusUserPostData; import com.zt.plat.module.databus.api.data.DatabusUserPostData;
import com.zt.plat.module.system.api.userpost.UserPostApi;
import com.zt.plat.module.system.api.userpost.dto.UserPostSaveReqDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
* 用户-岗位关系同步服务实现 * 用户-岗位关系同步服务实现(通过 Feign API 调用远程服务)
* <p> * <p>
* 使用条件: * 使用条件:
* 1. zt.databus.sync.client.enabled=true * 1. zt.databus.sync.client.enabled=true
* 2. 系统中存在 UserPostApi 接口Feign 客户端)
* <p> * <p>
* 注意:由于用户-岗位关系通常集成在用户管理中,此实现为占位符。 * 如果分公司需要自定义实现,可以创建自己的 UserPostSyncService Bean
* 分公司可以根据实际情况: * 此默认实现会自动失效(@ConditionalOnMissingBean
* 1. 自定义实现此接口,直接操作本地数据库
* 2. 或者通过用户管理 API 间接处理关联关系
* *
* @author ZT * @author ZT
*/ */
@Slf4j @Slf4j
@Service @Service
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnClass(name = "com.zt.plat.module.system.api.userpost.UserPostApi")
public class UserPostSyncServiceImpl implements UserPostSyncService { public class UserPostSyncServiceImpl implements UserPostSyncService {
@Autowired(required = false)
private UserPostApi userPostApi; // Feign 远程调用接口
@Override @Override
public void create(DatabusUserPostData data) { public void create(DatabusUserPostData data) {
log.info("[UserPostSync] 收到创建用户-岗位关系请求, userId={}, postId={}", if (userPostApi == null) {
data.getUserId(), data.getPostId()); log.warn("[UserPostSync] UserPostApi未注入跳过创建用户-岗位关系, userId={}", data.getUserId());
log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法,通过本地 API 或直接数据库操作完成同步 }
UserPostSaveReqDTO dto = buildUserPostDTO(data);
userPostApi.createUserPost(dto).checkError();
log.info("[UserPostSync] 用户-岗位关系创建成功, userId={}, postId={}", data.getUserId(), data.getPostId());
} }
@Override @Override
public void update(DatabusUserPostData data) { public void update(DatabusUserPostData data) {
log.info("[UserPostSync] 收到更新用户-岗位关系请求, userId={}, postId={}", if (userPostApi == null) {
data.getUserId(), data.getPostId()); log.warn("[UserPostSync] UserPostApi未注入跳过更新用户-岗位关系, userId={}", data.getUserId());
log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法 }
UserPostSaveReqDTO dto = buildUserPostDTO(data);
userPostApi.updateUserPost(dto).checkError();
log.info("[UserPostSync] 用户-岗位关系更新成功, userId={}, postId={}", data.getUserId(), data.getPostId());
} }
@Override @Override
public void delete(Long id) { public void delete(Long id) {
log.info("[UserPostSync] 收到删除用户-岗位关系请求, id={}", id); if (userPostApi == null) {
log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); log.warn("[UserPostSync] UserPostApi未注入跳过删除用户-岗位关系, id={}", id);
// TODO: 分公司需要实现此方法 return;
}
userPostApi.deleteUserPost(id).checkError();
log.info("[UserPostSync] 用户-岗位关系删除成功, id={}", id);
} }
@Override @Override
public void fullSync(DatabusUserPostData data) { public void fullSync(DatabusUserPostData data) {
log.info("[UserPostSync] 收到全量同步用户-岗位关系请求, userId={}, postId={}", if (userPostApi == null) {
data.getUserId(), data.getPostId()); log.warn("[UserPostSync] UserPostApi未注入跳过全量同步用户-岗位关系, userId={}", data.getUserId());
log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); return;
// TODO: 分公司需要实现此方法,逻辑:存在则更新,不存在则插入 }
UserPostSaveReqDTO dto = buildUserPostDTO(data);
try {
userPostApi.syncUserPost(dto).checkError();
log.info("[UserPostSync] 用户-岗位关系全量同步成功, id={}, userId={}, postId={}",
dto.getId(), dto.getUserId(), dto.getPostId());
} catch (Exception e) {
log.error("[UserPostSync] 用户-岗位关系全量同步失败, id={}, userId={}, postId={}, error={}",
dto.getId(), dto.getUserId(), dto.getPostId(), e.getMessage());
throw e;
}
}
/**
* 构建用户岗位关系 DTO用于 Feign 调用)
*/
private UserPostSaveReqDTO buildUserPostDTO(DatabusUserPostData data) {
UserPostSaveReqDTO dto = new UserPostSaveReqDTO();
dto.setId(data.getId());
dto.setUserId(data.getUserId());
dto.setPostId(data.getPostId());
return dto;
} }
} }

View File

@@ -34,7 +34,15 @@ public class DatabusUserChangeConsumer implements RocketMQListener<DatabusUserCh
@Override @Override
public void onMessage(DatabusUserChangeMessage message) { public void onMessage(DatabusUserChangeMessage message) {
log.info("[Databus] 收到用户变更消息, action={}, userId={}", message.getAction(), message.getUserId()); log.info("[Databus] 收到用户变更消息, action={}, userId={}, userSource={}",
message.getAction(), message.getUserId(), message.getUserSource());
// ⚠️ 只处理 userSource = 2 的用户
if (message.getUserSource() == null || message.getUserSource() != 2) {
log.info("[Databus] 跳过非集团用户的变更消息, userId={}, userSource={}",
message.getUserId(), message.getUserSource());
return;
}
try { try {
Map<String, Object> dataMap = new HashMap<>(); Map<String, Object> dataMap = new HashMap<>();

View File

@@ -0,0 +1,81 @@
package com.zt.plat.framework.databus.server.provider;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.databus.server.core.provider.DataProvider;
import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.data.DatabusUserDeptData;
import com.zt.plat.module.databus.api.provider.DatabusUserDeptProviderApi;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 用户-部门关系数据提供者
*/
@Slf4j
@Component
public class UserDeptDataFeignProvider implements DataProvider<DatabusUserDeptData> {
public static final String PROVIDER_TYPE = "USER_DEPT";
@Resource
private DatabusUserDeptProviderApi userDeptProviderApi;
@Resource
private DataProviderRegistry dataProviderRegistry;
@PostConstruct
public void init() {
dataProviderRegistry.register(this);
}
@Override
public String getProviderType() {
return PROVIDER_TYPE;
}
@Override
public CursorPageData<DatabusUserDeptData> getPageByCursor(LocalDateTime cursorTime, Long cursorId,
int batchSize, Long tenantId) {
CursorPageReqDTO reqDTO = CursorPageReqDTO.builder()
.cursorTime(cursorTime)
.cursorId(cursorId)
.batchSize(batchSize)
.tenantId(tenantId)
.build();
CommonResult<CursorPageResult<DatabusUserDeptData>> result = userDeptProviderApi.getPageByCursor(reqDTO);
if (!result.isSuccess()) {
throw new RuntimeException("获取用户-部门关系数据失败: " + result.getMsg());
}
CursorPageResult<DatabusUserDeptData> pageResult = result.getData();
return CursorPageData.of(
pageResult.getList(),
pageResult.getNextCursorTime(),
pageResult.getNextCursorId(),
pageResult.getCount(),
Boolean.TRUE.equals(pageResult.getHasMore()),
(pageResult.getTotal() != null ? pageResult.getTotal() : 0L)
);
}
@Override
public long count(Long tenantId) {
CommonResult<Long> result = userDeptProviderApi.count(tenantId);
if (!result.isSuccess()) {
throw new RuntimeException("获取用户-部门关系总数失败: " + result.getMsg());
}
return result.getData();
}
@Override
public Long extractUid(DatabusUserDeptData data) {
return data.getId();
}
}

View File

@@ -0,0 +1,81 @@
package com.zt.plat.framework.databus.server.provider;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.databus.server.core.provider.DataProvider;
import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.data.DatabusUserPostData;
import com.zt.plat.module.databus.api.provider.DatabusUserPostProviderApi;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 用户-岗位关系数据提供者
*/
@Slf4j
@Component
public class UserPostDataFeignProvider implements DataProvider<DatabusUserPostData> {
public static final String PROVIDER_TYPE = "USER_POST";
@Resource
private DatabusUserPostProviderApi userPostProviderApi;
@Resource
private DataProviderRegistry dataProviderRegistry;
@PostConstruct
public void init() {
dataProviderRegistry.register(this);
}
@Override
public String getProviderType() {
return PROVIDER_TYPE;
}
@Override
public CursorPageData<DatabusUserPostData> getPageByCursor(LocalDateTime cursorTime, Long cursorId,
int batchSize, Long tenantId) {
CursorPageReqDTO reqDTO = CursorPageReqDTO.builder()
.cursorTime(cursorTime)
.cursorId(cursorId)
.batchSize(batchSize)
.tenantId(tenantId)
.build();
CommonResult<CursorPageResult<DatabusUserPostData>> result = userPostProviderApi.getPageByCursor(reqDTO);
if (!result.isSuccess()) {
throw new RuntimeException("获取用户-岗位关系数据失败: " + result.getMsg());
}
CursorPageResult<DatabusUserPostData> pageResult = result.getData();
return CursorPageData.of(
pageResult.getList(),
pageResult.getNextCursorTime(),
pageResult.getNextCursorId(),
pageResult.getCount(),
Boolean.TRUE.equals(pageResult.getHasMore()),
(pageResult.getTotal() != null ? pageResult.getTotal() : 0L)
);
}
@Override
public long count(Long tenantId) {
CommonResult<Long> result = userPostProviderApi.count(tenantId);
if (!result.isSuccess()) {
throw new RuntimeException("获取用户-岗位关系总数失败: " + result.getMsg());
}
return result.getData();
}
@Override
public Long extractUid(DatabusUserPostData data) {
return data.getId();
}
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zt-framework</artifactId>
<groupId>com.zt.plat</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>zt-spring-boot-starter-seata-dm</artifactId>
<name>${project.artifactId}</name>
<description>
Seata 达梦数据库补丁模块
解决 DmdbTimestamp 时区格式不一致导致的 dirty undo log 回滚失败问题
补丁来源: https://github.com/apache/incubator-seata/pull/7538
Seata 2.6.0 发布后可移除此模块
</description>
<dependencies>
<!-- 仅编译时需要,运行时由 seata-spring-boot-starter 提供 -->
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,326 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.seata.rm.datasource;
import org.apache.seata.common.util.CollectionUtils;
import org.apache.seata.common.util.StringUtils;
import org.apache.seata.core.model.Result;
import org.apache.seata.rm.datasource.sql.struct.Field;
import org.apache.seata.rm.datasource.sql.struct.Row;
import org.apache.seata.rm.datasource.sql.struct.TableRecords;
import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager;
import org.apache.seata.rm.datasource.undo.parser.FastjsonUndoLogParser;
import org.apache.seata.rm.datasource.undo.parser.JacksonUndoLogParser;
import org.apache.seata.sqlparser.struct.TableMeta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* DataCompareUtils - 包含达梦数据库 DmdbTimestamp 时区问题的补丁
* <p>
* 此类覆盖 Seata 原有的 DataCompareUtils添加了对达梦数据库 DmdbTimestamp 类型的特殊处理。
* 通过将 DmdbTimestamp 转换为 UTC Instant 进行比较,解决时区格式不一致导致的 dirty undo log 问题。
* <p>
* 问题背景:
* - 达梦数据库的 DmdbTimestamp 类型在序列化/反序列化后时区格式不一致
* - 例如beforeImage 为 "2025-12-25 09:38:54.077811 +08:00"
* afterImage 为 "2025-12-25 09:38:54.077811"
* - 导致 Seata AT 模式回滚时 dirty undo log 检查失败
* <p>
* 解决方案:
* - 当检测到 DmdbTimestamp 类型时,将两个值都转换为 UTC Instant 进行比较
* - 这样可以忽略时区格式差异,只比较实际的时间点
* <p>
* 补丁来源: https://github.com/apache/incubator-seata/pull/7538
* 相关 Issue: https://github.com/apache/incubator-seata/issues/7453
* 该修复已合并到 Seata 2.x 分支,将在 Seata 2.6.0 正式发布,届时可删除此模块。
*
* @author Seata Community (PR #7538)
*/
public class DataCompareUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DataCompareUtils.class);
/**
* 标识补丁类是否已加载
*/
private static final boolean PATCHED;
static {
PATCHED = true;
LOGGER.info("[zt-spring-boot-starter-seata-dm] DataCompareUtils 补丁类已加载,用于解决达梦数据库 DmdbTimestamp 时区问题");
}
private DataCompareUtils() {}
/**
* Is field equals result.
*
* @param f0 the f 0
* @param f1 the f 1
* @return the result
*/
public static Result<Boolean> isFieldEquals(Field f0, Field f1) {
if (f0 == null) {
return Result.build(f1 == null);
} else {
if (f1 == null) {
return Result.build(false);
} else {
if (StringUtils.equalsIgnoreCase(f0.getName(), f1.getName()) && f0.getType() == f1.getType()) {
if (f0.getValue() == null) {
return Result.build(f1.getValue() == null);
} else {
if (f1.getValue() == null) {
return Result.buildWithParams(
false, "Field not equals, name {}, new value is null", f0.getName());
} else {
String currentSerializer = AbstractUndoLogManager.getCurrentSerializer();
if (StringUtils.equals(currentSerializer, FastjsonUndoLogParser.NAME)) {
convertType(f0, f1);
}
// 达梦数据库 DmdbTimestamp 时区补丁 (PR #7538)
if (StringUtils.equals(currentSerializer, JacksonUndoLogParser.NAME)) {
Object v0 = f0.getValue();
Object v1 = f1.getValue();
if (isDmdbTimestamp(v0) && isDmdbTimestamp(v1)) {
Instant i0 = toInstant(v0);
Instant i1 = toInstant(v1);
boolean equals = Objects.equals(i0, i1);
LOGGER.info("[zt-seata-dm-patch] DmdbTimestamp 字段比较: field={}, equals={}", f0.getName(), equals);
return equals
? Result.ok()
: Result.buildWithParams(
false,
"Field not equals (DmdbTimestamp), name {}, old value {}, new value {}",
f0.getName(),
v0,
v1);
}
}
boolean result = Objects.deepEquals(f0.getValue(), f1.getValue());
if (result) {
return Result.ok();
} else {
return Result.buildWithParams(
false,
"Field not equals, name {}, old value {}, new value {}",
f0.getName(),
f0.getValue(),
f1.getValue());
}
}
}
} else {
return Result.buildWithParams(
false,
"Field not equals, old name {} type {}, new name {} type {}",
f0.getName(),
f0.getType(),
f1.getName(),
f1.getType());
}
}
}
}
private static void convertType(Field f0, Field f1) {
int f0Type = f0.getType();
int f1Type = f1.getType();
if (f0Type == Types.DATE && f0.getValue().getClass().equals(String.class)) {
String[] strings = f0.getValue().toString().split(" ");
f0.setValue(Date.valueOf(strings[0]));
}
if (f1Type == Types.DATE && f1.getValue().getClass().equals(String.class)) {
String[] strings = f1.getValue().toString().split(" ");
f1.setValue(Date.valueOf(strings[0]));
}
if (f0Type == Types.TIME && f0.getValue().getClass().equals(String.class)) {
f0.setValue(Time.valueOf(f0.getValue().toString()));
}
if (f1Type == Types.TIME && f1.getValue().getClass().equals(String.class)) {
f1.setValue(Time.valueOf(f1.getValue().toString()));
}
if (f0Type == Types.TIMESTAMP && f0.getValue().getClass().equals(String.class)) {
if (f1.getValue().getClass().equals(LocalDateTime.class)) {
f0.setValue(LocalDateTime.parse(f0.getValue().toString()));
} else {
f0.setValue(Timestamp.valueOf(f0.getValue().toString()));
}
}
if (f1Type == Types.TIMESTAMP && f1.getValue().getClass().equals(String.class)) {
f1.setValue(Timestamp.valueOf(f1.getValue().toString()));
}
if (f0Type == Types.DECIMAL && f0.getValue().getClass().equals(Integer.class)) {
f0.setValue(new BigDecimal(f0.getValue().toString()));
}
if (f1Type == Types.DECIMAL && f1.getValue().getClass().equals(Integer.class)) {
f1.setValue(new BigDecimal(f1.getValue().toString()));
}
if (f0Type == Types.BIGINT && f0.getValue().getClass().equals(Integer.class)) {
f0.setValue(Long.parseLong(f0.getValue().toString()));
}
if (f1Type == Types.BIGINT && f1.getValue().getClass().equals(Integer.class)) {
f1.setValue(Long.parseLong(f1.getValue().toString()));
}
}
/**
* Is records equals result.
*
* @param beforeImage the before image
* @param afterImage the after image
* @return the result
*/
public static Result<Boolean> isRecordsEquals(TableRecords beforeImage, TableRecords afterImage) {
if (beforeImage == null) {
return Result.build(afterImage == null, null);
} else {
if (afterImage == null) {
return Result.build(false, null);
}
if (beforeImage.getTableName().equalsIgnoreCase(afterImage.getTableName())
&& CollectionUtils.isSizeEquals(beforeImage.getRows(), afterImage.getRows())) {
// when image is EmptyTableRecords, getTableMeta will throw an exception
if (CollectionUtils.isEmpty(beforeImage.getRows())) {
return Result.ok();
}
return compareRows(beforeImage.getTableMeta(), beforeImage.getRows(), afterImage.getRows());
} else {
return Result.build(false, null);
}
}
}
/**
* Is rows equals result.
*
* @param tableMetaData the table meta data
* @param oldRows the old rows
* @param newRows the new rows
* @return the result
*/
public static Result<Boolean> isRowsEquals(TableMeta tableMetaData, List<Row> oldRows, List<Row> newRows) {
if (!CollectionUtils.isSizeEquals(oldRows, newRows)) {
return Result.build(false, null);
}
return compareRows(tableMetaData, oldRows, newRows);
}
private static Result<Boolean> compareRows(TableMeta tableMetaData, List<Row> oldRows, List<Row> newRows) {
// old row to map
Map<String, Map<String, Field>> oldRowsMap = rowListToMap(oldRows, tableMetaData.getPrimaryKeyOnlyName());
// new row to map
Map<String, Map<String, Field>> newRowsMap = rowListToMap(newRows, tableMetaData.getPrimaryKeyOnlyName());
// compare data
for (Map.Entry<String, Map<String, Field>> oldEntry : oldRowsMap.entrySet()) {
String key = oldEntry.getKey();
Map<String, Field> oldRow = oldEntry.getValue();
Map<String, Field> newRow = newRowsMap.get(key);
if (newRow == null) {
return Result.buildWithParams(false, "compare row failed, rowKey {}, reason [newRow is null]", key);
}
for (Map.Entry<String, Field> oldRowEntry : oldRow.entrySet()) {
String fieldName = oldRowEntry.getKey();
Field oldField = oldRowEntry.getValue();
Field newField = newRow.get(fieldName);
if (newField == null) {
return Result.buildWithParams(
false,
"compare row failed, rowKey {}, fieldName {}, reason [newField is null]",
key,
fieldName);
}
Result<Boolean> oldEqualsNewFieldResult = isFieldEquals(oldField, newField);
if (!oldEqualsNewFieldResult.getResult()) {
return oldEqualsNewFieldResult;
}
}
}
return Result.ok();
}
/**
* Row list to map map.
*
* @param rowList the row list
* @param primaryKeyList the primary key list
* @return the map
*/
public static Map<String, Map<String, Field>> rowListToMap(List<Row> rowList, List<String> primaryKeyList) {
// {value of primaryKey, value of all columns}
Map<String, Map<String, Field>> rowMap = new HashMap<>();
for (Row row : rowList) {
// ensure the order of column
List<Field> rowFieldList = row.getFields().stream()
.sorted(Comparator.comparing(Field::getName))
.collect(Collectors.toList());
// {uppercase fieldName : field}
Map<String, Field> colsMap = new HashMap<>();
StringBuilder rowKey = new StringBuilder();
boolean firstUnderline = false;
for (int j = 0; j < rowFieldList.size(); j++) {
Field field = rowFieldList.get(j);
if (primaryKeyList.stream().anyMatch(e -> field.getName().equals(e))) {
if (firstUnderline && j > 0) {
rowKey.append("_");
}
rowKey.append(String.valueOf(field.getValue()));
firstUnderline = true;
}
colsMap.put(field.getName().trim().toUpperCase(), field);
}
rowMap.put(rowKey.toString(), colsMap);
}
return rowMap;
}
/**
* 判断是否为达梦数据库的 DmdbTimestamp 类型
*/
private static boolean isDmdbTimestamp(Object obj) {
return obj != null
&& "dm.jdbc.driver.DmdbTimestamp".equals(obj.getClass().getName());
}
/**
* 将 DmdbTimestamp 转换为 Instant
*/
private static Instant toInstant(Object dmdbTimestamp) {
try {
Method toInstantMethod = dmdbTimestamp.getClass().getMethod("toInstant");
return (Instant) toInstantMethod.invoke(dmdbTimestamp);
} catch (Exception e) {
throw new RuntimeException("Failed to convert DmdbTimestamp to Instant", e);
}
}
}

View File

@@ -30,6 +30,8 @@ spring:
username: ${config.username} # Nacos 账号 username: ${config.username} # Nacos 账号
password: ${config.password} # Nacos 密码 password: ${config.password} # Nacos 密码
discovery: # 【配置中心】配置项 discovery: # 【配置中心】配置项
ip: 172.16.46.62
port: 30092
namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换
group: ${config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: ${config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata: metadata:

View File

@@ -56,6 +56,10 @@ public class BpmModelMetaInfoVO {
@NotNull(message = "是否可见不能为空") @NotNull(message = "是否可见不能为空")
private Boolean visible; private Boolean visible;
@Schema(description = "是否允许重新发起", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否允许重新发起不能为空")
private Boolean restart;
@Schema(description = "可发起用户编号数组", example = "[1,2,3]") @Schema(description = "可发起用户编号数组", example = "[1,2,3]")
private List<Long> startUserIds; private List<Long> startUserIds;

View File

@@ -5,6 +5,7 @@ import com.zt.plat.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import com.zt.plat.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;

View File

@@ -4,6 +4,7 @@ import com.zt.plat.module.bpm.controller.admin.definition.vo.model.simple.BpmSim
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@Schema(description = "管理后台 - 流程模型的保存 Request VO") @Schema(description = "管理后台 - 流程模型的保存 Request VO")

View File

@@ -1,5 +1,6 @@
package com.zt.plat.module.bpm.controller.admin.task; package com.zt.plat.module.bpm.controller.admin.task;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.business.core.util.DeptUtil; import com.zt.plat.framework.business.core.util.DeptUtil;
@@ -11,8 +12,10 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.*;
import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert; import com.zt.plat.module.bpm.convert.task.BpmProcessInstanceConvert;
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.zt.plat.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.zt.plat.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import com.zt.plat.module.bpm.service.definition.BpmCategoryService; import com.zt.plat.module.bpm.service.definition.BpmCategoryService;
import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService; import com.zt.plat.module.bpm.service.definition.BpmProcessDefinitionService;
import com.zt.plat.module.bpm.service.task.BpmProcessInstanceCopyService;
import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService;
import com.zt.plat.module.bpm.service.task.BpmTaskService; import com.zt.plat.module.bpm.service.task.BpmTaskService;
import com.zt.plat.module.system.api.dept.DeptApi; import com.zt.plat.module.system.api.dept.DeptApi;
@@ -24,6 +27,8 @@ import io.swagger.v3.oas.annotations.Parameter;
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.validation.Valid; import jakarta.validation.Valid;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.list.SetUniqueList;
import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
@@ -31,6 +36,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.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -53,6 +60,8 @@ public class BpmProcessInstanceController {
private BpmProcessDefinitionService processDefinitionService; private BpmProcessDefinitionService processDefinitionService;
@Resource @Resource
private BpmCategoryService categoryService; private BpmCategoryService categoryService;
@Resource
private BpmProcessInstanceCopyService processInstanceCopyService;
@Resource @Resource
private AdminUserApi adminUserApi; private AdminUserApi adminUserApi;
@@ -181,6 +190,32 @@ public class BpmProcessInstanceController {
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
} }
@GetMapping("/copy-list-by-process-instance-id")
@Operation(summary = "根据流程实例编号获取抄送列表")
@Parameter(name = "id", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<List<BpmProcessInstanceCopyVO>> getCopyListByProcessInstanceId(@RequestParam("processInstanceId") String processInstanceId) {
List<BpmProcessInstanceCopyDO> copyDOList = processInstanceCopyService.getByProcessInstanceId(processInstanceId);
if (CollectionUtils.isEmpty(copyDOList)) {
return success(new ArrayList<>(0));
}
List<BpmProcessInstanceCopyVO> copyVOList = new ArrayList<>(copyDOList.size());
SetUniqueList<Long> userIdList = SetUniqueList.setUniqueList(new ArrayList<>());
for (BpmProcessInstanceCopyDO copyDO : copyDOList) {
BpmProcessInstanceCopyVO copyVO = new BpmProcessInstanceCopyVO();
BeanUtil.copyProperties(copyDO, copyVO);
copyVOList.add(copyVO);
userIdList.add(copyDO.getStartUserId());
userIdList.add(copyDO.getUserId());
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIdList);
for (BpmProcessInstanceCopyVO copyVO : copyVOList) {
copyVO.setStartUserName(userMap.get(copyVO.getStartUserId()).getNickname());
copyVO.setUserName(userMap.get(copyVO.getUserId()).getNickname());
}
return success(copyVOList);
}
@GetMapping("/get-next-approval-nodes") @GetMapping("/get-next-approval-nodes")
@Operation(summary = "获取下一个执行的流程节点") @Operation(summary = "获取下一个执行的流程节点")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")

View File

@@ -0,0 +1,88 @@
package com.zt.plat.module.bpm.controller.admin.task.vo.instance;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 流程抄送 VO
*
* @author kr
* @since 2025-12-31
*/
@Data
@NoArgsConstructor
public class BpmProcessInstanceCopyVO extends BaseDO {
/**
* 编号
*/
private Long id;
/**
* 发起人 Id
*/
@Schema(description ="发起人 Id")
private Long startUserId;
/**
* 发起人 姓名
*/
@Schema(description ="发起人 姓名")
private String startUserName;
/**
* 流程名
*/
@Schema(description ="流程名")
private String processInstanceName;
/**
* 流程实例的编号
*/
@Schema(description ="流程实例的编号")
private String processInstanceId;
/**
* 流程实例的流程定义编号
*/
@Schema(description ="流程实例的流程定义编号")
private String processDefinitionId;
/**
* 流程分类
*/
@Schema(description ="流程分类")
private String category;
/**
* 流程活动的编号
*/
@Schema(description ="流程活动的编号")
private String activityId;
/**
* 流程活动的名字
*/
@Schema(description ="流程活动的名字")
private String activityName;
/**
* 流程活动的编号
*/
@Schema(description ="流程活动的编号")
private String taskId;
/**
* 用户编号(被抄送的用户编号)
*/
@Schema(description ="用户编号(被抄送的用户编号)")
private Long userId;
/**
* 用户姓名(被抄送的用户姓名)
*/
@Schema(description ="用户姓名(被抄送的用户姓名)")
private String userName;
/**
* 抄送意见
*/
@Schema(description ="抄送意见")
private String reason;
}

View File

@@ -87,9 +87,16 @@ public interface BpmProcessInstanceConvert {
}); });
} }
} }
// 摘要 BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfoMap.get(respVO.getProcessDefinitionId());
respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()), if (processDefinitionInfo != null) {
pageResult.getList().get(i).getProcessVariables())); // 摘要
respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfo,
pageResult.getList().get(i).getProcessVariables()));
// 是否可见
respVO.getProcessDefinition().setVisible(processDefinitionInfo.getVisible());
// 是否可以重新发起流程
respVO.getProcessDefinition().setRestart(processDefinitionInfo.getRestart());
}
// 表单 // 表单
respVO.setFormVariables(pageResult.getList().get(i).getProcessVariables()); respVO.setFormVariables(pageResult.getList().get(i).getProcessVariables());
} }

View File

@@ -129,6 +129,13 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
* 目的:如果 false 不可见,则不展示在“发起流程”的列表里 * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
*/ */
private Boolean visible; private Boolean visible;
/**
* 是否允许重新发起
*
* 目的:如果 false 则不可以重新发起流程
*/
private Boolean restart;
/** /**
* 排序值 * 排序值
*/ */

View File

@@ -7,6 +7,8 @@ import com.zt.plat.module.bpm.controller.admin.task.vo.instance.BpmProcessInstan
import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import com.zt.plat.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper @Mapper
public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInstanceCopyDO> { public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInstanceCopyDO> {
@@ -22,4 +24,8 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInst
delete(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId); delete(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
} }
default List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId) {
return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
}
} }

View File

@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotEmpty;
import org.flowable.bpmn.model.FlowNode; import org.flowable.bpmn.model.FlowNode;
import java.util.Collection; import java.util.Collection;
import java.util.List;
/** /**
* 流程抄送 Service 接口 * 流程抄送 Service 接口
@@ -57,4 +58,12 @@ public interface BpmProcessInstanceCopyService {
*/ */
void deleteProcessInstanceCopy(String processInstanceId); void deleteProcessInstanceCopy(String processInstanceId);
/**
* 获得流程的抄送列表
*
* @param processInstanceId 流程实例 ID
* @return 抄送流程列表
*/
List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId);
} }

View File

@@ -93,4 +93,9 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId); processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId);
} }
@Override
public List<BpmProcessInstanceCopyDO> getByProcessInstanceId(String processInstanceId) {
return processInstanceCopyMapper.getByProcessInstanceId(processInstanceId);
}
} }

View File

@@ -73,4 +73,8 @@
</root> </root>
</springProfile> </springProfile>
<logger name="com.zt.plat.module.bpm.dal" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</configuration> </configuration>

View File

@@ -0,0 +1,71 @@
package com.zt.plat.module.databus.api.provider;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusUserDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Databus 用户-部门关系数据提供者 API
* <p>
* 供 Databus 调用,获取用户-部门关联数据用于全量/增量同步
*
* @author ZT
*/
@FeignClient(name = "${databus.provider.user-dept.service:system-server}")
@Tag(name = "RPC 服务 - Databus 用户-部门关系数据提供者")
public interface DatabusUserDeptProviderApi {
String PREFIX = "/rpc/databus/user-dept";
/**
* 游标分页查询用户-部门关系数据(用于全量同步)
*
* @param reqDTO 游标分页请求
* @return 用户-部门关系数据分页结果
*/
@PostMapping(PREFIX + "/page-by-cursor")
@Operation(summary = "游标分页查询用户-部门关系数据")
CommonResult<CursorPageResult<DatabusUserDeptData>> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO);
/**
* 根据ID查询用户-部门关系详情(用于增量同步)
*
* @param id 关系ID
* @return 用户-部门关系数据
*/
@GetMapping(PREFIX + "/get")
@Operation(summary = "查询用户-部门关系详情")
@Parameter(name = "id", description = "关系ID", required = true, example = "1001")
CommonResult<DatabusUserDeptData> getById(@RequestParam("id") Long id);
/**
* 批量查询用户-部门关系详情(用于增量同步批量获取)
*
* @param ids 关系ID列表
* @return 用户-部门关系数据列表
*/
@GetMapping(PREFIX + "/list")
@Operation(summary = "批量查询用户-部门关系详情")
@Parameter(name = "ids", description = "关系ID列表", required = true, example = "1001,1002,1003")
CommonResult<List<DatabusUserDeptData>> getListByIds(@RequestParam("ids") List<Long> ids);
/**
* 统计用户-部门关系总数(用于全量同步进度计算)
*
* @param tenantId 租户ID可选
* @return 用户-部门关系总数
*/
@GetMapping(PREFIX + "/count")
@Operation(summary = "统计用户-部门关系总数")
@Parameter(name = "tenantId", description = "租户ID", example = "1")
CommonResult<Long> count(@RequestParam(value = "tenantId", required = false) Long tenantId);
}

View File

@@ -0,0 +1,71 @@
package com.zt.plat.module.databus.api.provider;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusUserPostData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Databus 用户-岗位关系数据提供者 API
* <p>
* 供 Databus 调用,获取用户-岗位关联数据用于全量/增量同步
*
* @author ZT
*/
@FeignClient(name = "${databus.provider.user-post.service:system-server}")
@Tag(name = "RPC 服务 - Databus 用户-岗位关系数据提供者")
public interface DatabusUserPostProviderApi {
String PREFIX = "/rpc/databus/user-post";
/**
* 游标分页查询用户-岗位关系数据(用于全量同步)
*
* @param reqDTO 游标分页请求
* @return 用户-岗位关系数据分页结果
*/
@PostMapping(PREFIX + "/page-by-cursor")
@Operation(summary = "游标分页查询用户-岗位关系数据")
CommonResult<CursorPageResult<DatabusUserPostData>> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO);
/**
* 根据ID查询用户-岗位关系详情(用于增量同步)
*
* @param id 关系ID
* @return 用户-岗位关系数据
*/
@GetMapping(PREFIX + "/get")
@Operation(summary = "查询用户-岗位关系详情")
@Parameter(name = "id", description = "关系ID", required = true, example = "1001")
CommonResult<DatabusUserPostData> getById(@RequestParam("id") Long id);
/**
* 批量查询用户-岗位关系详情(用于增量同步批量获取)
*
* @param ids 关系ID列表
* @return 用户-岗位关系数据列表
*/
@GetMapping(PREFIX + "/list")
@Operation(summary = "批量查询用户-岗位关系详情")
@Parameter(name = "ids", description = "关系ID列表", required = true, example = "1001,1002,1003")
CommonResult<List<DatabusUserPostData>> getListByIds(@RequestParam("ids") List<Long> ids);
/**
* 统计用户-岗位关系总数(用于全量同步进度计算)
*
* @param tenantId 租户ID可选
* @return 用户-岗位关系总数
*/
@GetMapping(PREFIX + "/count")
@Operation(summary = "统计用户-岗位关系总数")
@Parameter(name = "tenantId", description = "租户ID", example = "1")
CommonResult<Long> count(@RequestParam(value = "tenantId", required = false) Long tenantId);
}

View File

@@ -185,6 +185,11 @@
<version>4.12.0</version> <version>4.12.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-databus-client</artifactId>
<version>${revision}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -33,6 +33,7 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -304,15 +305,28 @@ public class GatewaySecurityFilter extends OncePerRequestFilter {
.build() .build()
.getQueryParams(); .getQueryParams();
params.forEach((key, values) -> { params.forEach((key, values) -> {
if (!StringUtils.hasText(key) || "signature".equalsIgnoreCase(key)) { String decodedKey = URLDecoder.decode(key, StandardCharsets.UTF_8);
if (!StringUtils.hasText(decodedKey) || "signature".equalsIgnoreCase(decodedKey)) {
return; return;
} }
if (CollectionUtils.isEmpty(values)) { if (CollectionUtils.isEmpty(values)) {
target.put(key, ""); target.put(decodedKey, "");
} else if (values.size() == 1) { return;
target.put(key, values.get(0)); }
// 对每一个 value 做 URL 解码,确保与客户端原文签名一致
List<String> decodedValues = values.stream()
.map(val -> URLDecoder.decode(val, StandardCharsets.UTF_8))
.toList();
boolean allNullLiteral = decodedValues.stream()
.allMatch(v -> "null".equals(v));
if (allNullLiteral) {
// 过滤掉仅包含字符串 "null" 的参数
return;
}
if (decodedValues.size() == 1) {
target.put(decodedKey, decodedValues.get(0));
} else { } else {
target.put(key, String.join(",", values)); target.put(decodedKey, String.join(",", decodedValues));
} }
}); });
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {

View File

@@ -4,9 +4,13 @@ import com.zt.plat.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi; import com.zt.plat.module.databus.api.provider.DatabusDeptProviderApi;
import com.zt.plat.module.databus.api.provider.DatabusPostProviderApi; import com.zt.plat.module.databus.api.provider.DatabusPostProviderApi;
import com.zt.plat.module.databus.api.provider.DatabusUserProviderApi; import com.zt.plat.module.databus.api.provider.DatabusUserProviderApi;
import com.zt.plat.module.databus.api.provider.DatabusUserDeptProviderApi;
import com.zt.plat.module.databus.api.provider.DatabusUserPostProviderApi;
import com.zt.plat.module.system.api.dept.DeptApi; import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.PostApi; import com.zt.plat.module.system.api.dept.PostApi;
import com.zt.plat.module.system.api.user.AdminUserApi; import com.zt.plat.module.system.api.user.AdminUserApi;
import com.zt.plat.module.system.api.userdept.UserDeptApi;
import com.zt.plat.module.system.api.userpost.UserPostApi;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -21,9 +25,12 @@ import org.springframework.context.annotation.Configuration;
DatabusDeptProviderApi.class, DatabusDeptProviderApi.class,
DatabusUserProviderApi.class, DatabusUserProviderApi.class,
DatabusPostProviderApi.class, DatabusPostProviderApi.class,
PostApi.class, DatabusUserDeptProviderApi.class,
DeptApi.class, DatabusUserPostProviderApi.class,
AdminUserApi.class, PostApi.class,
DeptApi.class,
UserDeptApi.class,
UserPostApi.class,
}) })
public class RpcConfiguration { public class RpcConfiguration {
} }

View File

@@ -5,6 +5,10 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; import com.zt.plat.framework.common.util.security.CryptoSignatureUtils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.net.URI; import java.net.URI;
@@ -23,10 +27,6 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/** /**
* 可直接运行的示例,演示如何使用 appId=test 与对应密钥调用本地 Databus API。 * 可直接运行的示例,演示如何使用 appId=test 与对应密钥调用本地 Databus API。
@@ -37,14 +37,14 @@ public final class DatabusApiInvocationExample {
// private static final String APP_ID = "iwork"; // private static final String APP_ID = "iwork";
// private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk="; // private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk=";
private static final String APP_ID = "ztmy"; private static final String APP_ID = "jwyw";
private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ="; private static final String APP_SECRET = "MhfCcqB59rDTnB5yGOVXWtp/5a0JXir7pSjPl5cVMJ8=";
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/callback/v1"; // private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/callback/v1";
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1"; // private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
// private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1"; // private static final String TARGET_API = "http://172.16.46.195:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
private static final String TARGET_API = "https://jygk.chncopper.com:30078/admin-api/databus/api/portal/lgstOpenApi/v1"; // private static final String TARGET_API = "https://jygk.chncopper.com:30078/admin-api/databus/api/portal/lgstOpenApi/v1";
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/callback/v1"; private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/lgstOpenApi/v1"; // private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/lgstOpenApi/v1";
// private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456"; // private static final String TARGET_API = "http://localhost:48080/admin-api/databus/api/portal/testcbw/456";
// ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。 // ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。
@@ -102,10 +102,16 @@ public final class DatabusApiInvocationExample {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
OUT.println("=== GET 请求示例 ==="); OUT.println("=== GET 请求示例 ===");
// executeGetExample(); executeGetExample();
// OUT.println(); // OUT.println();
// OUT.println("=== POST 请求示例 ==="); OUT.println("=== POST 请求示例 ===");
executePostExample(); executePostExample("""
{"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"}
""");
executePostExample("""
{"msgCode":"YWJYGK0003","data":"{\\"memberId\\":65352,\\"routes\\":[{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"440000-440300\\",\\"endAddressDetail\\":\\"深圳港\\",\\"endAddressDetailDesc\\":\\"广东省深圳市盐田区深盐路\\",\\"endAddressLatitude\\":22.567426,\\"endAddressLongitude\\":114.283271,\\"endAddressName\\":\\"广东省-深圳市\\",\\"endAddressType\\":\\"port\\",\\"startAddressCode\\":\\"520000-0\\",\\"startAddressDetail\\":\\"安龙\\",\\"startAddressDetailDesc\\":\\"贵州省安龙县德卧镇坡告村\\",\\"startAddressLatitude\\":25.066532,\\"startAddressLongitude\\":105.244186,\\"startAddressName\\":\\"贵州省-null\\",\\"startAddressType\\":\\"railway-station\\",\\"taskEndTime\\":1766592000000,\\"taskStartTime\\":1766332800000,\\"transType\\":\\"10\\"},{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"230000-230600\\",\\"endAddressDetail\\":\\"大庆东\\",\\"endAddressDetailDesc\\":\\"黑龙江省大庆市龙凤区凤一路28号\\",\\"endAddressLatitude\\":46.544097,\\"endAddressLongitude\\":125.118902,\\"endAddressName\\":\\"黑龙江省-大庆市\\",\\"endAddressType\\":\\"railway-station\\",\\"startAddressCode\\":\\"440000-440300\\",\\"startAddressDetail\\":\\"深圳港\\",\\"startAddressDetailDesc\\":\\"广东省深圳市盐田区深盐路\\",\\"startAddressLatitude\\":22.567426,\\"startAddressLongitude\\":114.283271,\\"startAddressName\\":\\"广东省-深圳市\\",\\"startAddressType\\":\\"port\\",\\"taskEndTime\\":1767110400000,\\"taskStartTime\\":1766592000000,\\"transType\\":\\"30\\"},{\\"carrierCorpCode\\":\\"10193776\\",\\"carrierCorpName\\":\\"成都达海金属加工配送有限公司\\",\\"endAddressCode\\":\\"520000-0\\",\\"endAddressDetail\\":\\"郑屯\\",\\"endAddressDetailDesc\\":\\"贵州省郑屯镇\\",\\"endAddressName\\":\\"贵州省-null\\",\\"endAddressType\\":\\"railway-station\\",\\"startAddressCode\\":\\"230000-230600\\",\\"startAddressDetail\\":\\"大庆东\\",\\"startAddressDetailDesc\\":\\"黑龙江省大庆市龙凤区凤一路28号\\",\\"startAddressLatitude\\":46.544097,\\"startAddressLongitude\\":125.118902,\\"startAddressName\\":\\"黑龙江省-大庆市\\",\\"startAddressType\\":\\"railway-station\\",\\"taskEndTime\\":1768320000000,\\"taskStartTime\\":1767110400000,\\"transType\\":\\"20\\"}],\\"taskLineNumber\\":\\"CT202512230001_001\\",\\"taskNumber\\":\\"CT202512230001\\"}"}
""");
} }
private static void executeGetExample() throws Exception { private static void executeGetExample() throws Exception {
@@ -113,9 +119,11 @@ public final class DatabusApiInvocationExample {
queryParams.put("businessCode", "11"); queryParams.put("businessCode", "11");
queryParams.put("fileId", "11"); queryParams.put("fileId", "11");
queryParams.put("null", null); queryParams.put("null", null);
queryParams.put("empty", "");
queryParams.put("taskTimeEnd", "2025-12-28 23:00:00");
String signature = generateSignature(queryParams, Map.of()); String signature = generateSignature(queryParams, Map.of());
URI requestUri = buildUri(TARGET_API, queryParams); URI requestUri = buildUri(TARGET_API, queryParams);
String nonce = "171615676c7d4d96b9f55f3d90ad27e0"; String nonce = randomNonce();
HttpRequest request = HttpRequest.newBuilder(requestUri) HttpRequest request = HttpRequest.newBuilder(requestUri)
.timeout(Duration.ofSeconds(10)) .timeout(Duration.ofSeconds(10))
@@ -131,16 +139,14 @@ public final class DatabusApiInvocationExample {
printResponse(response); printResponse(response);
} }
private static void executePostExample() throws Exception { private static void executePostExample(String json) throws Exception {
Map<String, Object> queryParams = new LinkedHashMap<>(); Map<String, Object> queryParams = new LinkedHashMap<>();
long extraTimestamp = 1761556157185L; long extraTimestamp = 1761556157185L;
// String bodyJson = String.format(""" // String bodyJson = String.json("""
// {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"} // {"operateFlag":"I","__interfaceType__":"R_MY_JY_03","data":{"endAddressName":"1","customerCompanyName":"中铜国贸","endAddressDetail":"测试地址","remark":" ","custSuppType":"1","shipperCompanyName":"中铜国贸","consigneeCorpCode":" ","consignerContactPhone":" 11","importFlag":"10","businessSupplierCode":" ","entrustMainCode":"WT3162251027027","endAddressCode":" ","specifyCarrierCorpCode":"10086689","materDetail":[{"detailStatus":"10","batchNo":"ZLTD2510ZTGM0017001","measureCodeMdm":"CU032110001","packType":" ","quantityPlanDetail":1,"deliveryOrderNo":"ZLTD2510ZTGM0017001","measureCode":"CU032110001","goodsSpecification":" ","measureUnitCode":"PAC","entrustDetailCode":"WT3162251027027001","brand":" ","soNumber":"68ecf0055502d565d22b378a"}],"operateFlag":1,"custSuppName":"上海锦生金属有限公司","startAddressCode":" ","planStartTime":1761556166000,"customerCompanyCode":0,"importMethod":"EXW","startAddressType":"10","shipperCompanyCode":"3162","deliverCondition":"20","businessSupplierName":" ","startAddressDetail":" 111","transType":"30","endAddressType":"20","planEndTime":1761556166000,"specifyCarrierCorpName":null,"custSuppFlag":"0101","businessType":"20","consigneeCorpName":" ","custSuppCode":"10086689","startAddressName":" 111","consignerContactName":" 11"},"datetime":"20251027170929","busiBillCode":"WT3162251027027","system":"BRMS","__requestId__":"f918841c-14fb-49eb-9640-c5d1b3d46bd1"}
// """, extraTimestamp); // """, extraTimestamp);
String bodyJson = String.format(""" String bodyJson = String.format(json, extraTimestamp);
{}
""", extraTimestamp);
Map<String, Object> bodyParams = parseBodyJson(bodyJson); Map<String, Object> bodyParams = parseBodyJson(bodyJson);
String signature = generateSignature(queryParams, bodyParams); String signature = generateSignature(queryParams, bodyParams);

View File

@@ -2,6 +2,8 @@ package com.zt.plat.module.infra.service.file;
import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.json.JsonUtils; import com.zt.plat.framework.common.util.json.JsonUtils;
import com.zt.plat.framework.common.util.validation.ValidationUtils; import com.zt.plat.framework.common.util.validation.ValidationUtils;
@@ -14,8 +16,6 @@ import com.zt.plat.module.infra.framework.file.core.client.FileClient;
import com.zt.plat.module.infra.framework.file.core.client.FileClientConfig; import com.zt.plat.module.infra.framework.file.core.client.FileClientConfig;
import com.zt.plat.module.infra.framework.file.core.client.FileClientFactory; import com.zt.plat.module.infra.framework.file.core.client.FileClientFactory;
import com.zt.plat.module.infra.framework.file.core.enums.FileStorageEnum; import com.zt.plat.module.infra.framework.file.core.enums.FileStorageEnum;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import lombok.Getter; import lombok.Getter;
@@ -172,7 +172,7 @@ public class FileConfigServiceImpl implements FileConfigService {
// 校验存在 // 校验存在
validateFileConfigExists(id); validateFileConfigExists(id);
// 上传文件 // 上传文件
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg"); return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
} }

View File

@@ -32,8 +32,8 @@ spring:
servlet: servlet:
# 文件上传相关配置项 # 文件上传相关配置项
multipart: multipart:
max-file-size: 100MB # 单个文件大小 max-file-size: 128MB # 单个文件大小
max-request-size: 120MB # 设置总上传的文件大小 max-request-size: 512MB # 设置总上传的文件大小
# Jackson 配置项 # Jackson 配置项
jackson: jackson:

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -41,7 +41,7 @@ public class FtpFileClientTest {
client.init(); client.init();
// 上传文件 // 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg"; String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String fullPath = client.upload(content, path, "image/jpeg"); String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath); System.out.println("访问地址:" + fullPath);
if (false) { if (false) {

View File

@@ -20,7 +20,7 @@ public class LocalFileClientTest {
client.init(); client.init();
// 上传文件 // 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg"; String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String fullPath = client.upload(content, path, "image/jpeg"); String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath); System.out.println("访问地址:" + fullPath);
client.delete(path); client.delete(path);

View File

@@ -101,7 +101,7 @@ public class S3FileClientTest {
client.init(); client.init();
// 上传文件 // 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg"; String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String fullPath = client.upload(content, path, "image/jpeg"); String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath); System.out.println("访问地址:" + fullPath);
// 读取文件 // 读取文件

View File

@@ -34,7 +34,7 @@ public class SftpFileClientTest {
client.init(); client.init();
// 上传文件 // 上传文件
String path = IdUtil.fastSimpleUUID() + ".jpg"; String path = IdUtil.fastSimpleUUID() + ".jpg";
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String fullPath = client.upload(content, path, "image/jpeg"); String fullPath = client.upload(content, path, "image/jpeg");
System.out.println("访问地址:" + fullPath); System.out.println("访问地址:" + fullPath);
if (false) { if (false) {

View File

@@ -88,7 +88,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
@Test @Test
public void testCreateFile_success_01() throws Exception { public void testCreateFile_success_01() throws Exception {
// 准备参数 // 准备参数
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String name = "单测文件名"; String name = "单测文件名";
String directory = randomString(); String directory = randomString();
String type = "image/jpeg"; String type = "image/jpeg";
@@ -122,7 +122,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
@Test @Test
public void testCreateFile_success_02() throws Exception { public void testCreateFile_success_02() throws Exception {
// 准备参数 // 准备参数
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
// mock Master 文件客户端 // mock Master 文件客户端
String type = "image/jpeg"; String type = "image/jpeg";
FileClient client = mock(FileClient.class); FileClient client = mock(FileClient.class);
@@ -318,7 +318,7 @@ public class FileServiceImplTest extends BaseDbUnitTest {
@Test @Test
public void testCreateFile_withSameHash() throws Exception { public void testCreateFile_withSameHash() throws Exception {
// 准备参数 // 准备参数
byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); byte[] content = ResourceUtil.readBytes("file/bg1.png");
String name = "单测文件名"; String name = "单测文件名";
String directory = randomString(); String directory = randomString();
String type = "image/jpeg"; String type = "image/jpeg";

View File

@@ -86,4 +86,10 @@ public interface DeptApi {
@Parameter(name = "userId", description = "用户编号", example = "1", required = true) @Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId); CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步部门")
CommonResult<Boolean> syncDept(@RequestBody DeptSaveReqDTO syncReqDTO);
} }

View File

@@ -64,4 +64,10 @@ public interface PostApi {
return CollectionUtils.convertMap(list, PostRespDTO::getId); return CollectionUtils.convertMap(list, PostRespDTO::getId);
} }
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步岗位")
CommonResult<Boolean> syncPost(@RequestBody PostSaveReqDTO syncReqDTO);
} }

View File

@@ -15,9 +15,15 @@ public class DeptSaveReqDTO {
@Schema(description = "部门编号", example = "1024") @Schema(description = "部门编号", example = "1024")
private Long id; private Long id;
@Schema(description = "部门编码", example = "ZT001")
private String code;
@Schema(description = "部门名称", example = "ZT") @Schema(description = "部门名称", example = "ZT")
private String name; private String name;
@Schema(description = "部门简称", example = "技术")
private String shortName;
@Schema(description = "父部门 ID", example = "1024") @Schema(description = "父部门 ID", example = "1024")
private Long parentId; private Long parentId;
@@ -36,6 +42,15 @@ public class DeptSaveReqDTO {
@Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", example = "0") @Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", example = "0")
private Integer status; private Integer status;
@Schema(description = "是否集团", example = "false")
private Boolean isGroup;
@Schema(description = "是否公司", example = "false")
private Boolean isCompany;
@Schema(description = "部门来源类型", example = "1")
private Integer deptSource;
@Schema(description = "外部系统标识,用于建立编码映射", example = "ERP") @Schema(description = "外部系统标识,用于建立编码映射", example = "ERP")
private String externalSystemCode; private String externalSystemCode;

View File

@@ -0,0 +1,94 @@
package com.zt.plat.module.system.api.esp;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.module.system.api.dept.dto.*;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 部门")
public interface EspApi {
String PREFIX = ApiConstants.PREFIX + "/dept";
// === 以下为补全的接口方法 ===
@PostMapping(PREFIX + "/create")
@Operation(summary = "新增部门")
CommonResult<Long> createDept(@RequestBody DeptSaveReqDTO createReqVO);
@PutMapping(PREFIX + "/update")
@Operation(summary = "修改部门")
CommonResult<Boolean> updateDept(@RequestBody DeptSaveReqDTO updateReqVO);
@DeleteMapping(PREFIX + "/delete")
@Operation(summary = "删除部门")
CommonResult<Boolean> deleteDept(@RequestParam("id") Long id);
@PostMapping(PREFIX + "/list-all")
@Operation(summary = "获得部门列表")
CommonResult<List<DeptDetailRespDTO>> getDeptList(@RequestBody DeptListReqDTO reqVO);
@GetMapping(PREFIX + "/simple-list")
@Operation(summary = "获得部门精简信息列表")
CommonResult<List<DeptSimpleRespDTO>> getSimpleDeptList();
@GetMapping(PREFIX + "/simple-company-list")
@Operation(summary = "获得公司精简信息列表")
CommonResult<List<DeptSimpleRespDTO>> getSimpleCompanyList();
@GetMapping(PREFIX + "/all-company-list")
@Operation(summary = "获得所有公司精简信息列表")
CommonResult<List<DeptSimpleRespDTO>> getAllCompanyList();
@GetMapping(PREFIX + "/get")
@Operation(summary = "获得部门信息")
@Parameter(name = "id", description = "部门编号", example = "1024", required = true)
CommonResult<DeptRespDTO> getDept(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/list")
@Operation(summary = "获得部门信息数组")
@Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true)
CommonResult<List<DeptRespDTO>> getDeptList(@RequestParam("ids") Collection<Long> ids);
@GetMapping(PREFIX + "/valid")
@Operation(summary = "校验部门是否合法")
@Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true)
CommonResult<Boolean> validateDeptList(@RequestParam("ids") Collection<Long> ids);
/**
* 获得指定编号的部门 Map
*
* @param ids 部门编号数组
* @return 部门 Map
*/
default Map<Long, DeptRespDTO> getDeptMap(Collection<Long> ids) {
List<DeptRespDTO> list = getDeptList(ids).getCheckedData();
return CollectionUtils.convertMap(list, DeptRespDTO::getId);
}
@GetMapping(PREFIX + "/list-child")
@Operation(summary = "获得指定部门的所有子部门")
@Parameter(name = "id", description = "部门编号", example = "1024", required = true)
CommonResult<List<DeptRespDTO>> getChildDeptList(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/company-dept-info")
@Operation(summary = "获得指定用户的公司部门信息")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Set<CompanyDeptInfoRespDTO>> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步部门")
CommonResult<Boolean> syncDept(@RequestBody DeptSaveReqDTO syncReqDTO);
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.module.system.api.esp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "RPC 服务 - 推送外部系统配置信息 Response DTO")
@Data
public class EspDto {
@Schema(description = "部门名称,模糊匹配", example = "ZT")
private String name;
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status;
@Schema(description = "是否公司", example = "false")
private Boolean isCompany;
@Schema(description = "是否集团", example = "false")
private Boolean isGroup;
@Schema(description = "部门编号集合,支持多部门查询", example = "[\"1001\", \"1002\"]")
private List<String> ids;
}

View File

@@ -4,6 +4,7 @@ import com.zt.plat.framework.common.biz.system.permission.PermissionCommonApi;
import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.api.permission.dto.*; import com.zt.plat.module.system.api.permission.dto.*;
import com.zt.plat.module.system.enums.ApiConstants; import com.zt.plat.module.system.enums.ApiConstants;
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -50,4 +51,9 @@ public interface PermissionApi extends PermissionCommonApi {
@Parameter(name = "userId", description = "用户编号", example = "1", required = true) @Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Set<Long>> getUserRoleIdListByUserId(@RequestParam("userId") Long userId); CommonResult<Set<Long>> getUserRoleIdListByUserId(@RequestParam("userId") Long userId);
@GetMapping(PREFIX + "/user-data-permission-level")
@Operation(summary = "获得用户的数据权限级别")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<DataScopeEnum> getUserDataPermissionLevel(@RequestParam("userId") Long userId);
} }

View File

@@ -104,6 +104,12 @@ public interface AdminUserApi extends AutoTransable<AdminUserRespDTO> {
@Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true) @Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true)
CommonResult<Boolean> validateUserList(@RequestParam("ids") Collection<Long> ids); CommonResult<Boolean> validateUserList(@RequestParam("ids") Collection<Long> ids);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步用户")
CommonResult<Boolean> syncUser(@RequestBody AdminUserSaveReqDTO syncReqDTO);
@Override @Override
@FeignIgnore @FeignIgnore
default List<AdminUserRespDTO> selectByIds(List<?> ids) { default List<AdminUserRespDTO> selectByIds(List<?> ids) {

View File

@@ -50,4 +50,10 @@ public class AdminUserSaveReqDTO {
@Schema(description = "密码", example = "123456") @Schema(description = "密码", example = "123456")
private String password; private String password;
@Schema(description = "工号", example = "A00123")
private String workcode;
@Schema(description = "用户来源类型", example = "1")
private Integer userSource;
} }

View File

@@ -0,0 +1,70 @@
package com.zt.plat.module.system.api.userdept;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.api.userdept.dto.UserDeptRespDTO;
import com.zt.plat.module.system.api.userdept.dto.UserDeptSaveReqDTO;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
/**
* 用户-部门关系 Feign API
*
* @author ZT
*/
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 用户部门关系")
public interface UserDeptApi {
String PREFIX = ApiConstants.PREFIX + "/user-dept";
@PostMapping(PREFIX + "/create")
@Operation(summary = "新增用户部门关系")
CommonResult<Long> createUserDept(@RequestBody UserDeptSaveReqDTO reqVO);
@PutMapping(PREFIX + "/update")
@Operation(summary = "修改用户部门关系")
CommonResult<Boolean> updateUserDept(@RequestBody UserDeptSaveReqDTO reqVO);
@DeleteMapping(PREFIX + "/delete")
@Operation(summary = "删除用户部门关系")
@Parameter(name = "id", description = "关系编号", example = "1", required = true)
CommonResult<Boolean> deleteUserDept(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/get")
@Operation(summary = "通过ID查询用户部门关系")
@Parameter(name = "id", description = "关系编号", example = "1", required = true)
CommonResult<UserDeptRespDTO> getUserDept(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/list-by-user-id")
@Operation(summary = "通过用户ID查询用户部门关系列表")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<List<UserDeptRespDTO>> getUserDeptListByUserId(@RequestParam("userId") Long userId);
@GetMapping(PREFIX + "/list-by-dept-id")
@Operation(summary = "通过部门ID查询用户部门关系列表")
@Parameter(name = "deptId", description = "部门编号", example = "1", required = true)
CommonResult<List<UserDeptRespDTO>> getUserDeptListByDeptId(@RequestParam("deptId") Long deptId);
@DeleteMapping(PREFIX + "/delete-by-user-id")
@Operation(summary = "通过用户ID删除用户部门关系")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Boolean> deleteUserDeptByUserId(@RequestParam("userId") Long userId);
@DeleteMapping(PREFIX + "/delete-by-dept-id")
@Operation(summary = "通过部门ID删除用户部门关系")
@Parameter(name = "deptId", description = "部门编号", example = "1", required = true)
CommonResult<Boolean> deleteUserDeptByDeptId(@RequestParam("deptId") Long deptId);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步用户部门关系")
CommonResult<Boolean> syncUserDept(@RequestBody UserDeptSaveReqDTO syncReqDTO);
}

View File

@@ -0,0 +1,31 @@
package com.zt.plat.module.system.api.userdept.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户部门关系 Response DTO
*
* @author ZT
*/
@Schema(description = "RPC 服务 - 用户部门关系 Response DTO")
@Data
public class UserDeptRespDTO {
@Schema(description = "关系编号", example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "部门编号", example = "100")
private Long deptId;
@Schema(description = "备注", example = "主部门")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.module.system.api.userdept.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户部门关系创建/修改 Request DTO
*
* @author ZT
*/
@Schema(description = "RPC 服务 - 用户部门关系创建/修改 Request DTO")
@Data
public class UserDeptSaveReqDTO {
@Schema(description = "关系编号", example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1", required = true)
private Long userId;
@Schema(description = "部门编号", example = "100", required = true)
private Long deptId;
@Schema(description = "备注", example = "主部门")
private String remark;
}

View File

@@ -0,0 +1,70 @@
package com.zt.plat.module.system.api.userpost;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.system.api.userpost.dto.UserPostRespDTO;
import com.zt.plat.module.system.api.userpost.dto.UserPostSaveReqDTO;
import com.zt.plat.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
/**
* 用户-岗位关系 Feign API
*
* @author ZT
*/
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 用户岗位关系")
public interface UserPostApi {
String PREFIX = ApiConstants.PREFIX + "/user-post";
@PostMapping(PREFIX + "/create")
@Operation(summary = "新增用户岗位关系")
CommonResult<Long> createUserPost(@RequestBody UserPostSaveReqDTO reqVO);
@PutMapping(PREFIX + "/update")
@Operation(summary = "修改用户岗位关系")
CommonResult<Boolean> updateUserPost(@RequestBody UserPostSaveReqDTO reqVO);
@DeleteMapping(PREFIX + "/delete")
@Operation(summary = "删除用户岗位关系")
@Parameter(name = "id", description = "关系编号", example = "1", required = true)
CommonResult<Boolean> deleteUserPost(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/get")
@Operation(summary = "通过ID查询用户岗位关系")
@Parameter(name = "id", description = "关系编号", example = "1", required = true)
CommonResult<UserPostRespDTO> getUserPost(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/list-by-user-id")
@Operation(summary = "通过用户ID查询用户岗位关系列表")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<List<UserPostRespDTO>> getUserPostListByUserId(@RequestParam("userId") Long userId);
@GetMapping(PREFIX + "/list-by-post-id")
@Operation(summary = "通过岗位ID查询用户岗位关系列表")
@Parameter(name = "postId", description = "岗位编号", example = "1", required = true)
CommonResult<List<UserPostRespDTO>> getUserPostListByPostId(@RequestParam("postId") Long postId);
@DeleteMapping(PREFIX + "/delete-by-user-id")
@Operation(summary = "通过用户ID删除用户岗位关系")
@Parameter(name = "userId", description = "用户编号", example = "1", required = true)
CommonResult<Boolean> deleteUserPostByUserId(@RequestParam("userId") Long userId);
@DeleteMapping(PREFIX + "/delete-by-post-id")
@Operation(summary = "通过岗位ID删除用户岗位关系")
@Parameter(name = "postId", description = "岗位编号", example = "1", required = true)
CommonResult<Boolean> deleteUserPostByPostId(@RequestParam("postId") Long postId);
// ========== 数据同步专用接口 ==========
@PostMapping(PREFIX + "/sync")
@Operation(summary = "同步用户岗位关系")
CommonResult<Boolean> syncUserPost(@RequestBody UserPostSaveReqDTO syncReqDTO);
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.api.userpost.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户岗位关系 Response DTO
*
* @author ZT
*/
@Schema(description = "RPC 服务 - 用户岗位关系 Response DTO")
@Data
public class UserPostRespDTO {
@Schema(description = "关系编号", example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "岗位编号", example = "100")
private Long postId;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,23 @@
package com.zt.plat.module.system.api.userpost.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户岗位关系创建/修改 Request DTO
*
* @author ZT
*/
@Schema(description = "RPC 服务 - 用户岗位关系创建/修改 Request DTO")
@Data
public class UserPostSaveReqDTO {
@Schema(description = "关系编号", example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1", required = true)
private Long userId;
@Schema(description = "岗位编号", example = "100", required = true)
private Long postId;
}

View File

@@ -127,8 +127,8 @@ public interface ErrorCodeConstants {
ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期"); ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用"); ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量"); ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量:{}次");
ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁,请于{}分钟后再试");
// ========== 租户信息 1-002-015-000 ========== // ========== 租户信息 1-002-015-000 ==========
ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在"); ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在");

View File

@@ -1,10 +1,12 @@
package com.zt.plat.module.system.enums.permission; package com.zt.plat.module.system.enums.permission;
import com.fasterxml.jackson.annotation.JsonValue;
import com.zt.plat.framework.common.core.ArrayValuable; import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects;
/** /**
* 数据范围枚举类 * 数据范围枚举类
@@ -33,6 +35,26 @@ public enum DataScopeEnum implements ArrayValuable<Integer> {
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new); public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new);
/**
* Jackson 序列化时输出整数 code兼容旧客户端
*/
@JsonValue
public Integer getScope() {
return scope;
}
public static DataScopeEnum findByScope(Integer scope) {
if (scope == null) {
return null;
}
for (DataScopeEnum value : values()) {
if (Objects.equals(value.scope, scope)) {
return value;
}
}
return null;
}
@Override @Override
public Integer[] array() { public Integer[] array() {
return ARRAYS; return ARRAYS;

View File

@@ -137,7 +137,12 @@
<groupId>com.zt.plat</groupId> <groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-monitor</artifactId> <artifactId>zt-spring-boot-starter-monitor</artifactId>
</dependency> </dependency>
<!-- Seata 达梦数据库补丁,必须最先引入以覆盖 seata-all 中的类 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-seata-dm</artifactId>
<version>${revision}</version>
</dependency>
<!-- 分布式事务 --> <!-- 分布式事务 -->
<dependency> <dependency>
<groupId>org.apache.seata</groupId> <groupId>org.apache.seata</groupId>
@@ -190,6 +195,10 @@
<groupId>com.zt.plat</groupId> <groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-mq</artifactId> <artifactId>zt-spring-boot-starter-mq</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,127 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusUserDeptData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusUserDeptProviderApi;
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 用户-部门关系数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusUserDeptProviderApiImpl implements DatabusUserDeptProviderApi {
@Resource
private UserDeptMapper userDeptMapper;
@Override
public CommonResult<CursorPageResult<DatabusUserDeptData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
// 查询用户部门关系
List<UserDeptDO> list = userDeptMapper.selectPageByCursorWithUserSource(
reqDTO.isFirstPage() ? null : reqDTO.getCursorTime(),
reqDTO.isFirstPage() ? null : reqDTO.getCursorId(),
reqDTO.getTenantId(),
limit + 1
);
// 判断是否有更多
boolean hasMore = list.size() > limit;
if (hasMore) {
list = list.subList(0, limit);
}
if (CollUtil.isEmpty(list)) {
return success(CursorPageResult.empty());
}
// 转换为同步数据
List<DatabusUserDeptData> dataList = list.stream()
.map(this::convertToData)
.collect(Collectors.toList());
// 获取最后一条数据的游标
UserDeptDO last = list.get(list.size() - 1);
// 首次查询时返<E697B6><E8BF94><EFBFBD>总数
Long total = null;
if (reqDTO.isFirstPage()) {
// 统计用户部门关系
total = userDeptMapper.countWithUserSource(reqDTO.getTenantId());
}
return success(CursorPageResult.of(
dataList,
last.getCreateTime(),
last.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusUserDeptData> getById(Long id) {
UserDeptDO userDept = userDeptMapper.selectById(id);
if (userDept == null) {
return success(null);
}
return success(convertToData(userDept));
}
@Override
public CommonResult<List<DatabusUserDeptData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<UserDeptDO> list = userDeptMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
}
return success(list.stream()
.map(this::convertToData)
.collect(Collectors.toList()));
}
@Override
public CommonResult<Long> count(Long tenantId) {
// 统计用户部门关系
return success(userDeptMapper.countWithUserSource(tenantId));
}
/**
* 将 UserDeptDO 转换为 DatabusUserDeptData
*/
private DatabusUserDeptData convertToData(UserDeptDO userDept) {
return DatabusUserDeptData.builder()
.id(userDept.getId())
.userId(userDept.getUserId())
.deptId(userDept.getDeptId())
.tenantId(userDept.getTenantId())
.remark(userDept.getRemark())
.build();
}
}

View File

@@ -0,0 +1,125 @@
package com.zt.plat.module.system.api.databus;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.module.databus.api.data.DatabusUserPostData;
import com.zt.plat.module.databus.api.dto.CursorPageReqDTO;
import com.zt.plat.module.databus.api.dto.CursorPageResult;
import com.zt.plat.module.databus.api.provider.DatabusUserPostProviderApi;
import com.zt.plat.module.system.dal.dataobject.dept.UserPostDO;
import com.zt.plat.module.system.dal.mysql.dept.UserPostMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* Databus 用户-岗位关系数据提供者 API 实现
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class DatabusUserPostProviderApiImpl implements DatabusUserPostProviderApi {
@Resource
private UserPostMapper userPostMapper;
@Override
public CommonResult<CursorPageResult<DatabusUserPostData>> getPageByCursor(CursorPageReqDTO reqDTO) {
// 多查一条判断是否有更多数据
int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100;
// ⚠️ 使用关联查询,只查询 userSource = 2 的用户的岗位关系
List<UserPostDO> list = userPostMapper.selectPageByCursorWithUserSource(
reqDTO.isFirstPage() ? null : reqDTO.getCursorTime(),
reqDTO.isFirstPage() ? null : reqDTO.getCursorId(),
reqDTO.getTenantId(),
limit + 1
);
// 判断是否有更多
boolean hasMore = list.size() > limit;
if (hasMore) {
list = list.subList(0, limit);
}
if (CollUtil.isEmpty(list)) {
return success(CursorPageResult.empty());
}
// 转换为同步数据
List<DatabusUserPostData> dataList = list.stream()
.map(this::convertToData)
.collect(Collectors.toList());
// 获取最后一条数据的游标
UserPostDO last = list.get(list.size() - 1);
// 首次查询时返回总数
Long total = null;
if (reqDTO.isFirstPage()) {
// ⚠️ 只统计 userSource = 2 的用户的岗位关系
total = userPostMapper.countWithUserSource(reqDTO.getTenantId());
}
return success(CursorPageResult.of(
dataList,
last.getCreateTime(),
last.getId(),
hasMore,
total
));
}
@Override
public CommonResult<DatabusUserPostData> getById(Long id) {
UserPostDO userPost = userPostMapper.selectById(id);
if (userPost == null) {
return success(null);
}
return success(convertToData(userPost));
}
@Override
public CommonResult<List<DatabusUserPostData>> getListByIds(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return success(Collections.emptyList());
}
List<UserPostDO> list = userPostMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
}
return success(list.stream()
.map(this::convertToData)
.collect(Collectors.toList()));
}
@Override
public CommonResult<Long> count(Long tenantId) {
// ⚠️ 只统计 userSource = 2 的用户的岗位关系
return success(userPostMapper.countWithUserSource(tenantId));
}
/**
* 将 UserPostDO 转换为 DatabusUserPostData
*/
private DatabusUserPostData convertToData(UserPostDO userPost) {
return DatabusUserPostData.builder()
.id(userPost.getId())
.userId(userPost.getUserId())
.postId(userPost.getPostId())
.build();
}
}

View File

@@ -54,6 +54,9 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi {
// 构建游标查询条件 // 构建游标查询条件
LambdaQueryWrapper<AdminUserDO> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<AdminUserDO> queryWrapper = new LambdaQueryWrapper<>();
// ⚠️ 只同步 userSource = 2 的用户
queryWrapper.eq(AdminUserDO::getUserSource, 2);
// 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) // 游标条件create_time > cursorTime OR (create_time = cursorTime AND id > cursorId)
if (!reqDTO.isFirstPage()) { if (!reqDTO.isFirstPage()) {
queryWrapper.and(w -> w queryWrapper.and(w -> w
@@ -100,6 +103,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi {
Long total = null; Long total = null;
if (reqDTO.isFirstPage()) { if (reqDTO.isFirstPage()) {
LambdaQueryWrapper<AdminUserDO> countWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<AdminUserDO> countWrapper = new LambdaQueryWrapper<>();
// ⚠️ 只统计 userSource = 2 的用户
countWrapper.eq(AdminUserDO::getUserSource, 2);
if (reqDTO.getTenantId() != null) { if (reqDTO.getTenantId() != null) {
countWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId()); countWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId());
} }
@@ -143,6 +148,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi {
@Override @Override
public CommonResult<Long> count(Long tenantId) { public CommonResult<Long> count(Long tenantId) {
LambdaQueryWrapper<AdminUserDO> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<AdminUserDO> queryWrapper = new LambdaQueryWrapper<>();
// ⚠️ 只统计 userSource = 2 的用户
queryWrapper.eq(AdminUserDO::getUserSource, 2);
if (tenantId != null) { if (tenantId != null) {
queryWrapper.eq(AdminUserDO::getTenantId, tenantId); queryWrapper.eq(AdminUserDO::getTenantId, tenantId);
} }

View File

@@ -107,4 +107,13 @@ public class DeptApiImpl implements DeptApi {
return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class));
} }
// ========== 数据同步专用接口 ==========
@Override
public CommonResult<Boolean> syncDept(DeptSaveReqDTO syncReqDTO) {
DeptSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, DeptSaveReqVO.class);
deptService.syncDept(reqVO);
return success(true);
}
} }

View File

@@ -10,6 +10,7 @@ import com.zt.plat.module.system.controller.admin.dept.vo.post.PostSaveReqVO;
import com.zt.plat.module.system.dal.dataobject.dept.PostDO; import com.zt.plat.module.system.dal.dataobject.dept.PostDO;
import com.zt.plat.module.system.service.dept.PostService; import com.zt.plat.module.system.service.dept.PostService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -20,6 +21,7 @@ import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success; import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Slf4j
@RestController // 提供 RESTful API 接口,给 Feign 调用 @RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated @Validated
public class PostApiImpl implements PostApi { public class PostApiImpl implements PostApi {
@@ -49,6 +51,7 @@ public class PostApiImpl implements PostApi {
@Override @Override
public CommonResult<PostRespDTO> getPost(Long id) { public CommonResult<PostRespDTO> getPost(Long id) {
log.error("cccccccc"+id);
PostDO post = postService.getPost(id); PostDO post = postService.getPost(id);
return success(BeanUtils.toBean(post, PostRespDTO.class)); return success(BeanUtils.toBean(post, PostRespDTO.class));
} }
@@ -72,4 +75,11 @@ public class PostApiImpl implements PostApi {
return success(BeanUtils.toBean(list, PostRespDTO.class)); return success(BeanUtils.toBean(list, PostRespDTO.class));
} }
@Override
public CommonResult<Boolean> syncPost(PostSaveReqDTO syncReqDTO) {
PostSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, PostSaveReqVO.class);
postService.syncPost(reqVO);
return success(true);
}
} }

View File

@@ -6,6 +6,7 @@ import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.permission.dto.*; import com.zt.plat.module.system.api.permission.dto.*;
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.PermissionAssignUserRoleReqVO; import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.zt.plat.module.system.enums.permission.DataScopeEnum;
import com.zt.plat.module.system.service.permission.PermissionService; import com.zt.plat.module.system.service.permission.PermissionService;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -65,6 +66,11 @@ public class PermissionApiImpl implements PermissionApi {
return success(permissionService.getUserRoleIdListByUserIdFromCache(userId)); return success(permissionService.getUserRoleIdListByUserIdFromCache(userId));
} }
@Override
public CommonResult<DataScopeEnum> getUserDataPermissionLevel(Long userId) {
return success(permissionService.getUserDataPermissionLevel(userId));
}
@Override @Override
public CommonResult<Boolean> hasAnyPermissions(Long userId, String... permissions) { public CommonResult<Boolean> hasAnyPermissions(Long userId, String... permissions) {
return success(permissionService.hasAnyPermissions(userId, permissions)); return success(permissionService.hasAnyPermissions(userId, permissions));

View File

@@ -149,4 +149,11 @@ public class AdminUserApiImpl implements AdminUserApi {
return success(true); return success(true);
} }
@Override
public CommonResult<Boolean> syncUser(AdminUserSaveReqDTO syncReqDTO) {
UserSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, UserSaveReqVO.class);
userService.syncUser(reqVO);
return success(true);
}
} }

View File

@@ -0,0 +1,88 @@
package com.zt.plat.module.system.api.userdept;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.userdept.dto.UserDeptRespDTO;
import com.zt.plat.module.system.api.userdept.dto.UserDeptSaveReqDTO;
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
import com.zt.plat.module.system.service.userdept.UserDeptService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 用户-部门关系 API 实现类
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class UserDeptApiImpl implements UserDeptApi {
@Resource
private UserDeptService userDeptService;
@Override
public CommonResult<Long> createUserDept(UserDeptSaveReqDTO reqVO) {
UserDeptDO userDept = BeanUtils.toBean(reqVO, UserDeptDO.class);
Long id = userDeptService.createUserDept(userDept);
return success(id);
}
@Override
public CommonResult<Boolean> updateUserDept(UserDeptSaveReqDTO reqVO) {
UserDeptDO userDept = BeanUtils.toBean(reqVO, UserDeptDO.class);
userDeptService.updateUserDept(userDept);
return success(true);
}
@Override
public CommonResult<Boolean> deleteUserDept(Long id) {
userDeptService.deleteUserDept(id);
return success(true);
}
@Override
public CommonResult<UserDeptRespDTO> getUserDept(Long id) {
UserDeptDO userDept = userDeptService.getUserDept(id);
return success(BeanUtils.toBean(userDept, UserDeptRespDTO.class));
}
@Override
public CommonResult<List<UserDeptRespDTO>> getUserDeptListByUserId(Long userId) {
List<UserDeptDO> list = userDeptService.getValidUserDeptListByUserIds(List.of(userId));
return success(BeanUtils.toBean(list, UserDeptRespDTO.class));
}
@Override
public CommonResult<List<UserDeptRespDTO>> getUserDeptListByDeptId(Long deptId) {
List<UserDeptDO> list = userDeptService.getValidUserDeptListByDeptIds(List.of(deptId));
return success(BeanUtils.toBean(list, UserDeptRespDTO.class));
}
@Override
public CommonResult<Boolean> deleteUserDeptByUserId(Long userId) {
userDeptService.deleteUserDeptByUserId(userId);
return success(true);
}
@Override
public CommonResult<Boolean> deleteUserDeptByDeptId(Long deptId) {
// 需要实现此方法,暂时返回成功
return success(true);
}
@Override
public CommonResult<Boolean> syncUserDept(UserDeptSaveReqDTO syncReqDTO) {
UserDeptDO userDept = BeanUtils.toBean(syncReqDTO, UserDeptDO.class);
userDeptService.syncUserDept(userDept);
return success(true);
}
}

View File

@@ -0,0 +1,106 @@
package com.zt.plat.module.system.api.userpost;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.api.userpost.dto.UserPostRespDTO;
import com.zt.plat.module.system.api.userpost.dto.UserPostSaveReqDTO;
import com.zt.plat.module.system.dal.dataobject.dept.UserPostDO;
import com.zt.plat.module.system.dal.mysql.dept.UserPostMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 用户-岗位关系 API 实现类
*
* @author ZT
*/
@Slf4j
@RestController
@Validated
public class UserPostApiImpl implements UserPostApi {
@Resource
private UserPostMapper userPostMapper;
@Override
public CommonResult<Long> createUserPost(UserPostSaveReqDTO reqVO) {
UserPostDO userPost = BeanUtils.toBean(reqVO, UserPostDO.class);
userPostMapper.insert(userPost);
return success(userPost.getId());
}
@Override
public CommonResult<Boolean> updateUserPost(UserPostSaveReqDTO reqVO) {
UserPostDO userPost = BeanUtils.toBean(reqVO, UserPostDO.class);
userPostMapper.updateById(userPost);
return success(true);
}
@Override
public CommonResult<Boolean> deleteUserPost(Long id) {
userPostMapper.deleteById(id);
return success(true);
}
@Override
public CommonResult<UserPostRespDTO> getUserPost(Long id) {
UserPostDO userPost = userPostMapper.selectById(id);
return success(BeanUtils.toBean(userPost, UserPostRespDTO.class));
}
@Override
public CommonResult<List<UserPostRespDTO>> getUserPostListByUserId(Long userId) {
List<UserPostDO> list = userPostMapper.selectListByUserId(userId);
return success(BeanUtils.toBean(list, UserPostRespDTO.class));
}
@Override
public CommonResult<List<UserPostRespDTO>> getUserPostListByPostId(Long postId) {
List<UserPostDO> list = userPostMapper.selectListByPostIds(List.of(postId));
return success(BeanUtils.toBean(list, UserPostRespDTO.class));
}
@Override
public CommonResult<Boolean> deleteUserPostByUserId(Long userId) {
userPostMapper.deleteByUserId(userId);
return success(true);
}
@Override
public CommonResult<Boolean> deleteUserPostByPostId(Long postId) {
userPostMapper.delete(UserPostDO::getPostId, postId);
return success(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public CommonResult<Boolean> syncUserPost(UserPostSaveReqDTO syncReqDTO) {
if (syncReqDTO.getId() == null) {
return success(false);
}
UserPostDO existing = userPostMapper.selectById(syncReqDTO.getId());
UserPostDO userPost = BeanUtils.toBean(syncReqDTO, UserPostDO.class);
if (existing != null) {
userPostMapper.updateById(userPost);
log.info("[syncUserPost] 用户岗位关系同步-更新成功, id={}, userId={}, postId={}",
userPost.getId(), userPost.getUserId(), userPost.getPostId());
} else {
userPostMapper.insert(userPost);
log.info("[syncUserPost] 用户岗位关系同步-创建成功, id={}, userId={}, postId={}",
userPost.getId(), userPost.getUserId(), userPost.getPostId());
}
return success(true);
}
}

View File

@@ -143,6 +143,7 @@ public class AuthController {
@PostMapping("/sms-login") @PostMapping("/sms-login")
@PermitAll @PermitAll
@TenantIgnore
@Operation(summary = "使用短信验证码登录") @Operation(summary = "使用短信验证码登录")
public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
return success(authService.smsLogin(reqVO)); return success(authService.smsLogin(reqVO));

View File

@@ -0,0 +1,117 @@
package com.zt.plat.module.system.controller.admin.dept;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
import com.zt.plat.module.system.dal.dataobject.dept.DeptDO;
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
import com.zt.plat.module.system.service.dept.DeptService;
import com.zt.plat.module.system.service.dept.IEspService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 部门推送消息")
@RestController
@RequestMapping("/system/esp")
@Validated
public class EspController
{
@Resource
private IEspService espService;
@Resource
private DeptService deptService;
@PostMapping("/create")
@Operation(summary = "创建部门推送消息")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:create')")
public CommonResult<Long> create(@Valid @RequestBody EspSaveRespVo createReqVO) {
Long id = espService.createDeptPushMsg(createReqVO);
return success(id);
}
@PutMapping("/update")
@Operation(summary = "修改部门推送消息")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:update')")
public CommonResult<Boolean> update(@Valid @RequestBody EspSaveRespVo updateReqVO) {
espService.updateDeptPushMsg(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除部门推送消息")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:delete')")
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
espService.deleteDeptPushMsg(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取部门推送消息详情")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
public CommonResult<EspSaveRespVo> get(@RequestParam("id") Long id) {
DeptPushMsgDO entity = espService.getDeptPushMsgDetails(id);
EspSaveRespVo respVO = BeanUtils.toBean(entity, EspSaveRespVo.class);
fillDeptInfo(List.of(respVO));
return success(respVO);
}
@GetMapping("/page")
@Operation(summary = "分页查询部门推送消息")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
public CommonResult<PageResult<EspSaveRespVo>> page(@Valid EspPageReqVO reqVO) {
PageResult<DeptPushMsgDO> pageResult = espService.getDeptExternalCodePage(reqVO);
PageResult<EspSaveRespVo> result = BeanUtils.toBean(pageResult, EspSaveRespVo.class);
fillDeptInfo(result.getList());
return success(result);
}
@GetMapping("/list-by-dept")
@Operation(summary = "根据部门部门推送消息")
@Parameter(name = "deptId", description = "部门编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:esp-external-code:query')")
public CommonResult<List<EspSaveRespVo>> listByDept(@RequestParam("deptId") Long deptId) {
List<DeptPushMsgDO> list = espService.getPushMsgByDeptId(deptId);
List<EspSaveRespVo> respList = BeanUtils.toBean(list, EspSaveRespVo.class);
fillDeptInfo(respList);
return success(respList);
}
private void fillDeptInfo(List<EspSaveRespVo> list) {
if (list == null || list.isEmpty()) {
return;
}
Set<Long> deptIds = list.stream()
.map(EspSaveRespVo::getDeptId)
.collect(Collectors.toCollection(HashSet::new));
if (deptIds == null || deptIds.isEmpty()) {
return;
}
Map<Long, DeptDO> deptMap = deptService.getDeptList(deptIds).stream()
.collect(Collectors.toMap(DeptDO::getId, dept -> dept, (left, right) -> left));
list.forEach(item -> {
DeptDO dept = deptMap.get(item.getDeptId());
if (dept != null) {
item.setDeptName(dept.getName());
item.setDeptCode(dept.getCode());
}
});
}
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 部门外部组织编码映射分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class EspPageReqVO extends PageParam {
@Schema(description = "部门编号", example = "1024")
private Long deptId;
@Schema(description = "外部系统标识", example = "ERP")
private String systemCode;
@Schema(description = "外部组织编码", example = "100200")
private String externalDeptCode;
@Schema(description = "状态", example = "0")
private Integer status;
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 部门外消息推送创建/修改 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class EspSaveRespVo extends DeptExternalCodeBaseVO {
@Schema(description = "映射编号", example = "1024")
private Long id;
@Schema(description = "所属部门名称", example = "技术部")
private String deptName;
@Schema(description = "所属部门编码", example = "DEPT_001")
private String deptCode;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "最后更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,58 @@
package com.zt.plat.module.system.dal.dataobject.dept;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.common.enums.CommonStatusEnum;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 部门推送消息 DO
*/
@TableName("system_dept_push_msg")
@KeySequence("system_dept_push_msg_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class DeptPushMsgDO extends TenantBaseDO {
/**
* 主键编号
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 本系统部门 ID
*/
private Long deptId;
/**
* 外部系统标识
*/
private String systemCode;
/**
* 外部系统组织编码
*/
private String externalDeptCode;
/**
* 外部系统组织名称
*/
private String externalDeptName;
/**
* 映射状态
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 备注
*/
private String remark;
}

View File

@@ -127,12 +127,17 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
/** /**
* 根据部门编码查询部门 * 根据部门编码查询部门
* <p>
* 注意:如果存在多条相同编码的记录,只返回第一条
* *
* @param code 部门编码 * @param code 部门编码
* @return 部门信息 * @return 部门信息
*/ */
default DeptDO selectByCode(String code) { default DeptDO selectByCode(String code) {
return selectOne(DeptDO::getCode, code); List<DeptDO> list = selectList(new LambdaQueryWrapperX<DeptDO>()
.eq(DeptDO::getCode, code)
.last("LIMIT 1"));
return CollUtil.isNotEmpty(list) ? list.get(0) : null;
} }
/** /**

View File

@@ -0,0 +1,57 @@
package com.zt.plat.module.system.dal.mysql.dept;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO;
import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo;
import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 部门推送消息接口Mapper
*/
@Mapper
public interface EspMapper extends BaseMapperX<DeptPushMsgDO> {
/**
* 分页查询
* @param reqVO 消息推送VO
* @return PageResult
*/
default PageResult<DeptPushMsgDO> selectPage(EspPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DeptPushMsgDO>()
.eqIfPresent(DeptPushMsgDO::getDeptId,reqVO.getDeptId())
.eqIfPresent(DeptPushMsgDO::getSystemCode, reqVO.getSystemCode())
.likeIfPresent(DeptPushMsgDO::getExternalDeptCode, reqVO.getExternalDeptCode())
.eqIfPresent(DeptPushMsgDO::getStatus, reqVO.getStatus())
.orderByDesc(DeptPushMsgDO::getId));
}
default DeptPushMsgDO selectBySystemCodeAndDeptId(String systemCode, Long deptId) {
return selectOne(new LambdaQueryWrapperX<DeptPushMsgDO>()
.eq(DeptPushMsgDO::getSystemCode, systemCode)
.eq(DeptPushMsgDO::getDeptId, deptId));
}
default DeptPushMsgDO selectBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) {
return selectOne(new LambdaQueryWrapperX<DeptPushMsgDO>()
.eq(DeptPushMsgDO::getSystemCode, systemCode)
.eq(DeptPushMsgDO::getExternalDeptCode, externalDeptCode));
}
default List<DeptPushMsgDO> selectListByDeptId(Long deptId) {
return selectList(DeptPushMsgDO::getDeptId, deptId);
}
default int deleteByDeptId(Long deptId) {
return delete(DeptPushMsgDO::getDeptId, deptId);
}
default List<DeptPushMsgDO> selectListBySystemCode(String systemCode) {
return selectList(DeptPushMsgDO::getSystemCode, systemCode);
}
}

View File

@@ -5,7 +5,10 @@ import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.dal.dataobject.dept.UserPostDO; import com.zt.plat.module.system.dal.dataobject.dept.UserPostDO;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -29,4 +32,44 @@ public interface UserPostMapper extends BaseMapperX<UserPostDO> {
default void deleteByUserId(Long userId) { default void deleteByUserId(Long userId) {
delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId)); delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId));
} }
/**
* 游标分页查询用户-岗位关系(只查询 userSource = 2 的用户)
* @param cursorTime 游标时间
* @param cursorId 游标ID
* @param tenantId 租户ID可选
* @param limit 限制数量
* @return 用户岗位关系列表
*/
@Select("<script>" +
"SELECT up.* FROM system_user_post up " +
"INNER JOIN system_users u ON up.user_id = u.id " +
"WHERE u.user_source = 2 " +
"AND up.deleted = 0 " +
"<if test='tenantId != null'> AND up.tenant_id = #{tenantId} </if>" +
"<if test='cursorTime != null'>" +
" AND (up.create_time > #{cursorTime} " +
" OR (up.create_time = #{cursorTime} AND up.id > #{cursorId}))" +
"</if>" +
"ORDER BY up.create_time ASC, up.id ASC " +
"LIMIT #{limit}" +
"</script>")
List<UserPostDO> selectPageByCursorWithUserSource(@Param("cursorTime") LocalDateTime cursorTime,
@Param("cursorId") Long cursorId,
@Param("tenantId") Long tenantId,
@Param("limit") Integer limit);
/**
* 统计用户-岗位关系数量(只统计 userSource = 2 的用户)
* @param tenantId 租户ID可选
* @return 数量
*/
@Select("<script>" +
"SELECT COUNT(*) FROM system_user_post up " +
"INNER JOIN system_users u ON up.user_id = u.id " +
"WHERE u.user_source = 2 " +
"AND up.deleted = 0 " +
"<if test='tenantId != null'> AND up.tenant_id = #{tenantId} </if>" +
"</script>")
Long countWithUserSource(@Param("tenantId") Long tenantId);
} }

View File

@@ -4,7 +4,10 @@ import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO; import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -45,4 +48,44 @@ public interface UserDeptMapper extends BaseMapperX<UserDeptDO> {
); );
} }
/**
* 游标分页查询用户-部门关系(只查询 userSource = 2 的用户)
* @param cursorTime 游标时间
* @param cursorId 游标ID
* @param tenantId 租户ID可选
* @param limit 限制数量
* @return 用户部门关系列表
*/
@Select("<script>" +
"SELECT ud.* FROM system_user_dept ud " +
"INNER JOIN system_users u ON ud.user_id = u.id " +
"WHERE u.user_source = 2 " +
"AND ud.deleted = 0 " +
"<if test='tenantId != null'> AND ud.tenant_id = #{tenantId} </if>" +
"<if test='cursorTime != null'>" +
" AND (ud.create_time > #{cursorTime} " +
" OR (ud.create_time = #{cursorTime} AND ud.id > #{cursorId}))" +
"</if>" +
"ORDER BY ud.create_time ASC, ud.id ASC " +
"LIMIT #{limit}" +
"</script>")
List<UserDeptDO> selectPageByCursorWithUserSource(@Param("cursorTime") LocalDateTime cursorTime,
@Param("cursorId") Long cursorId,
@Param("tenantId") Long tenantId,
@Param("limit") Integer limit);
/**
* 统计用户-部门关系数量(只统计 userSource = 2 的用户)
* @param tenantId 租户ID可选
* @return 数量
*/
@Select("<script>" +
"SELECT COUNT(*) FROM system_user_dept ud " +
"INNER JOIN system_users u ON ud.user_id = u.id " +
"WHERE u.user_source = 2 " +
"AND ud.deleted = 0 " +
"<if test='tenantId != null'> AND ud.tenant_id = #{tenantId} </if>" +
"</script>")
Long countWithUserSource(@Param("tenantId") Long tenantId);
} }

View File

@@ -14,7 +14,7 @@ import java.util.List;
@Data @Data
public class SmsSendMessage { public class SmsSendMessage {
public static final String TOPIC = "SMS_SEND_TOPIC"; // 重点:需要增加消息对应的 Topic public static final String TOPIC = "SMS_SEND_TOPIC_TEST"; // 重点:需要增加消息对应的 Topic
/** /**
* 短信日志编号 * 短信日志编号
*/ */

View File

@@ -174,4 +174,15 @@ public interface DeptService {
* @return 部门列表 * @return 部门列表
*/ */
List<DeptDO> searchDeptTree(String keyword); List<DeptDO> searchDeptTree(String keyword);
/**
* 回填缺失的部门编码(不触发事件)。
*
* @param deptIds 需要回填的部门 ID 列表
*/
void backfillMissingCodesWithoutEvent(Collection<Long> deptIds);
// ========== 数据同步专用接口 ==========
void syncDept(DeptSaveReqVO syncReqVO);
} }

Some files were not shown because too many files have changed in this diff Show More