diff --git a/pom.xml b/pom.xml index e10371b8..e4f07311 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 3.0.45 + 3.0.46 17 ${java.version} @@ -242,6 +242,19 @@ 1.0.0 + + klw-dev + + dev + + 172.16.46.63:30848 + klw + DEFAULT_GROUP + nacos + P@ssword25 + 1.0.0 + + env-prod @@ -271,7 +284,8 @@ chenbowen - local + + dev 172.16.46.63:30848 chenbowen diff --git a/sql/dm/bpm.sql b/sql/dm/bpm.sql index 04bc442b..a0501d50 100644 --- a/sql/dm/bpm.sql +++ b/sql/dm/bpm.sql @@ -120,6 +120,7 @@ CREATE TABLE bpm_process_definition_info ( simple_model text NULL, sort bigint DEFAULT 0 NULL, visible bit DEFAULT '1' NOT NULL, + restart bit DEFAULT '1' NOT NULL, start_user_ids varchar(256) DEFAULT NULL NULL, start_dept_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.sort 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_dept_ids IS '可发起部门编号数组'; COMMENT ON COLUMN bpm_process_definition_info.manager_user_ids IS '可管理用户编号数组'; diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index e159dc37..53667219 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -331,6 +331,8 @@ CREATE TABLE infra_file ( name varchar(256) DEFAULT NULL NULL, path varchar(512) 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, size int NOT 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.path IS '文件路径'; 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加密时的随机IV(Base64编码)'; COMMENT ON COLUMN infra_file.type IS '文件类型'; COMMENT ON COLUMN infra_file.size 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 TABLE infra_file IS '文件表'; +CREATE INDEX idx_infra_file_hash ON infra_file(hash); + -- ---------------------------- -- 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 (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 (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; -- SET IDENTITY_INSERT system_menu OFF; -- @formatter:on @@ -1944,7 +2060,7 @@ COMMENT ON TABLE system_oauth2_refresh_token IS 'OAuth2 刷新令牌'; CREATE TABLE system_operate_log ( id bigint NOT NULL PRIMARY KEY, trace_id varchar(64) DEFAULT '' NULL, - user_id bigint NOT NULL, + user_id bigint NULL, user_type smallint DEFAULT 0 NOT NULL, type varchar(50) NOT NULL, sub_type varchar(50) NOT NULL, @@ -3560,7 +3676,7 @@ COMMENT ON TABLE system_users IS '用户信息表'; -- ---------------------------- -- @formatter:off -- 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 (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); @@ -3897,6 +4013,7 @@ CREATE TABLE infra_bsn_file ( file_id bigint NOT NULL, file_name varchar(500) DEFAULT '' NULL, src varchar(100) DEFAULT '' NULL, + status smallint DEFAULT 1 NOT NULL, creator varchar(64) DEFAULT '' NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP NOT 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_name 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.create_time IS '创建时间'; COMMENT ON COLUMN infra_bsn_file.updater IS '更新者'; COMMENT ON COLUMN infra_bsn_file.update_time IS '最后更新时间'; COMMENT ON COLUMN infra_bsn_file.deleted 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 @@ -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.deleted 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); diff --git a/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql b/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql index ab9dcc19..c9dc74f1 100644 --- a/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql +++ b/sql/dm/数据总线API凭证绑定与访问日志补充_20251209.sql @@ -19,7 +19,8 @@ CREATE TABLE databus_api_definition_credential ( 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_cred ON databus_api_definition_credential (credential_id); diff --git a/sql/dm/流程定义扩展信息表新增字段_20251230.sql b/sql/dm/流程定义扩展信息表新增字段_20251230.sql new file mode 100644 index 00000000..938b3946 --- /dev/null +++ b/sql/dm/流程定义扩展信息表新增字段_20251230.sql @@ -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 '是否允许重新发起'; + + diff --git a/zt-dependencies/pom.xml b/zt-dependencies/pom.xml index b5c1f76c..e3e5f972 100644 --- a/zt-dependencies/pom.xml +++ b/zt-dependencies/pom.xml @@ -26,12 +26,13 @@ https://github.com/YunaiV/ruoyi-vue-pro - 3.0.45 + 3.0.46 1.6.0 3.4.5 2024.0.1 2023.0.3.2 + 2.4.0 2.8.3 4.6.0 @@ -133,6 +134,24 @@ import + + + org.apache.seata + seata-all + ${seata.version} + + + org.apache.seata + seata-spring-boot-starter + ${seata.version} + + + + com.zt.plat + zt-spring-boot-starter-seata-dm + ${revision} + + io.github.mouzt diff --git a/zt-framework/pom.xml b/zt-framework/pom.xml index 241a211e..5eca3e83 100644 --- a/zt-framework/pom.xml +++ b/zt-framework/pom.xml @@ -33,6 +33,7 @@ zt-spring-boot-starter-biz-data-permission zt-spring-boot-starter-biz-ip zt-spring-boot-starter-biz-business + zt-spring-boot-starter-seata-dm zt-framework diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java index 03502d9c..0f18c4bb 100644 --- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java +++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/core/util/BusinessDeptHandleUtil.java @@ -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.security.core.LoginUser; 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.module.system.api.dept.DeptApi; import com.zt.plat.module.system.api.dept.dto.CompanyDeptInfoRespDTO; @@ -197,6 +198,9 @@ public class BusinessDeptHandleUtil { } CompanyContextHolder.setIgnore(false); CompanyContextHolder.setCompanyId(Long.valueOf(info.getCompanyId())); + DeptContextHolder.setIgnore(false); + DeptContextHolder.setCompanyId(Long.valueOf(info.getCompanyId())); + DeptContextHolder.setDeptId(Long.valueOf(info.getDeptId())); return true; } } diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java index 174049ad..5f9a19c8 100644 --- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionConfiguration.java @@ -7,6 +7,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; import java.util.LinkedHashSet; import java.util.Set; @@ -18,14 +20,12 @@ import java.util.Set; public class BusinessDataPermissionConfiguration { @Bean - public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext) { + public BusinessDataPermissionEntityScanner businessDataPermissionEntityScanner(BeanFactory beanFactory, ApplicationContext applicationContext, Environment environment) { Set basePackages = new LinkedHashSet<>(); + addConfiguredBasePackages(environment, basePackages); if (AutoConfigurationPackages.has(beanFactory)) { basePackages.addAll(AutoConfigurationPackages.get(beanFactory)); } - if (basePackages.isEmpty()) { - basePackages.add("com.zt"); - } ClassLoader classLoader = applicationContext != null ? applicationContext.getClassLoader() : Thread.currentThread().getContextClassLoader(); @@ -35,6 +35,21 @@ public class BusinessDataPermissionConfiguration { return new BusinessDataPermissionEntityScanner(basePackages, classLoader); } + private void addConfiguredBasePackages(Environment environment, Set 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 public CompanyDataPermissionRuleCustomizer autoCompanyDataPermissionRuleCustomizer(BusinessDataPermissionEntityScanner scanner) { return rule -> scanner.getEntityMetadata().forEach(metadata -> { diff --git a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java index 93c306f5..2c195fed 100644 --- a/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java +++ b/zt-framework/zt-spring-boot-starter-biz-business/src/main/java/com/zt/plat/framework/business/framework/BusinessDataPermissionEntityScanner.java @@ -28,6 +28,14 @@ import java.util.*; @Slf4j public class BusinessDataPermissionEntityScanner { + /** + * 临时排除的包前缀(物流模块 DO,不参与数据权限扫描) + */ + private static final Set 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 basePackages; private final ClassLoader classLoader; @@ -70,6 +78,9 @@ public class BusinessDataPermissionEntityScanner { if (!StringUtils.hasText(className)) { continue; } + if (isExcludedPackage(className)) { + continue; + } try { Class clazz = ClassUtils.forName(className, classLoader); if (clazz == BusinessBaseDO.class || !BusinessBaseDO.class.isAssignableFrom(clazz)) { @@ -92,6 +103,15 @@ public class BusinessDataPermissionEntityScanner { 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 entityClass) { String tableName = resolveTableName(entityClass); if (!StringUtils.hasText(tableName)) { diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md b/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md new file mode 100644 index 00000000..669a0729 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/docs/数据权限忽略与上下文说明.md @@ -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` 中恢复旧值,嵌套调用安全。 +- 若需要同时忽略公司与部门数据权限,可叠加两个注解或在业务代码中分别设置忽略标记。 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java index 0a1c5350..1c7d9e91 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java @@ -1,6 +1,8 @@ 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.DeptDataPermissionIgnoreAspect; 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.DataPermissionRuleFactory; @@ -43,4 +45,14 @@ public class ZtDataPermissionAutoConfiguration { return new DataPermissionAnnotationAdvisor(); } + @Bean + public DeptDataPermissionIgnoreAspect deptDataPermissionIgnoreAspect() { + return new DeptDataPermissionIgnoreAspect(); + } + + @Bean + public CompanyDataPermissionIgnoreAspect companyDataPermissionIgnoreAspect() { + return new CompanyDataPermissionIgnoreAspect(); + } + } diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java new file mode 100644 index 00000000..ecc4f1bb --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/CompanyDataPermissionIgnore.java @@ -0,0 +1,21 @@ +package com.zt.plat.framework.datapermission.core.annotation; + +import java.lang.annotation.*; + +/** + * 忽略公司数据权限的注解。 + *

+ * 标记在方法或类上时,匹配的调用会临时忽略公司类型的数据权限规则。 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface CompanyDataPermissionIgnore { + + /** + * 是否开启忽略,默认开启。 + * 支持 Spring EL 表达式,返回 true 时生效。 + */ + String enable() default "true"; +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java new file mode 100644 index 00000000..de80a7d1 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/annotation/DeptDataPermissionIgnore.java @@ -0,0 +1,21 @@ +package com.zt.plat.framework.datapermission.core.annotation; + +import java.lang.annotation.*; + +/** + * 忽略部门数据权限的注解。 + *

+ * 标记在方法或类上时,匹配的调用会临时忽略部门类型的数据权限规则。 + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DeptDataPermissionIgnore { + + /** + * 是否开启忽略,默认开启。 + * 支持 Spring EL 表达式,返回 true 时生效。 + */ + String enable() default "true"; +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java new file mode 100644 index 00000000..ae051a25 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/CompanyDataPermissionIgnoreAspect.java @@ -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); + } + } +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java new file mode 100644 index 00000000..4ee9054e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/aop/DeptDataPermissionIgnoreAspect.java @@ -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); + } + } +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java index 0ad24ca5..6f1e3f48 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/db/DataPermissionRuleHandler.java @@ -10,7 +10,9 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.schema.Table; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck; @@ -41,6 +43,7 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler { } // 生成条件 + final Set processed = new HashSet<>(); Expression allExpression = null; for (DataPermissionRule rule : rules) { // 判断表名是否匹配 @@ -49,6 +52,14 @@ public class DataPermissionRuleHandler implements MultiDataPermissionHandler { 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()); if (oneExpress == null) { diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java index f7d7e84d..25909d05 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/company/CompanyDataPermissionRule.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil; import com.zt.plat.framework.common.util.collection.CollectionUtils; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule; import com.zt.plat.framework.mybatis.core.util.MyBatisUtils; +import com.zt.plat.framework.tenant.core.context.CompanyContextHolder; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Alias; @@ -49,6 +50,10 @@ public class CompanyDataPermissionRule implements DataPermissionRule { @Override public Expression getExpression(String tableName, Alias tableAlias) { + // 显式忽略公司数据权限时直接放行 + if (CompanyContextHolder.isIgnore()) { + return null; + } // 业务拼接 Company 的条件 if (getLoginUserCompanyId() == null) { // 如果没有登录用户的公司编号,则不需要拼接条件 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index 7ff3fe02..ea4a8a93 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -3,6 +3,7 @@ package com.zt.plat.framework.datapermission.core.rule.dept; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; 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.dto.DeptDataPermissionRespDTO; 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.util.SecurityFrameworkUtils; 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.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Alias; @@ -108,6 +109,11 @@ public class DeptDataPermissionRule implements DataPermissionRule { return null; } + // 显式忽略部门数据权限时直接放行 + if (DeptContextHolder.shouldIgnore()) { + return null; + } + // 获得数据权限 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 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; } - // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 - if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) - && Boolean.FALSE.equals(deptDataPermission.getSelf())) { + // 情况二:仅在有效部门集合为空且不可查看自己时,才认为无权限;若上下文提供部门,则跳过该兜底 + if (CollUtil.isEmpty(effectiveDeptIds) + && Boolean.FALSE.equals(effectiveSelf)) { return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空 } // 情况三,拼接 Dept 和 Company User 的条件,最后组合 - Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); + Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds); // 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) { // TODO ZT:获得不到条件的时候,暂时不抛出异常,而是不返回数据 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java index 2177695b..e68941db 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/test/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java @@ -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.security.core.LoginUser; 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.common.biz.system.permission.dto.DeptDataPermissionRespDTO; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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.same; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; /** @@ -48,7 +52,13 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { // 清空 rule rule.getTableNames().clear(); ((Map) ReflectUtil.getFieldValue(rule, "deptColumns")).clear(); - ((Map) ReflectUtil.getFieldValue(rule, "deptColumns")).clear(); + ((Map) ReflectUtil.getFieldValue(rule, "userColumns")).clear(); + } + + @AfterEach + void tearDown() { + DeptContextHolder.clear(); + CompanyContextHolder.clear(); } @Test // 无 LoginUser @@ -236,4 +246,151 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest { } } + @Test // 忽略部门数据权限,直接放行 + void testGetExpression_ignoreDeptContext() { + try (MockedStatic secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic 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 secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic 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 secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic 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 secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic 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 secMock = mockStatic(SecurityFrameworkUtils.class); + MockedStatic deptCtxMock = mockStatic(DeptContextHolder.class); + MockedStatic 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()); + } + } + } diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java new file mode 100644 index 00000000..e463ae50 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java @@ -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 DEPT_ID = new TransmittableThreadLocal<>(); + /** 当前部门所属公司编号(用于一致性校验) */ + private static final ThreadLocal COMPANY_ID = new TransmittableThreadLocal<>(); + /** 是否忽略部门数据权限 */ + private static final ThreadLocal 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(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/TenantContextHolder.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/TenantContextHolder.java index ac0b17aa..be8d66ca 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/TenantContextHolder.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/TenantContextHolder.java @@ -1,7 +1,5 @@ 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; /** @@ -38,8 +36,10 @@ public class TenantContextHolder { public static Long getRequiredTenantId() { Long tenantId = getTenantId(); if (tenantId == null) { - throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:" - + DocumentEnum.TENANT.getUrl()); +// throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:" +// + DocumentEnum.TENANT.getUrl()); + // 暂定所有获取不到租户的操作,默认都使用主租户进行操作 1l + tenantId = 1L; } return tenantId; } diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java index 538f6d4f..f4688a21 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java @@ -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.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.web.core.util.WebFrameworkUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -66,11 +67,19 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor { if (companyId == null || companyId <= 0L) { CompanyContextHolder.setIgnore(true); + DeptContextHolder.clear(); return true; } CompanyContextHolder.setIgnore(false); CompanyContextHolder.setCompanyId(companyId); + // 默认不忽略部门数据权限;如果有有效部门则写入上下文 + DeptContextHolder.setIgnore(false); + if (deptId != null && deptId > 0L) { + DeptContextHolder.setContext(deptId, companyId); + } else { + DeptContextHolder.clear(); + } if (loginUser == null) { return true; } @@ -91,7 +100,9 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor { LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); if (loginUser != null) { loginUser.setVisitCompanyId(0L); + loginUser.setVisitDeptId(0L); } + DeptContextHolder.clear(); } private Long resolveLong(Object value) { diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/test/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptorTest.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/test/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptorTest.java new file mode 100644 index 00000000..6fbe99ff --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/test/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptorTest.java @@ -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()); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-client/pom.xml b/zt-framework/zt-spring-boot-starter-databus-client/pom.xml index 08e8dcc8..ce35f87e 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/pom.xml +++ b/zt-framework/zt-spring-boot-starter-databus-client/pom.xml @@ -46,14 +46,12 @@ com.zt.plat zt-spring-boot-starter-mq - - com.zt.plat - zt-spring-boot-starter-redis + zt-spring-boot-starter-biz-tenant - + org.springframework.boot spring-boot-starter-web diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java index 4a7c5cdb..0000776e 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/core/consumer/DatabusClientConsumer.java @@ -13,7 +13,7 @@ import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; - +import com.zt.plat.framework.tenant.core.context.TenantContextHolder; /** * DataBus 客户端统一消费者 *

@@ -33,8 +33,8 @@ import org.springframework.stereotype.Component; @Component @ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true") @RocketMQMessageListener( - topic = "${zt.databus.sync.client.mq.topic: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}" + topic = "${zt.databus.sync.client.mq.topic-base:databus-sync}-${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 { @@ -46,6 +46,7 @@ public class DatabusClientConsumer implements RocketMQListener { log.debug("[DatabusClient] 收到消息, body={}", body); try { + TenantContextHolder.setTenantId(1L); // 1. 解析消息获取 eventType DatabusEventType eventType = parseEventType(body); if (eventType == null) { diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java index ca760435..00d9913a 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/dept/DeptSyncServiceImpl.java @@ -69,21 +69,15 @@ public class DeptSyncServiceImpl implements DeptSyncService { return; } DeptSaveReqDTO dto = buildDeptDTO(data); + + // 使用专用同步接口,跳过业务校验,直接 upsert try { - // 尝试获取,存在则更新,不存在则创建 - var existing = deptApi.getDept(dto.getId()); - 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()); - } + deptApi.syncDept(dto).checkError(); + log.info("[DeptSync] 部门全量同步成功, deptId={}, deptName={}", dto.getId(), dto.getName()); } catch (Exception e) { - // 获取失败,尝试创建 - log.warn("[DeptSync] 部门获取失败,尝试创建, deptId={}", dto.getId()); - deptApi.createDept(dto).checkError(); - log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId()); + log.error("[DeptSync] 部门全量同步失败, deptId={}, deptName={}, parentId={}, code={}, error={}", + dto.getId(), dto.getName(), dto.getParentId(), dto.getCode(), e.getMessage()); + throw e; } } @@ -93,13 +87,18 @@ public class DeptSyncServiceImpl implements DeptSyncService { private DeptSaveReqDTO buildDeptDTO(DatabusDeptData data) { DeptSaveReqDTO dto = new DeptSaveReqDTO(); dto.setId(data.getId()); + dto.setCode(data.getCode()); // ⚠️ 重要:传递编码,保持一致 dto.setName(data.getName()); + dto.setShortName(data.getShortName()); dto.setParentId(data.getParentId()); dto.setSort(data.getSort()); dto.setLeaderUserId(data.getLeaderUserId()); dto.setPhone(data.getPhone()); dto.setEmail(data.getEmail()); dto.setStatus(data.getStatus()); + dto.setIsGroup(data.getIsGroup()); + dto.setIsCompany(data.getIsCompany()); + dto.setDeptSource(data.getDeptSource()); return dto; } } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java index 463fdc6e..6ad68f2e 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/post/PostSyncServiceImpl.java @@ -68,25 +68,14 @@ public class PostSyncServiceImpl implements PostSyncService { return; } PostSaveReqDTO dto = buildPostDTO(data); + try { - // 尝试获取,存在则更新,不存在则创建 - var existing = postApi.getPost(dto.getId()); - 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()); - } + postApi.syncPost(dto).checkError(); + log.info("[PostSync] 岗位全量同步成功, postId={}, postName={}", dto.getId(), dto.getName()); } catch (Exception e) { - // 获取失败,尝试创建 - try { - postApi.createPost(dto).checkError(); - log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName()); - } catch (Exception createEx) { - log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}", dto.getId(), dto.getName(), createEx); - throw createEx; - } + log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}, code={}, error={}", + dto.getId(), dto.getName(), dto.getCode(), e.getMessage()); + throw e; } } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java index d40cfd51..a0b239df 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/user/AdminUserSyncServiceImpl.java @@ -72,21 +72,14 @@ public class AdminUserSyncServiceImpl implements AdminUserSyncService { return; } AdminUserSaveReqDTO dto = buildUserDTO(data); + try { - // 尝试获取,存在则更新,不存在则创建 - var existing = adminUserApi.getUser(dto.getId()); - 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()); - } + adminUserApi.syncUser(dto).checkError(); + log.info("[UserSync] 用户全量同步成功, userId={}, username={}", dto.getId(), dto.getUsername()); } catch (Exception e) { - // 获取失败,尝试创建 - log.warn("[UserSync] 用户获取失败,尝试创建, userId={}", dto.getId()); - adminUserApi.createUser(dto).checkError(); - log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId()); + log.error("[UserSync] 用户全量同步失败, userId={}, username={}, error={}", + dto.getId(), dto.getUsername(), e.getMessage()); + throw e; } } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userdept/UserDeptSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userdept/UserDeptSyncServiceImpl.java index 9027e379..36914af0 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userdept/UserDeptSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userdept/UserDeptSyncServiceImpl.java @@ -1,56 +1,94 @@ package com.zt.plat.framework.databus.client.handler.userdept; 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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; /** - * 用户-部门关系同步服务实现 + * 用户-部门关系同步服务实现(通过 Feign API 调用远程服务) *

* 使用条件: * 1. zt.databus.sync.client.enabled=true + * 2. 系统中存在 UserDeptApi 接口(Feign 客户端) *

- * 注意:由于用户-部门关系通常集成在用户管理中,此实现为占位符。 - * 分公司可以根据实际情况: - * 1. 自定义实现此接口,直接操作本地数据库 - * 2. 或者通过用户管理 API 间接处理关联关系 + * 如果分公司需要自定义实现,可以创建自己的 UserDeptSyncService Bean, + * 此默认实现会自动失效(@ConditionalOnMissingBean) * * @author ZT */ @Slf4j @Service @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 { + @Autowired(required = false) + private UserDeptApi userDeptApi; // Feign 远程调用接口 + @Override public void create(DatabusUserDeptData data) { - log.info("[UserDeptSync] 收到创建用户-部门关系请求, userId={}, deptId={}", - data.getUserId(), data.getDeptId()); - log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法,通过本地 API 或直接数据库操作完成同步 + if (userDeptApi == null) { + log.warn("[UserDeptSync] UserDeptApi未注入,跳过创建用户-部门关系, userId={}", data.getUserId()); + return; + } + UserDeptSaveReqDTO dto = buildUserDeptDTO(data); + userDeptApi.createUserDept(dto).checkError(); + log.info("[UserDeptSync] 用户-部门关系创建成功, userId={}, deptId={}", data.getUserId(), data.getDeptId()); } @Override public void update(DatabusUserDeptData data) { - log.info("[UserDeptSync] 收到更新用户-部门关系请求, userId={}, deptId={}", - data.getUserId(), data.getDeptId()); - log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法 + if (userDeptApi == null) { + log.warn("[UserDeptSync] UserDeptApi未注入,跳过更新用户-部门关系, userId={}", data.getUserId()); + return; + } + UserDeptSaveReqDTO dto = buildUserDeptDTO(data); + userDeptApi.updateUserDept(dto).checkError(); + log.info("[UserDeptSync] 用户-部门关系更新成功, userId={}, deptId={}", data.getUserId(), data.getDeptId()); } @Override public void delete(Long id) { - log.info("[UserDeptSync] 收到删除用户-部门关系请求, id={}", id); - log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法 + if (userDeptApi == null) { + log.warn("[UserDeptSync] UserDeptApi未注入,跳过删除用户-部门关系, id={}", id); + return; + } + userDeptApi.deleteUserDept(id).checkError(); + log.info("[UserDeptSync] 用户-部门关系删除成功, id={}", id); } @Override public void fullSync(DatabusUserDeptData data) { - log.info("[UserDeptSync] 收到全量同步用户-部门关系请求, userId={}, deptId={}", - data.getUserId(), data.getDeptId()); - log.warn("[UserDeptSync] 用户-部门关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法,逻辑:存在则更新,不存在则插入 + if (userDeptApi == null) { + log.warn("[UserDeptSync] UserDeptApi未注入,跳过全量同步用户-部门关系, userId={}", data.getUserId()); + return; + } + 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; } } diff --git a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userpost/UserPostSyncServiceImpl.java b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userpost/UserPostSyncServiceImpl.java index b41d6ac8..47251092 100644 --- a/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userpost/UserPostSyncServiceImpl.java +++ b/zt-framework/zt-spring-boot-starter-databus-client/src/main/java/com/zt/plat/framework/databus/client/handler/userpost/UserPostSyncServiceImpl.java @@ -1,56 +1,93 @@ package com.zt.plat.framework.databus.client.handler.userpost; 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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; /** - * 用户-岗位关系同步服务实现 + * 用户-岗位关系同步服务实现(通过 Feign API 调用远程服务) *

* 使用条件: * 1. zt.databus.sync.client.enabled=true + * 2. 系统中存在 UserPostApi 接口(Feign 客户端) *

- * 注意:由于用户-岗位关系通常集成在用户管理中,此实现为占位符。 - * 分公司可以根据实际情况: - * 1. 自定义实现此接口,直接操作本地数据库 - * 2. 或者通过用户管理 API 间接处理关联关系 + * 如果分公司需要自定义实现,可以创建自己的 UserPostSyncService Bean, + * 此默认实现会自动失效(@ConditionalOnMissingBean) * * @author ZT */ @Slf4j @Service @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 { + @Autowired(required = false) + private UserPostApi userPostApi; // Feign 远程调用接口 + @Override public void create(DatabusUserPostData data) { - log.info("[UserPostSync] 收到创建用户-岗位关系请求, userId={}, postId={}", - data.getUserId(), data.getPostId()); - log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法,通过本地 API 或直接数据库操作完成同步 + if (userPostApi == null) { + log.warn("[UserPostSync] UserPostApi未注入,跳过创建用户-岗位关系, userId={}", data.getUserId()); + return; + } + UserPostSaveReqDTO dto = buildUserPostDTO(data); + userPostApi.createUserPost(dto).checkError(); + log.info("[UserPostSync] 用户-岗位关系创建成功, userId={}, postId={}", data.getUserId(), data.getPostId()); } @Override public void update(DatabusUserPostData data) { - log.info("[UserPostSync] 收到更新用户-岗位关系请求, userId={}, postId={}", - data.getUserId(), data.getPostId()); - log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法 + if (userPostApi == null) { + log.warn("[UserPostSync] UserPostApi未注入,跳过更新用户-岗位关系, userId={}", data.getUserId()); + return; + } + UserPostSaveReqDTO dto = buildUserPostDTO(data); + userPostApi.updateUserPost(dto).checkError(); + log.info("[UserPostSync] 用户-岗位关系更新成功, userId={}, postId={}", data.getUserId(), data.getPostId()); } @Override public void delete(Long id) { - log.info("[UserPostSync] 收到删除用户-岗位关系请求, id={}", id); - log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法 + if (userPostApi == null) { + log.warn("[UserPostSync] UserPostApi未注入,跳过删除用户-岗位关系, id={}", id); + return; + } + userPostApi.deleteUserPost(id).checkError(); + log.info("[UserPostSync] 用户-岗位关系删除成功, id={}", id); } @Override public void fullSync(DatabusUserPostData data) { - log.info("[UserPostSync] 收到全量同步用户-岗位关系请求, userId={}, postId={}", - data.getUserId(), data.getPostId()); - log.warn("[UserPostSync] 用户-岗位关系同步服务需要分公司自定义实现,当前为占位符实现"); - // TODO: 分公司需要实现此方法,逻辑:存在则更新,不存在则插入 + if (userPostApi == null) { + log.warn("[UserPostSync] UserPostApi未注入,跳过全量同步用户-岗位关系, userId={}", data.getUserId()); + return; + } + 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; } } diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusUserChangeConsumer.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusUserChangeConsumer.java index dd3a52e0..43fd8e97 100644 --- a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusUserChangeConsumer.java +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/consumer/DatabusUserChangeConsumer.java @@ -34,7 +34,15 @@ public class DatabusUserChangeConsumer implements RocketMQListener dataMap = new HashMap<>(); diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDeptDataFeignProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDeptDataFeignProvider.java new file mode 100644 index 00000000..81f3bcf2 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserDeptDataFeignProvider.java @@ -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 { + + 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 getPageByCursor(LocalDateTime cursorTime, Long cursorId, + int batchSize, Long tenantId) { + CursorPageReqDTO reqDTO = CursorPageReqDTO.builder() + .cursorTime(cursorTime) + .cursorId(cursorId) + .batchSize(batchSize) + .tenantId(tenantId) + .build(); + + CommonResult> result = userDeptProviderApi.getPageByCursor(reqDTO); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户-部门关系数据失败: " + result.getMsg()); + } + + CursorPageResult 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 result = userDeptProviderApi.count(tenantId); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户-部门关系总数失败: " + result.getMsg()); + } + return result.getData(); + } + + @Override + public Long extractUid(DatabusUserDeptData data) { + return data.getId(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserPostDataFeignProvider.java b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserPostDataFeignProvider.java new file mode 100644 index 00000000..b7df241a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-databus-server/src/main/java/com/zt/plat/framework/databus/server/provider/UserPostDataFeignProvider.java @@ -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 { + + 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 getPageByCursor(LocalDateTime cursorTime, Long cursorId, + int batchSize, Long tenantId) { + CursorPageReqDTO reqDTO = CursorPageReqDTO.builder() + .cursorTime(cursorTime) + .cursorId(cursorId) + .batchSize(batchSize) + .tenantId(tenantId) + .build(); + + CommonResult> result = userPostProviderApi.getPageByCursor(reqDTO); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户-岗位关系数据失败: " + result.getMsg()); + } + + CursorPageResult 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 result = userPostProviderApi.count(tenantId); + if (!result.isSuccess()) { + throw new RuntimeException("获取用户-岗位关系总数失败: " + result.getMsg()); + } + return result.getData(); + } + + @Override + public Long extractUid(DatabusUserPostData data) { + return data.getId(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-seata-dm/pom.xml b/zt-framework/zt-spring-boot-starter-seata-dm/pom.xml new file mode 100644 index 00000000..49d7b1e2 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-seata-dm/pom.xml @@ -0,0 +1,32 @@ + + + + zt-framework + com.zt.plat + ${revision} + + 4.0.0 + jar + + zt-spring-boot-starter-seata-dm + + ${project.artifactId} + + Seata 达梦数据库补丁模块 + 解决 DmdbTimestamp 时区格式不一致导致的 dirty undo log 回滚失败问题 + 补丁来源: https://github.com/apache/incubator-seata/pull/7538 + Seata 2.6.0 发布后可移除此模块 + + + + + + org.apache.seata + seata-spring-boot-starter + provided + + + + diff --git a/zt-framework/zt-spring-boot-starter-seata-dm/src/main/java/org/apache/seata/rm/datasource/DataCompareUtils.java b/zt-framework/zt-spring-boot-starter-seata-dm/src/main/java/org/apache/seata/rm/datasource/DataCompareUtils.java new file mode 100644 index 00000000..24c15d5a --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-seata-dm/src/main/java/org/apache/seata/rm/datasource/DataCompareUtils.java @@ -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 时区问题的补丁 + *

+ * 此类覆盖 Seata 原有的 DataCompareUtils,添加了对达梦数据库 DmdbTimestamp 类型的特殊处理。 + * 通过将 DmdbTimestamp 转换为 UTC Instant 进行比较,解决时区格式不一致导致的 dirty undo log 问题。 + *

+ * 问题背景: + * - 达梦数据库的 DmdbTimestamp 类型在序列化/反序列化后时区格式不一致 + * - 例如:beforeImage 为 "2025-12-25 09:38:54.077811 +08:00" + * afterImage 为 "2025-12-25 09:38:54.077811" + * - 导致 Seata AT 模式回滚时 dirty undo log 检查失败 + *

+ * 解决方案: + * - 当检测到 DmdbTimestamp 类型时,将两个值都转换为 UTC Instant 进行比较 + * - 这样可以忽略时区格式差异,只比较实际的时间点 + *

+ * 补丁来源: 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 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 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 isRowsEquals(TableMeta tableMetaData, List oldRows, List newRows) { + if (!CollectionUtils.isSizeEquals(oldRows, newRows)) { + return Result.build(false, null); + } + return compareRows(tableMetaData, oldRows, newRows); + } + + private static Result compareRows(TableMeta tableMetaData, List oldRows, List newRows) { + // old row to map + Map> oldRowsMap = rowListToMap(oldRows, tableMetaData.getPrimaryKeyOnlyName()); + // new row to map + Map> newRowsMap = rowListToMap(newRows, tableMetaData.getPrimaryKeyOnlyName()); + // compare data + for (Map.Entry> oldEntry : oldRowsMap.entrySet()) { + String key = oldEntry.getKey(); + Map oldRow = oldEntry.getValue(); + Map newRow = newRowsMap.get(key); + if (newRow == null) { + return Result.buildWithParams(false, "compare row failed, rowKey {}, reason [newRow is null]", key); + } + for (Map.Entry 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 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> rowListToMap(List rowList, List primaryKeyList) { + // {value of primaryKey, value of all columns} + Map> rowMap = new HashMap<>(); + for (Row row : rowList) { + // ensure the order of column + List rowFieldList = row.getFields().stream() + .sorted(Comparator.comparing(Field::getName)) + .collect(Collectors.toList()); + // {uppercase fieldName : field} + Map 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); + } + } +} diff --git a/zt-gateway/src/main/resources/application.yaml b/zt-gateway/src/main/resources/application.yaml index a9cfc07c..3426a39a 100644 --- a/zt-gateway/src/main/resources/application.yaml +++ b/zt-gateway/src/main/resources/application.yaml @@ -30,6 +30,8 @@ spring: username: ${config.username} # Nacos 账号 password: ${config.password} # Nacos 密码 discovery: # 【配置中心】配置项 + ip: 172.16.46.62 + port: 30092 namespace: ${config.namespace} # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 group: ${config.group} # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP metadata: diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java index cabc1fd1..dcc06fc9 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -56,6 +56,10 @@ public class BpmModelMetaInfoVO { @NotNull(message = "是否可见不能为空") private Boolean visible; + @Schema(description = "是否允许重新发起", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否允许重新发起不能为空") + private Boolean restart; + @Schema(description = "可发起用户编号数组", example = "[1,2,3]") private List startUserIds; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java index e0b32f3c..63ef57d5 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -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.process.BpmProcessDefinitionRespVO; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.time.LocalDateTime; diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java index 905e6166..6d573413 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java @@ -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 jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; @Schema(description = "管理后台 - 流程模型的保存 Request VO") diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java index 53ea6faf..d55f27a0 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -1,5 +1,6 @@ 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.util.StrUtil; 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.dal.dataobject.definition.BpmCategoryDO; 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.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.BpmTaskService; 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 jakarta.annotation.Resource; 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.repository.ProcessDefinition; 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.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,6 +60,8 @@ public class BpmProcessInstanceController { private BpmProcessDefinitionService processDefinitionService; @Resource private BpmCategoryService categoryService; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; @Resource private AdminUserApi adminUserApi; @@ -181,6 +190,32 @@ public class BpmProcessInstanceController { 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> getCopyListByProcessInstanceId(@RequestParam("processInstanceId") String processInstanceId) { + List copyDOList = processInstanceCopyService.getByProcessInstanceId(processInstanceId); + if (CollectionUtils.isEmpty(copyDOList)) { + return success(new ArrayList<>(0)); + } + List copyVOList = new ArrayList<>(copyDOList.size()); + SetUniqueList 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 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") @Operation(summary = "获取下一个执行的流程节点") @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java new file mode 100644 index 00000000..0012c190 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyVO.java @@ -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; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java index 791e6949..87652f87 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -87,9 +87,16 @@ public interface BpmProcessInstanceConvert { }); } } - // 摘要 - respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()), - pageResult.getList().get(i).getProcessVariables())); + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfoMap.get(respVO.getProcessDefinitionId()); + if (processDefinitionInfo != null) { + // 摘要 + 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()); } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index b65c192b..43a879b4 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -129,6 +129,13 @@ public class BpmProcessDefinitionInfoDO extends BaseDO { * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 */ private Boolean visible; + + /** + * 是否允许重新发起 + * + * 目的:如果 false 则不可以重新发起流程 + */ + private Boolean restart; /** * 排序值 */ diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java index 61a73c3c..b35d217d 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java @@ -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 org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface BpmProcessInstanceCopyMapper extends BaseMapperX { @@ -22,4 +24,8 @@ public interface BpmProcessInstanceCopyMapper extends BaseMapperX getByProcessInstanceId(String processInstanceId) { + return selectList(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId); + } + } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java index e5f06b6f..76350bc8 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotEmpty; import org.flowable.bpmn.model.FlowNode; import java.util.Collection; +import java.util.List; /** * 流程抄送 Service 接口 @@ -57,4 +58,12 @@ public interface BpmProcessInstanceCopyService { */ void deleteProcessInstanceCopy(String processInstanceId); + /** + * 获得流程的抄送列表 + * + * @param processInstanceId 流程实例 ID + * @return 抄送流程列表 + */ + List getByProcessInstanceId(String processInstanceId); + } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index dd842b9f..5fec3947 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -93,4 +93,9 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy processInstanceCopyMapper.deleteByProcessInstanceId(processInstanceId); } + @Override + public List getByProcessInstanceId(String processInstanceId) { + return processInstanceCopyMapper.getByProcessInstanceId(processInstanceId); + } + } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml index 0e551414..51a9a489 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml +++ b/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml @@ -73,4 +73,8 @@ + + + + diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserDeptProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserDeptProviderApi.java new file mode 100644 index 00000000..5e393626 --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserDeptProviderApi.java @@ -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 + *

+ * 供 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> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO); + + /** + * 根据ID查询用户-部门关系详情(用于增量同步) + * + * @param id 关系ID + * @return 用户-部门关系数据 + */ + @GetMapping(PREFIX + "/get") + @Operation(summary = "查询用户-部门关系详情") + @Parameter(name = "id", description = "关系ID", required = true, example = "1001") + CommonResult 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> getListByIds(@RequestParam("ids") List ids); + + /** + * 统计用户-部门关系总数(用于全量同步进度计算) + * + * @param tenantId 租户ID(可选) + * @return 用户-部门关系总数 + */ + @GetMapping(PREFIX + "/count") + @Operation(summary = "统计用户-部门关系总数") + @Parameter(name = "tenantId", description = "租户ID", example = "1") + CommonResult count(@RequestParam(value = "tenantId", required = false) Long tenantId); + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserPostProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserPostProviderApi.java new file mode 100644 index 00000000..6e00365d --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusUserPostProviderApi.java @@ -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 + *

+ * 供 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> getPageByCursor(@RequestBody CursorPageReqDTO reqDTO); + + /** + * 根据ID查询用户-岗位关系详情(用于增量同步) + * + * @param id 关系ID + * @return 用户-岗位关系数据 + */ + @GetMapping(PREFIX + "/get") + @Operation(summary = "查询用户-岗位关系详情") + @Parameter(name = "id", description = "关系ID", required = true, example = "1001") + CommonResult 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> getListByIds(@RequestParam("ids") List ids); + + /** + * 统计用户-岗位关系总数(用于全量同步进度计算) + * + * @param tenantId 租户ID(可选) + * @return 用户-岗位关系总数 + */ + @GetMapping(PREFIX + "/count") + @Operation(summary = "统计用户-岗位关系总数") + @Parameter(name = "tenantId", description = "租户ID", example = "1") + CommonResult count(@RequestParam(value = "tenantId", required = false) Long tenantId); + +} diff --git a/zt-module-databus/zt-module-databus-server/pom.xml b/zt-module-databus/zt-module-databus-server/pom.xml index e94168ad..ceec001c 100644 --- a/zt-module-databus/zt-module-databus-server/pom.xml +++ b/zt-module-databus/zt-module-databus-server/pom.xml @@ -185,6 +185,11 @@ 4.12.0 test + + com.zt.plat + zt-spring-boot-starter-databus-client + ${revision} + diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java index 48c2a627..f9e5754d 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java @@ -33,6 +33,7 @@ import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; +import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -304,15 +305,28 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { .build() .getQueryParams(); 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; } if (CollectionUtils.isEmpty(values)) { - target.put(key, ""); - } else if (values.size() == 1) { - target.put(key, values.get(0)); + target.put(decodedKey, ""); + return; + } + // 对每一个 value 做 URL 解码,确保与客户端原文签名一致 + List 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 { - target.put(key, String.join(",", values)); + target.put(decodedKey, String.join(",", decodedValues)); } }); } catch (IllegalArgumentException ex) { diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java index 32e911e1..d79beb52 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/rpc/config/RpcConfiguration.java @@ -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.DatabusPostProviderApi; 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.PostApi; 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.context.annotation.Configuration; @@ -21,9 +25,12 @@ import org.springframework.context.annotation.Configuration; DatabusDeptProviderApi.class, DatabusUserProviderApi.class, DatabusPostProviderApi.class, - PostApi.class, - DeptApi.class, - AdminUserApi.class, + DatabusUserDeptProviderApi.class, + DatabusUserPostProviderApi.class, + PostApi.class, + DeptApi.class, + UserDeptApi.class, + UserPostApi.class, }) public class RpcConfiguration { } diff --git a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java index 5bd323d3..af84f587 100644 --- a/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java +++ b/zt-module-databus/zt-module-databus-server/src/test/java/com/zt/plat/module/databus/framework/integration/gateway/sample/DatabusApiInvocationExample.java @@ -5,6 +5,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; 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.PrintStream; import java.net.URI; @@ -23,10 +27,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; 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。 @@ -37,14 +37,14 @@ public final class DatabusApiInvocationExample { // private static final String APP_ID = "iwork"; // private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk="; - private static final String APP_ID = "ztmy"; - private static final String APP_SECRET = "zFre/nTRGi7LpoFjN7oQkKeOT09x1fWTyIswrc702QQ="; + private static final String APP_ID = "jwyw"; + private static final String APP_SECRET = "MhfCcqB59rDTnB5yGOVXWtp/5a0JXir7pSjPl5cVMJ8="; 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.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 = "http://localhost:48080/admin-api/databus/api/portal/callback/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/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/testcbw/456"; // ⚠️ 仅用于联调:信任所有证书 + 关闭主机名校验,生产环境请改为受信 CA 或自定义 truststore。 @@ -102,10 +102,16 @@ public final class DatabusApiInvocationExample { public static void main(String[] args) throws Exception { OUT.println("=== GET 请求示例 ==="); -// executeGetExample(); + executeGetExample(); // OUT.println(); -// OUT.println("=== POST 请求示例 ==="); - executePostExample(); + OUT.println("=== POST 请求示例 ==="); + 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 { @@ -113,9 +119,11 @@ public final class DatabusApiInvocationExample { queryParams.put("businessCode", "11"); queryParams.put("fileId", "11"); queryParams.put("null", null); + queryParams.put("empty", ""); + queryParams.put("taskTimeEnd", "2025-12-28 23:00:00"); String signature = generateSignature(queryParams, Map.of()); URI requestUri = buildUri(TARGET_API, queryParams); - String nonce = "171615676c7d4d96b9f55f3d90ad27e0"; + String nonce = randomNonce(); HttpRequest request = HttpRequest.newBuilder(requestUri) .timeout(Duration.ofSeconds(10)) @@ -131,16 +139,14 @@ public final class DatabusApiInvocationExample { printResponse(response); } - private static void executePostExample() throws Exception { + private static void executePostExample(String json) throws Exception { Map queryParams = new LinkedHashMap<>(); 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"} // """, extraTimestamp); - String bodyJson = String.format(""" - {} - """, extraTimestamp); + String bodyJson = String.format(json, extraTimestamp); Map bodyParams = parseBodyJson(bodyJson); String signature = generateSignature(queryParams, bodyParams); diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/file/FileConfigServiceImpl.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/file/FileConfigServiceImpl.java index f0e41360..f625e2db 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/file/FileConfigServiceImpl.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/service/file/FileConfigServiceImpl.java @@ -2,6 +2,8 @@ package com.zt.plat.module.infra.service.file; import cn.hutool.core.io.resource.ResourceUtil; 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.util.json.JsonUtils; 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.FileClientFactory; 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.validation.Validator; import lombok.Getter; @@ -172,7 +172,7 @@ public class FileConfigServiceImpl implements FileConfigService { // 校验存在 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"); } diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml b/zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml index 13ec6c0a..73104a95 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml +++ b/zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml @@ -32,8 +32,8 @@ spring: servlet: # 文件上传相关配置项 multipart: - max-file-size: 100MB # 单个文件大小 - max-request-size: 120MB # 设置总上传的文件大小 + max-file-size: 128MB # 单个文件大小 + max-request-size: 512MB # 设置总上传的文件大小 # Jackson 配置项 jackson: diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/file/bg1.png b/zt-module-infra/zt-module-infra-server/src/main/resources/file/bg1.png new file mode 100644 index 00000000..fa136983 Binary files /dev/null and b/zt-module-infra/zt-module-infra-server/src/main/resources/file/bg1.png differ diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/file/erweima.jpg b/zt-module-infra/zt-module-infra-server/src/main/resources/file/erweima.jpg deleted file mode 100644 index 1447283c..00000000 Binary files a/zt-module-infra/zt-module-infra-server/src/main/resources/file/erweima.jpg and /dev/null differ diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/ftp/FtpFileClientTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/ftp/FtpFileClientTest.java index 3cf505d1..bb8f3f33 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/ftp/FtpFileClientTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/ftp/FtpFileClientTest.java @@ -41,7 +41,7 @@ public class FtpFileClientTest { client.init(); // 上传文件 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"); System.out.println("访问地址:" + fullPath); if (false) { diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/local/LocalFileClientTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/local/LocalFileClientTest.java index 7d7f3f58..fe246f45 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/local/LocalFileClientTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/local/LocalFileClientTest.java @@ -20,7 +20,7 @@ public class LocalFileClientTest { client.init(); // 上传文件 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"); System.out.println("访问地址:" + fullPath); client.delete(path); diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/s3/S3FileClientTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/s3/S3FileClientTest.java index 40f04f85..8687b731 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/s3/S3FileClientTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/s3/S3FileClientTest.java @@ -101,7 +101,7 @@ public class S3FileClientTest { client.init(); // 上传文件 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"); System.out.println("访问地址:" + fullPath); // 读取文件 diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/sftp/SftpFileClientTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/sftp/SftpFileClientTest.java index 457912e7..0f73cc42 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/sftp/SftpFileClientTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/framework/file/core/sftp/SftpFileClientTest.java @@ -34,7 +34,7 @@ public class SftpFileClientTest { client.init(); // 上传文件 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"); System.out.println("访问地址:" + fullPath); if (false) { diff --git a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileServiceImplTest.java b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileServiceImplTest.java index bbb02810..b72e7adb 100644 --- a/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileServiceImplTest.java +++ b/zt-module-infra/zt-module-infra-server/src/test/java/com/zt/plat/module/infra/service/file/FileServiceImplTest.java @@ -88,7 +88,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { @Test public void testCreateFile_success_01() throws Exception { // 准备参数 - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + byte[] content = ResourceUtil.readBytes("file/bg1.png"); String name = "单测文件名"; String directory = randomString(); String type = "image/jpeg"; @@ -122,7 +122,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { @Test public void testCreateFile_success_02() throws Exception { // 准备参数 - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + byte[] content = ResourceUtil.readBytes("file/bg1.png"); // mock Master 文件客户端 String type = "image/jpeg"; FileClient client = mock(FileClient.class); @@ -318,7 +318,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { @Test public void testCreateFile_withSameHash() throws Exception { // 准备参数 - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + byte[] content = ResourceUtil.readBytes("file/bg1.png"); String name = "单测文件名"; String directory = randomString(); String type = "image/jpeg"; diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java index eea5499e..abc53972 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/DeptApi.java @@ -86,4 +86,10 @@ public interface DeptApi { @Parameter(name = "userId", description = "用户编号", example = "1", required = true) CommonResult> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId); + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步部门") + CommonResult syncDept(@RequestBody DeptSaveReqDTO syncReqDTO); + } diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/PostApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/PostApi.java index fed71544..4ef8bd45 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/PostApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/PostApi.java @@ -64,4 +64,10 @@ public interface PostApi { return CollectionUtils.convertMap(list, PostRespDTO::getId); } + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步岗位") + CommonResult syncPost(@RequestBody PostSaveReqDTO syncReqDTO); + } diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java index fbb311cc..b01a712d 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptSaveReqDTO.java @@ -15,9 +15,15 @@ public class DeptSaveReqDTO { @Schema(description = "部门编号", example = "1024") private Long id; + @Schema(description = "部门编码", example = "ZT001") + private String code; + @Schema(description = "部门名称", example = "ZT") private String name; + @Schema(description = "部门简称", example = "技术") + private String shortName; + @Schema(description = "父部门 ID", example = "1024") private Long parentId; @@ -36,6 +42,15 @@ public class DeptSaveReqDTO { @Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", example = "0") 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") private String externalSystemCode; diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java new file mode 100644 index 00000000..a7c338a5 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java @@ -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 createDept(@RequestBody DeptSaveReqDTO createReqVO); + + @PutMapping(PREFIX + "/update") + @Operation(summary = "修改部门") + CommonResult updateDept(@RequestBody DeptSaveReqDTO updateReqVO); + + @DeleteMapping(PREFIX + "/delete") + @Operation(summary = "删除部门") + CommonResult deleteDept(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/list-all") + @Operation(summary = "获得部门列表") + CommonResult> getDeptList(@RequestBody DeptListReqDTO reqVO); + + @GetMapping(PREFIX + "/simple-list") + @Operation(summary = "获得部门精简信息列表") + CommonResult> getSimpleDeptList(); + + @GetMapping(PREFIX + "/simple-company-list") + @Operation(summary = "获得公司精简信息列表") + CommonResult> getSimpleCompanyList(); + + @GetMapping(PREFIX + "/all-company-list") + @Operation(summary = "获得所有公司精简信息列表") + CommonResult> getAllCompanyList(); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "获得部门信息") + @Parameter(name = "id", description = "部门编号", example = "1024", required = true) + CommonResult getDept(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list") + @Operation(summary = "获得部门信息数组") + @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) + CommonResult> getDeptList(@RequestParam("ids") Collection ids); + + @GetMapping(PREFIX + "/valid") + @Operation(summary = "校验部门是否合法") + @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) + CommonResult validateDeptList(@RequestParam("ids") Collection ids); + + /** + * 获得指定编号的部门 Map + * + * @param ids 部门编号数组 + * @return 部门 Map + */ + default Map getDeptMap(Collection ids) { + List 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> getChildDeptList(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/company-dept-info") + @Operation(summary = "获得指定用户的公司部门信息") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult> getCompanyDeptInfoListByUserId(@RequestParam("userId") Long userId); + + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步部门") + CommonResult syncDept(@RequestBody DeptSaveReqDTO syncReqDTO); + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java new file mode 100644 index 00000000..d69e1ab2 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/dto/EspDto.java @@ -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 ids; + + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java index 9e541926..b30f62f6 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java @@ -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.module.system.api.permission.dto.*; 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.Parameter; import io.swagger.v3.oas.annotations.Operation; @@ -50,4 +51,9 @@ public interface PermissionApi extends PermissionCommonApi { @Parameter(name = "userId", description = "用户编号", example = "1", required = true) CommonResult> getUserRoleIdListByUserId(@RequestParam("userId") Long userId); + @GetMapping(PREFIX + "/user-data-permission-level") + @Operation(summary = "获得用户的数据权限级别") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult getUserDataPermissionLevel(@RequestParam("userId") Long userId); + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/AdminUserApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/AdminUserApi.java index 9831e89f..31a7a5ee 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/AdminUserApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/AdminUserApi.java @@ -104,6 +104,12 @@ public interface AdminUserApi extends AutoTransable { @Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true) CommonResult validateUserList(@RequestParam("ids") Collection ids); + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步用户") + CommonResult syncUser(@RequestBody AdminUserSaveReqDTO syncReqDTO); + @Override @FeignIgnore default List selectByIds(List ids) { diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/dto/AdminUserSaveReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/dto/AdminUserSaveReqDTO.java index 4e4a30c2..691f5204 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/dto/AdminUserSaveReqDTO.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/user/dto/AdminUserSaveReqDTO.java @@ -50,4 +50,10 @@ public class AdminUserSaveReqDTO { @Schema(description = "密码", example = "123456") private String password; + @Schema(description = "工号", example = "A00123") + private String workcode; + + @Schema(description = "用户来源类型", example = "1") + private Integer userSource; + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApi.java new file mode 100644 index 00000000..050216ae --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApi.java @@ -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 createUserDept(@RequestBody UserDeptSaveReqDTO reqVO); + + @PutMapping(PREFIX + "/update") + @Operation(summary = "修改用户部门关系") + CommonResult updateUserDept(@RequestBody UserDeptSaveReqDTO reqVO); + + @DeleteMapping(PREFIX + "/delete") + @Operation(summary = "删除用户部门关系") + @Parameter(name = "id", description = "关系编号", example = "1", required = true) + CommonResult deleteUserDept(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "通过ID查询用户部门关系") + @Parameter(name = "id", description = "关系编号", example = "1", required = true) + CommonResult getUserDept(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list-by-user-id") + @Operation(summary = "通过用户ID查询用户部门关系列表") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult> getUserDeptListByUserId(@RequestParam("userId") Long userId); + + @GetMapping(PREFIX + "/list-by-dept-id") + @Operation(summary = "通过部门ID查询用户部门关系列表") + @Parameter(name = "deptId", description = "部门编号", example = "1", required = true) + CommonResult> getUserDeptListByDeptId(@RequestParam("deptId") Long deptId); + + @DeleteMapping(PREFIX + "/delete-by-user-id") + @Operation(summary = "通过用户ID删除用户部门关系") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult deleteUserDeptByUserId(@RequestParam("userId") Long userId); + + @DeleteMapping(PREFIX + "/delete-by-dept-id") + @Operation(summary = "通过部门ID删除用户部门关系") + @Parameter(name = "deptId", description = "部门编号", example = "1", required = true) + CommonResult deleteUserDeptByDeptId(@RequestParam("deptId") Long deptId); + + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步用户部门关系") + CommonResult syncUserDept(@RequestBody UserDeptSaveReqDTO syncReqDTO); +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptRespDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptRespDTO.java new file mode 100644 index 00000000..5a558b90 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptRespDTO.java @@ -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; +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptSaveReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptSaveReqDTO.java new file mode 100644 index 00000000..b18f2a56 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userdept/dto/UserDeptSaveReqDTO.java @@ -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; +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApi.java new file mode 100644 index 00000000..d72e4706 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApi.java @@ -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 createUserPost(@RequestBody UserPostSaveReqDTO reqVO); + + @PutMapping(PREFIX + "/update") + @Operation(summary = "修改用户岗位关系") + CommonResult updateUserPost(@RequestBody UserPostSaveReqDTO reqVO); + + @DeleteMapping(PREFIX + "/delete") + @Operation(summary = "删除用户岗位关系") + @Parameter(name = "id", description = "关系编号", example = "1", required = true) + CommonResult deleteUserPost(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/get") + @Operation(summary = "通过ID查询用户岗位关系") + @Parameter(name = "id", description = "关系编号", example = "1", required = true) + CommonResult getUserPost(@RequestParam("id") Long id); + + @GetMapping(PREFIX + "/list-by-user-id") + @Operation(summary = "通过用户ID查询用户岗位关系列表") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult> getUserPostListByUserId(@RequestParam("userId") Long userId); + + @GetMapping(PREFIX + "/list-by-post-id") + @Operation(summary = "通过岗位ID查询用户岗位关系列表") + @Parameter(name = "postId", description = "岗位编号", example = "1", required = true) + CommonResult> getUserPostListByPostId(@RequestParam("postId") Long postId); + + @DeleteMapping(PREFIX + "/delete-by-user-id") + @Operation(summary = "通过用户ID删除用户岗位关系") + @Parameter(name = "userId", description = "用户编号", example = "1", required = true) + CommonResult deleteUserPostByUserId(@RequestParam("userId") Long userId); + + @DeleteMapping(PREFIX + "/delete-by-post-id") + @Operation(summary = "通过岗位ID删除用户岗位关系") + @Parameter(name = "postId", description = "岗位编号", example = "1", required = true) + CommonResult deleteUserPostByPostId(@RequestParam("postId") Long postId); + + // ========== 数据同步专用接口 ========== + + @PostMapping(PREFIX + "/sync") + @Operation(summary = "同步用户岗位关系") + CommonResult syncUserPost(@RequestBody UserPostSaveReqDTO syncReqDTO); +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostRespDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostRespDTO.java new file mode 100644 index 00000000..e7251a61 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostRespDTO.java @@ -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; +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostSaveReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostSaveReqDTO.java new file mode 100644 index 00000000..4ad5af68 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/userpost/dto/UserPostSaveReqDTO.java @@ -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; +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java index 8178c53e..fd2aee37 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java @@ -127,8 +127,8 @@ public interface ErrorCodeConstants { ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期"); 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_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); + 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, "短信发送过于频繁,请于{}分钟后再试"); // ========== 租户信息 1-002-015-000 ========== ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在"); diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java index b0edcfd0..4a1ab13f 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataScopeEnum.java @@ -1,10 +1,12 @@ package com.zt.plat.module.system.enums.permission; +import com.fasterxml.jackson.annotation.JsonValue; import com.zt.plat.framework.common.core.ArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; +import java.util.Objects; /** * 数据范围枚举类 @@ -33,6 +35,26 @@ public enum DataScopeEnum implements ArrayValuable { 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 public Integer[] array() { return ARRAYS; diff --git a/zt-module-system/zt-module-system-server/pom.xml b/zt-module-system/zt-module-system-server/pom.xml index 71cc87f0..b20de884 100644 --- a/zt-module-system/zt-module-system-server/pom.xml +++ b/zt-module-system/zt-module-system-server/pom.xml @@ -137,7 +137,12 @@ com.zt.plat zt-spring-boot-starter-monitor - + + + com.zt.plat + zt-spring-boot-starter-seata-dm + ${revision} + org.apache.seata @@ -190,6 +195,10 @@ com.zt.plat zt-spring-boot-starter-mq + + cn.hutool + hutool-all + diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserDeptProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserDeptProviderApiImpl.java new file mode 100644 index 00000000..efbe2e1d --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserDeptProviderApiImpl.java @@ -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> getPageByCursor(CursorPageReqDTO reqDTO) { + // 多查一条判断是否有更多数据 + int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100; + + // 查询用户部门关系 + List 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 dataList = list.stream() + .map(this::convertToData) + .collect(Collectors.toList()); + + // 获取最后一条数据的游标 + UserDeptDO last = list.get(list.size() - 1); + + // 首次查询时返���总数 + 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 getById(Long id) { + UserDeptDO userDept = userDeptMapper.selectById(id); + if (userDept == null) { + return success(null); + } + return success(convertToData(userDept)); + } + + @Override + public CommonResult> getListByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return success(Collections.emptyList()); + } + + List 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 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(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserPostProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserPostProviderApiImpl.java new file mode 100644 index 00000000..faabe471 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserPostProviderApiImpl.java @@ -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> getPageByCursor(CursorPageReqDTO reqDTO) { + // 多查一条判断是否有更多数据 + int limit = reqDTO.getBatchSize() != null ? reqDTO.getBatchSize() : 100; + + // ⚠️ 使用关联查询,只查询 userSource = 2 的用户的岗位关系 + List 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 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 getById(Long id) { + UserPostDO userPost = userPostMapper.selectById(id); + if (userPost == null) { + return success(null); + } + return success(convertToData(userPost)); + } + + @Override + public CommonResult> getListByIds(List ids) { + if (CollUtil.isEmpty(ids)) { + return success(Collections.emptyList()); + } + + List 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 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(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java index 286b9984..f6b9050a 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java @@ -54,6 +54,9 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { // 构建游标查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // ⚠️ 只同步 userSource = 2 的用户 + queryWrapper.eq(AdminUserDO::getUserSource, 2); + // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) if (!reqDTO.isFirstPage()) { queryWrapper.and(w -> w @@ -100,6 +103,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { Long total = null; if (reqDTO.isFirstPage()) { LambdaQueryWrapper countWrapper = new LambdaQueryWrapper<>(); + // ⚠️ 只统计 userSource = 2 的用户 + countWrapper.eq(AdminUserDO::getUserSource, 2); if (reqDTO.getTenantId() != null) { countWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId()); } @@ -143,6 +148,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { @Override public CommonResult count(Long tenantId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // ⚠️ 只统计 userSource = 2 的用户 + queryWrapper.eq(AdminUserDO::getUserSource, 2); if (tenantId != null) { queryWrapper.eq(AdminUserDO::getTenantId, tenantId); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java index d06b79a6..bed2d2b6 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/DeptApiImpl.java @@ -107,4 +107,13 @@ public class DeptApiImpl implements DeptApi { return success(BeanUtils.toBean(companyDeptInfos, CompanyDeptInfoRespDTO.class)); } + // ========== 数据同步专用接口 ========== + + @Override + public CommonResult syncDept(DeptSaveReqDTO syncReqDTO) { + DeptSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, DeptSaveReqVO.class); + deptService.syncDept(reqVO); + return success(true); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java index 40c7e22b..bb760ec4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/dept/PostApiImpl.java @@ -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.service.dept.PostService; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; 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; +@Slf4j @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class PostApiImpl implements PostApi { @@ -49,6 +51,7 @@ public class PostApiImpl implements PostApi { @Override public CommonResult getPost(Long id) { + log.error("cccccccc"+id); PostDO post = postService.getPost(id); return success(BeanUtils.toBean(post, PostRespDTO.class)); } @@ -72,4 +75,11 @@ public class PostApiImpl implements PostApi { return success(BeanUtils.toBean(list, PostRespDTO.class)); } + @Override + public CommonResult syncPost(PostSaveReqDTO syncReqDTO) { + PostSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, PostSaveReqVO.class); + postService.syncPost(reqVO); + return success(true); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java index 955fa69a..771f322b 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java @@ -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.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO; 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 org.springframework.context.annotation.Primary; import org.springframework.validation.annotation.Validated; @@ -65,6 +66,11 @@ public class PermissionApiImpl implements PermissionApi { return success(permissionService.getUserRoleIdListByUserIdFromCache(userId)); } + @Override + public CommonResult getUserDataPermissionLevel(Long userId) { + return success(permissionService.getUserDataPermissionLevel(userId)); + } + @Override public CommonResult hasAnyPermissions(Long userId, String... permissions) { return success(permissionService.hasAnyPermissions(userId, permissions)); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/user/AdminUserApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/user/AdminUserApiImpl.java index a80db06e..523e432a 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/user/AdminUserApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/user/AdminUserApiImpl.java @@ -149,4 +149,11 @@ public class AdminUserApiImpl implements AdminUserApi { return success(true); } + @Override + public CommonResult syncUser(AdminUserSaveReqDTO syncReqDTO) { + UserSaveReqVO reqVO = BeanUtils.toBean(syncReqDTO, UserSaveReqVO.class); + userService.syncUser(reqVO); + return success(true); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApiImpl.java new file mode 100644 index 00000000..91b30187 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userdept/UserDeptApiImpl.java @@ -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 createUserDept(UserDeptSaveReqDTO reqVO) { + UserDeptDO userDept = BeanUtils.toBean(reqVO, UserDeptDO.class); + Long id = userDeptService.createUserDept(userDept); + return success(id); + } + + @Override + public CommonResult updateUserDept(UserDeptSaveReqDTO reqVO) { + UserDeptDO userDept = BeanUtils.toBean(reqVO, UserDeptDO.class); + userDeptService.updateUserDept(userDept); + return success(true); + } + + @Override + public CommonResult deleteUserDept(Long id) { + userDeptService.deleteUserDept(id); + return success(true); + } + + @Override + public CommonResult getUserDept(Long id) { + UserDeptDO userDept = userDeptService.getUserDept(id); + return success(BeanUtils.toBean(userDept, UserDeptRespDTO.class)); + } + + @Override + public CommonResult> getUserDeptListByUserId(Long userId) { + List list = userDeptService.getValidUserDeptListByUserIds(List.of(userId)); + return success(BeanUtils.toBean(list, UserDeptRespDTO.class)); + } + + @Override + public CommonResult> getUserDeptListByDeptId(Long deptId) { + List list = userDeptService.getValidUserDeptListByDeptIds(List.of(deptId)); + return success(BeanUtils.toBean(list, UserDeptRespDTO.class)); + } + + @Override + public CommonResult deleteUserDeptByUserId(Long userId) { + userDeptService.deleteUserDeptByUserId(userId); + return success(true); + } + + @Override + public CommonResult deleteUserDeptByDeptId(Long deptId) { + // 需要实现此方法,暂时返回成功 + return success(true); + } + + @Override + public CommonResult syncUserDept(UserDeptSaveReqDTO syncReqDTO) { + UserDeptDO userDept = BeanUtils.toBean(syncReqDTO, UserDeptDO.class); + userDeptService.syncUserDept(userDept); + return success(true); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApiImpl.java new file mode 100644 index 00000000..1ee2db26 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/userpost/UserPostApiImpl.java @@ -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 createUserPost(UserPostSaveReqDTO reqVO) { + UserPostDO userPost = BeanUtils.toBean(reqVO, UserPostDO.class); + userPostMapper.insert(userPost); + return success(userPost.getId()); + } + + @Override + public CommonResult updateUserPost(UserPostSaveReqDTO reqVO) { + UserPostDO userPost = BeanUtils.toBean(reqVO, UserPostDO.class); + userPostMapper.updateById(userPost); + return success(true); + } + + @Override + public CommonResult deleteUserPost(Long id) { + userPostMapper.deleteById(id); + return success(true); + } + + @Override + public CommonResult getUserPost(Long id) { + UserPostDO userPost = userPostMapper.selectById(id); + return success(BeanUtils.toBean(userPost, UserPostRespDTO.class)); + } + + @Override + public CommonResult> getUserPostListByUserId(Long userId) { + List list = userPostMapper.selectListByUserId(userId); + return success(BeanUtils.toBean(list, UserPostRespDTO.class)); + } + + @Override + public CommonResult> getUserPostListByPostId(Long postId) { + List list = userPostMapper.selectListByPostIds(List.of(postId)); + return success(BeanUtils.toBean(list, UserPostRespDTO.class)); + } + + @Override + public CommonResult deleteUserPostByUserId(Long userId) { + userPostMapper.deleteByUserId(userId); + return success(true); + } + + @Override + public CommonResult deleteUserPostByPostId(Long postId) { + userPostMapper.delete(UserPostDO::getPostId, postId); + return success(true); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public CommonResult 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); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java index 510eb674..ca3fb848 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/AuthController.java @@ -143,6 +143,7 @@ public class AuthController { @PostMapping("/sms-login") @PermitAll + @TenantIgnore @Operation(summary = "使用短信验证码登录") public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { return success(authService.smsLogin(reqVO)); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java new file mode 100644 index 00000000..8ae5afa5 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/EspController.java @@ -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 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 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 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 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> page(@Valid EspPageReqVO reqVO) { + PageResult pageResult = espService.getDeptExternalCodePage(reqVO); + PageResult 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> listByDept(@RequestParam("deptId") Long deptId) { + List list = espService.getPushMsgByDeptId(deptId); + List respList = BeanUtils.toBean(list, EspSaveRespVo.class); + fillDeptInfo(respList); + return success(respList); + } + + + + private void fillDeptInfo(List list) { + if (list == null || list.isEmpty()) { + return; + } + Set deptIds = list.stream() + .map(EspSaveRespVo::getDeptId) + .collect(Collectors.toCollection(HashSet::new)); + if (deptIds == null || deptIds.isEmpty()) { + return; + } + Map deptMap = deptService.getDeptList(deptIds).stream() + .collect(Collectors.toMap(DeptDO::getId, dept -> dept, (left, right) -> left)); + list.forEach(item -> { + DeptDO dept = deptMap.get(item.getDeptId()); + if (dept != null) { + item.setDeptName(dept.getName()); + item.setDeptCode(dept.getCode()); + } + }); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java new file mode 100644 index 00000000..10e0ab8b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspPageReqVO.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 部门外部组织编码映射分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class 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; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java new file mode 100644 index 00000000..859ba772 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/vo/depexternalcode/EspSaveRespVo.java @@ -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; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java new file mode 100644 index 00000000..237c28bc --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptPushMsgDO.java @@ -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; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java index 76943edf..a8416485 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/DeptMapper.java @@ -127,12 +127,17 @@ public interface DeptMapper extends BaseMapperX { /** * 根据部门编码查询部门 + *

+ * 注意:如果存在多条相同编码的记录,只返回第一条 * * @param code 部门编码 * @return 部门信息 */ default DeptDO selectByCode(String code) { - return selectOne(DeptDO::getCode, code); + List list = selectList(new LambdaQueryWrapperX() + .eq(DeptDO::getCode, code) + .last("LIMIT 1")); + return CollUtil.isNotEmpty(list) ? list.get(0) : null; } /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java new file mode 100644 index 00000000..7f4cc147 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/EspMapper.java @@ -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 { + + + /** + * 分页查询 + * @param reqVO 消息推送VO + * @return PageResult + */ + default PageResult selectPage(EspPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .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() + .eq(DeptPushMsgDO::getSystemCode, systemCode) + .eq(DeptPushMsgDO::getDeptId, deptId)); + } + + default DeptPushMsgDO selectBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) { + return selectOne(new LambdaQueryWrapperX() + .eq(DeptPushMsgDO::getSystemCode, systemCode) + .eq(DeptPushMsgDO::getExternalDeptCode, externalDeptCode)); + } + + default List selectListByDeptId(Long deptId) { + return selectList(DeptPushMsgDO::getDeptId, deptId); + } + + default int deleteByDeptId(Long deptId) { + return delete(DeptPushMsgDO::getDeptId, deptId); + } + + default List selectListBySystemCode(String systemCode) { + return selectList(DeptPushMsgDO::getSystemCode, systemCode); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/UserPostMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/UserPostMapper.java index fa9bd6aa..2df5c891 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/UserPostMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/dept/UserPostMapper.java @@ -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.baomidou.mybatisplus.core.toolkit.Wrappers; 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.List; @@ -29,4 +32,44 @@ public interface UserPostMapper extends BaseMapperX { default void deleteByUserId(Long userId) { delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId)); } + + /** + * 游标分页查询用户-岗位关系(只查询 userSource = 2 的用户) + * @param cursorTime 游标时间 + * @param cursorId 游标ID + * @param tenantId 租户ID(可选) + * @param limit 限制数量 + * @return 用户岗位关系列表 + */ + @Select("") + List selectPageByCursorWithUserSource(@Param("cursorTime") LocalDateTime cursorTime, + @Param("cursorId") Long cursorId, + @Param("tenantId") Long tenantId, + @Param("limit") Integer limit); + + /** + * 统计用户-岗位关系数量(只统计 userSource = 2 的用户) + * @param tenantId 租户ID(可选) + * @return 数量 + */ + @Select("") + Long countWithUserSource(@Param("tenantId") Long tenantId); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java index b9aad69a..bf21f41d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java @@ -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.module.system.dal.dataobject.userdept.UserDeptDO; 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.Collections; import java.util.List; @@ -45,4 +48,44 @@ public interface UserDeptMapper extends BaseMapperX { ); } + /** + * 游标分页查询用户-部门关系(只查询 userSource = 2 的用户) + * @param cursorTime 游标时间 + * @param cursorId 游标ID + * @param tenantId 租户ID(可选) + * @param limit 限制数量 + * @return 用户部门关系列表 + */ + @Select("") + List selectPageByCursorWithUserSource(@Param("cursorTime") LocalDateTime cursorTime, + @Param("cursorId") Long cursorId, + @Param("tenantId") Long tenantId, + @Param("limit") Integer limit); + + /** + * 统计用户-部门关系数量(只统计 userSource = 2 的用户) + * @param tenantId 租户ID(可选) + * @return 数量 + */ + @Select("") + Long countWithUserSource(@Param("tenantId") Long tenantId); + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/sms/SmsSendMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/sms/SmsSendMessage.java index fa86b5a6..5ce00196 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/sms/SmsSendMessage.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/sms/SmsSendMessage.java @@ -14,7 +14,7 @@ import java.util.List; @Data public class SmsSendMessage { - public static final String TOPIC = "SMS_SEND_TOPIC"; // 重点:需要增加消息对应的 Topic + public static final String TOPIC = "SMS_SEND_TOPIC_TEST"; // 重点:需要增加消息对应的 Topic /** * 短信日志编号 */ diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java index c97510df..3996eef8 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptService.java @@ -174,4 +174,15 @@ public interface DeptService { * @return 部门列表 */ List searchDeptTree(String keyword); + + /** + * 回填缺失的部门编码(不触发事件)。 + * + * @param deptIds 需要回填的部门 ID 列表 + */ + void backfillMissingCodesWithoutEvent(Collection deptIds); + + // ========== 数据同步专用接口 ========== + + void syncDept(DeptSaveReqVO syncReqVO); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index 03cadf43..0a5c607e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -900,4 +900,64 @@ public class DeptServiceImpl implements DeptService { } } + @Override + @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) + public void backfillMissingCodesWithoutEvent(Collection deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return; + } + List targets = deptMapper.selectBatchIds(deptIds); + for (DeptDO dept : targets) { + if (dept == null || StrUtil.isNotBlank(dept.getCode())) { + continue; + } + Integer source = ObjectUtil.defaultIfNull(dept.getDeptSource(), DeptSourceEnum.EXTERNAL.getSource()); + try { + String code = generateDeptCode(dept.getParentId(), source); + validateDeptCodeUnique(dept.getId(), code); + updateDeptCode(dept.getId(), code); + } catch (Exception ex) { + log.warn("[iWork] 回填部门编码失败 id={} name={} msg={}", dept.getId(), dept.getName(), ex.getMessage()); + } + } + } + + // ========== 数据同步专用接口 ========== + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true) + @DataPermission(enable = false) + public void syncDept(DeptSaveReqVO syncReqVO) { + if (syncReqVO.getId() == null) { + log.warn("[syncDept] 同步部门失败,ID 不能为空"); + return; + } + // 标准化父部门 ID + syncReqVO.setParentId(normalizeParentId(syncReqVO.getParentId())); + // 默认部门来源 + if (syncReqVO.getDeptSource() == null) { + syncReqVO.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + } + + // 检查部门是否存在 + DeptDO existingDept = deptMapper.selectById(syncReqVO.getId()); + + // 转换为 DO + DeptDO dept = BeanUtils.toBean(syncReqVO, DeptDO.class); + + if (existingDept != null) { + // 部门存在,执行更新 + deptMapper.updateById(dept); + log.info("[syncDept] 部门同步-更新成功, deptId={}, deptName={}", dept.getId(), dept.getName()); + } else { + // 部门不存在,执行插入 + deptMapper.insert(dept); + log.info("[syncDept] 部门同步-创建成功, deptId={}, deptName={}", dept.getId(), dept.getName()); + } + + // 注意:不发布变更事件,避免循环同步 + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java new file mode 100644 index 00000000..bd746bfb --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java @@ -0,0 +1,256 @@ +package com.zt.plat.module.system.service.dept; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.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.dal.mysql.dept.DeptMapper; +import com.zt.plat.module.system.dal.mysql.dept.EspMapper; +import com.zt.plat.module.system.dal.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import java.util.List; +import java.util.Objects; +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; + +/** + * 部门推送消息接口ServiceImpl实现类 + */ +@Service +@Validated +public class EspServiceImpl implements IEspService { + + @Resource + private EspMapper espMapper; + @Resource + private DeptMapper deptMapper; + @Resource + private CacheManager cacheManager; + + @Override + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false) + public Long createDeptPushMsg(EspSaveRespVo createReqVO) { + + //请求校验 + normalizeRequest(createReqVO); + //冲突禁用-映射 + disableActiveMappingIfConflict(createReqVO.getDeptId(), createReqVO.getSystemCode(), createReqVO.getExternalDeptCode()); + validateForCreateOrUpdate(null, createReqVO.getDeptId(), createReqVO.getSystemCode(), + createReqVO.getExternalDeptCode()); + + DeptPushMsgDO entity = BeanUtils.toBean(createReqVO, DeptPushMsgDO.class); + if (entity.getStatus() == null) { + entity.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + espMapper.insert(entity); + return entity.getId(); + } + + @Override + public void updateDeptPushMsg(EspSaveRespVo updateReqVO) { + normalizeRequest(updateReqVO); + DeptPushMsgDO exists = validateExists(updateReqVO.getId()); + disableActiveMappingIfConflict(updateReqVO.getDeptId(), updateReqVO.getSystemCode(), updateReqVO.getExternalDeptCode()); + validateForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getDeptId(), updateReqVO.getSystemCode(), + updateReqVO.getExternalDeptCode()); + + DeptPushMsgDO updateObj = BeanUtils.toBean(updateReqVO, DeptPushMsgDO.class); + // 保持原有的状态默认值逻辑 + if (updateObj.getStatus() == null) { + updateObj.setStatus(exists.getStatus() == null ? CommonStatusEnum.ENABLE.getStatus() : exists.getStatus()); + } + espMapper.updateById(updateObj); + evictCacheSafely(exists.getDeptId()); + evictCacheSafely(updateObj.getDeptId()); + } + + @Override + public void deleteDeptPushMsg(Long id) { + DeptPushMsgDO exists = validateExists(id); + espMapper.deleteById(id); + evictCacheSafely(exists.getDeptId()); + } + + @Override + public DeptPushMsgDO getDeptPushMsgDetails(Long id) { + return espMapper.selectById(id); + } + + @Override + public PageResult getDeptExternalCodePage(EspPageReqVO reqVO) { + + return espMapper.selectPage(reqVO); + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#deptId") + public List getPushMsgByDeptId(Long deptId) { + return espMapper.selectListByDeptId(deptId); + } + +/* @Override + public DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode) { + if (StrUtil.hasEmpty(systemCode, externalDeptCode)) { + return null; + } + return espMapper.selectBySystemCodeAndExternalCode(systemCode.trim(), externalDeptCode.trim()); + }*/ + +/* @Override + public DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId) { + if (StrUtil.isBlank(systemCode) || deptId == null) { + return null; + } + return espMapper.selectBySystemCodeAndDeptId(systemCode.trim(), deptId); + }*/ + +/* @Override + public Long getDeptPushMsgDetails(Long deptId, String systemCode, String externalDeptCode, + String externalDeptName, Integer status) { + + if (StrUtil.hasEmpty(systemCode, externalDeptCode) || deptId == null) { + return null; + } + String normalizedSystemCode = systemCode.trim(); + String normalizedExternalCode = externalDeptCode.trim(); + String normalizedExternalName = StrUtil.blankToDefault(StrUtil.trimToNull(externalDeptName), null); + + disableActiveMappingIfConflict(deptId, normalizedSystemCode, normalizedExternalCode); + + // 如果存在则更新,否则创建 + DeptExternalCodeDO exists = espMapper.selectBySystemCodeAndDeptId(normalizedSystemCode, deptId); + if (exists != null) { + DeptExternalCodeSaveReqVO updateReqVO = new DeptExternalCodeSaveReqVO(); + updateReqVO.setId(exists.getId()); + updateReqVO.setDeptId(deptId); + updateReqVO.setSystemCode(normalizedSystemCode); + updateReqVO.setExternalDeptCode(normalizedExternalCode); + updateReqVO.setExternalDeptName(normalizedExternalName); + updateReqVO.setStatus(status == null ? exists.getStatus() : status); + + //TODO + //getDeptPushMsgDetails(updateReqVO); + return exists.getId(); + } + + DeptExternalCodeSaveReqVO createReqVO = new DeptExternalCodeSaveReqVO(); + createReqVO.setDeptId(deptId); + createReqVO.setSystemCode(normalizedSystemCode); + createReqVO.setExternalDeptCode(normalizedExternalCode); + createReqVO.setExternalDeptName(normalizedExternalName); + createReqVO.setStatus(status == null ? CommonStatusEnum.ENABLE.getStatus() : status); + return getDeptPushMsgDetails(createReqVO); + }*/ + +/* @Override + public void deleteDeptExternalCodesByDeptId(Long deptId) { + if (deptId == null) { + return; + } + espMapper.deleteByDeptId(deptId); + evictCacheSafely(deptId); + }*/ + + private DeptPushMsgDO validateExists(Long id) { + if (id == null) { + throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); + } + DeptPushMsgDO entity = espMapper.selectById(id); + if (entity == null) { + throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); + } + return entity; + } + + private void validateForCreateOrUpdate(Long id, Long deptId, String systemCode, String externalDeptCode) { + + // 校验部门存在 + DeptDO dept = deptMapper.selectById(deptId); + if (dept == null) { + throw exception(DEPT_NOT_FOUND); + } + String normalizedSystemCode = StrUtil.blankToDefault(systemCode, null); + String normalizedExternalCode = StrUtil.blankToDefault(externalDeptCode, null); + + // 校验同一系统下部门唯一 + if (StrUtil.isNotBlank(normalizedSystemCode)) { + DeptPushMsgDO sameDept = espMapper + .selectBySystemCodeAndDeptId(normalizedSystemCode, deptId); + if (sameDept != null && (id == null || !sameDept.getId().equals(id))) { + throw exception(DEPT_EXTERNAL_RELATION_EXISTS, normalizedSystemCode); + } + } + // 校验同一系统下外部编码唯一 + if (StrUtil.isNotBlank(normalizedSystemCode) && StrUtil.isNotBlank(normalizedExternalCode)) { + DeptPushMsgDO sameExternal = espMapper + .selectBySystemCodeAndExternalCode(normalizedSystemCode, normalizedExternalCode); + if (sameExternal != null && (id == null || !sameExternal.getId().equals(id))) { + boolean sameDept = Objects.equals(deptId, sameExternal.getDeptId()); + boolean activeConflict = !sameDept && CommonStatusEnum.isEnable(sameExternal.getStatus()); + if (activeConflict) { + throw exception(DEPT_EXTERNAL_CODE_DUPLICATE, normalizedSystemCode, normalizedExternalCode); + } + } + } + } + + private void normalizeRequest(EspSaveRespVo reqVO) { + if (reqVO == null) { + return; + } + if (StrUtil.isNotBlank(reqVO.getSystemCode())) { + reqVO.setSystemCode(reqVO.getSystemCode().trim()); + } + if (StrUtil.isNotBlank(reqVO.getExternalDeptCode())) { + reqVO.setExternalDeptCode(reqVO.getExternalDeptCode().trim()); + } + if (reqVO.getStatus() == null) { + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + } + + private void disableActiveMappingIfConflict(Long targetDeptId, String systemCode, String externalDeptCode) { + String normalizedSystem = StrUtil.trimToNull(systemCode); + String normalizedExternal = StrUtil.trimToNull(externalDeptCode); + if (StrUtil.hasEmpty(normalizedSystem, normalizedExternal) || targetDeptId == null) { + return; + } + DeptPushMsgDO existing = espMapper.selectBySystemCodeAndExternalCode(normalizedSystem, normalizedExternal); + if (existing == null) { + return; + } + if (Objects.equals(existing.getDeptId(), targetDeptId)) { + return; + } + if (CommonStatusEnum.isEnable(existing.getStatus())) { + DeptPushMsgDO update = new DeptPushMsgDO(); + update.setId(existing.getId()); + update.setStatus(CommonStatusEnum.DISABLE.getStatus()); + espMapper.updateById(update); + evictCacheSafely(existing.getDeptId()); + } + } + + private void evictCacheSafely(Long deptId) { + + if (deptId == null || cacheManager == null) { + return; + } + try { + if (cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST) != null) { + cacheManager.getCache(RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST).evict(deptId); + } + } catch (Exception ignore) { + // 缓存失效失败不影响主流程 + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java new file mode 100644 index 00000000..97c83454 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.system.service.dept; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO; +import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo; +import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO; + +import java.util.List; + +/** + * 部门推送消息 Service 接口 + */ +public interface IEspService { + + /** + * 创建映射关系 + * @param createReqVO 创建请求 + * @return 新增记录编号 + */ + Long createDeptPushMsg(EspSaveRespVo createReqVO); + + /** + * 更新映射关系 + * @param updateReqVO 更新请求 + */ + void updateDeptPushMsg(EspSaveRespVo updateReqVO); + + /** + * 删除映射关系 + * + * @param id 记录编号 + */ + void deleteDeptPushMsg(Long id); + + /** + * 获取映射详情 + */ + DeptPushMsgDO getDeptPushMsgDetails(Long id); + + /** + * 分页查询映射 + */ + PageResult getDeptExternalCodePage(EspPageReqVO reqVO); + + /** + * 根据部门推送消息 + */ + List getPushMsgByDeptId(Long deptId); + + /** + * 根据部门与外部系统保存/更新映射(存在则更新,不存在则创建) + * @param deptId 本系统部门 ID + * @param systemCode 外部系统标识 + * @param externalDeptCode 外部系统组织编码 + * @param externalDeptName 外部系统组织名称(可选) + * @param status 状态,默认启用 + * @return 映射记录 ID + */ + /* Long getDeptPushMsgDetails(Long deptId, String systemCode, String externalDeptCode, String externalDeptName, + Integer status);*/ + + /** + * 根据部门删除全部外部编码映射 + * + * @param deptId 部门编号 + */ + //void deleteDeptExternalCodesByDeptId(Long deptId); + + /** + * 根据外部系统与外部组织编码查询映射 + */ + //DeptExternalCodeDO getBySystemCodeAndExternalCode(String systemCode, String externalDeptCode); + + /** + * 根据外部系统与部门编号查询映射 + */ + //DeptExternalCodeDO getBySystemCodeAndDeptId(String systemCode, Long deptId); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostService.java index 4ece87ea..153c7fd3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostService.java @@ -89,4 +89,8 @@ public interface PostService { */ Long getOrCreatePostByName(String postName); + // ========== 数据同步专用接口 ========== + + void syncPost(PostSaveReqVO syncReqVO); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java index ff0aa7b4..9dcf31e2 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/PostServiceImpl.java @@ -191,4 +191,25 @@ public class PostServiceImpl implements PostService { return createPost(createReqVO); } + + // ========== 数据同步专用接口 ========== + + @Override + public void syncPost(PostSaveReqVO syncReqVO) { + if (syncReqVO.getId() == null) { + return; + } + + PostDO existingPost = postMapper.selectById(syncReqVO.getId()); + PostDO post = BeanUtils.toBean(syncReqVO, PostDO.class); + + if (existingPost != null) { + postMapper.updateById(post); + } else { + if (post.getStatus() == null) { + post.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + postMapper.insert(post); + } + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java index c19d3975..17ec9b6a 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkSyncProcessor.java @@ -5,23 +5,42 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrJo import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrUserPageRespVO; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Abstraction for applying iWork entities into local persistence. */ public interface IWorkSyncProcessor { - BatchResult syncSubcompanies(List data, SyncOptions options); + BatchResult syncSubcompanies(List data, + SyncOptions options); + + BatchResult syncSubcompanies(List data, + SyncOptions options, + DeptSyncContext context); BatchResult syncDepartments(List data, SyncOptions options); + BatchResult syncDepartments(List data, + SyncOptions options, + DeptSyncContext context); + BatchResult syncJobTitles(List data, SyncOptions options); BatchResult syncUsers(List data, SyncOptions options); + /** + * 对当次同步累计的待处理/占位部门做最终补偿(跨页父子依赖)。 + */ + default BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) { + return BatchResult.empty(); + } + /** * Execution options shared by batch and single sync flows. */ @@ -53,6 +72,32 @@ public interface IWorkSyncProcessor { } } + /** + * 部门/分部跨页同步上下文,用于累计待处理记录与已就绪父级。 + */ + final class DeptSyncContext { + private final Set readyParentIds = new HashSet<>(); + private final List pendingSubcompanies = new ArrayList<>(); + private final List pendingDepartments = new ArrayList<>(); + private final Set placeholderDeptIds = new HashSet<>(); + + public Set getReadyParentIds() { + return readyParentIds; + } + + public List getPendingSubcompanies() { + return pendingSubcompanies; + } + + public List getPendingDepartments() { + return pendingDepartments; + } + + public Set getPlaceholderDeptIds() { + return placeholderDeptIds; + } + } + /** * Aggregated result for a sync batch. */ @@ -170,11 +215,11 @@ public interface IWorkSyncProcessor { } default BatchResult syncSubcompany(IWorkHrSubcompanyPageRespVO.Subcompany data, SyncOptions options) { - return syncSubcompanies(Collections.singletonList(data), options); + return syncSubcompanies(Collections.singletonList(data), options, null); } default BatchResult syncDepartment(IWorkHrDepartmentPageRespVO.Department data, SyncOptions options) { - return syncDepartments(Collections.singletonList(data), options); + return syncDepartments(Collections.singletonList(data), options, null); } default BatchResult syncJobTitle(IWorkHrJobTitlePageRespVO.JobTitle data, SyncOptions options) { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java index 3c004a2b..abf94ac4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImpl.java @@ -50,14 +50,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { @Override public BatchResult syncSubcompanies(List data, SyncOptions options) { + return syncSubcompanies(data, options, null); + } + + @Override + public BatchResult syncSubcompanies(List data, + SyncOptions options, + DeptSyncContext context) { + return syncSubcompaniesInternal(data, options, context, false); + } + + private BatchResult syncSubcompaniesInternal(List data, + SyncOptions options, + DeptSyncContext context, + boolean allowPlaceholderOnRemaining) { List records = CollUtil.emptyIfNull(data); BatchResult result = BatchResult.empty(); - if (records.isEmpty()) { + if (records.isEmpty() + && (context == null || CollUtil.isEmpty(context.getPendingSubcompanies()))) { return result; } result.increasePulled(records.size()); - List queue = new ArrayList<>(records); - Set readyParentIds = new HashSet<>(); + List queue = new ArrayList<>(); + if (context != null && CollUtil.isNotEmpty(context.getPendingSubcompanies())) { + queue.addAll(context.getPendingSubcompanies()); + context.getPendingSubcompanies().clear(); + } + queue.addAll(records); + Set readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -106,6 +126,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { break; } } + if (!queue.isEmpty()) { + if (context != null && !allowPlaceholderOnRemaining) { + context.getPendingSubcompanies().addAll(queue); + queue.clear(); + } + } if (!queue.isEmpty()) { for (IWorkHrSubcompanyPageRespVO.Subcompany remaining : queue) { log.warn("[iWork] 分部父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getSubcompanyname()); @@ -117,6 +143,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { try { DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); applyDeptOutcome(result, outcome, "分部", remaining.getSubcompanyname()); + if (context != null && outcome.deptId() != null) { + context.getPlaceholderDeptIds().add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 分部占位插入失败: id={} name={}", remaining.getId(), remaining.getSubcompanyname(), ex); result.increaseFailed(); @@ -128,14 +157,34 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { @Override public BatchResult syncDepartments(List data, SyncOptions options) { + return syncDepartments(data, options, null); + } + + @Override + public BatchResult syncDepartments(List data, + SyncOptions options, + DeptSyncContext context) { + return syncDepartmentsInternal(data, options, context, false); + } + + private BatchResult syncDepartmentsInternal(List data, + SyncOptions options, + DeptSyncContext context, + boolean allowPlaceholderOnRemaining) { List records = CollUtil.emptyIfNull(data); BatchResult result = BatchResult.empty(); - if (records.isEmpty()) { + if (records.isEmpty() + && (context == null || CollUtil.isEmpty(context.getPendingDepartments()))) { return result; } result.increasePulled(records.size()); - List queue = new ArrayList<>(records); - Set readyParentIds = new HashSet<>(); + List queue = new ArrayList<>(); + if (context != null && CollUtil.isNotEmpty(context.getPendingDepartments())) { + queue.addAll(context.getPendingDepartments()); + context.getPendingDepartments().clear(); + } + queue.addAll(records); + Set readyParentIds = context != null ? context.getReadyParentIds() : new HashSet<>(); int guard = 0; int maxPasses = Math.max(1, queue.size() * 2); while (!queue.isEmpty() && guard++ < maxPasses) { @@ -184,6 +233,12 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { break; } } + if (!queue.isEmpty()) { + if (context != null && !allowPlaceholderOnRemaining) { + context.getPendingDepartments().addAll(queue); + queue.clear(); + } + } if (!queue.isEmpty()) { for (IWorkHrDepartmentPageRespVO.Department remaining : queue) { log.warn("[iWork] 部门父级缺失,延迟生成编码插入占位: id={} name={}", remaining.getId(), remaining.getDepartmentname()); @@ -195,6 +250,9 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { try { DeptSyncOutcome outcome = upsertDept(saveReq.getId(), saveReq, isCanceledFlag(remaining.getCanceled()), options); applyDeptOutcome(result, outcome, "部门", remaining.getDepartmentname()); + if (context != null && outcome.deptId() != null) { + context.getPlaceholderDeptIds().add(outcome.deptId()); + } } catch (Exception ex) { log.error("[iWork] 部门占位插入失败: id={} name={}", remaining.getId(), remaining.getDepartmentname(), ex); result.increaseFailed(); @@ -204,6 +262,17 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { return result; } + @Override + public BatchResult flushDeptPending(DeptSyncContext context, SyncOptions options) { + BatchResult result = BatchResult.empty(); + if (context == null) { + return result; + } + result.merge(syncSubcompaniesInternal(Collections.emptyList(), options, context, true)); + result.merge(syncDepartmentsInternal(Collections.emptyList(), options, context, true)); + return result; + } + @Override public BatchResult syncJobTitles(List data, SyncOptions options) { List records = CollUtil.emptyIfNull(data); @@ -444,7 +513,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { req.setIsGroup(Boolean.FALSE); req.setDeptSource(DeptSourceEnum.IWORK.getSource()); req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode()); - req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getSubcompanycode()), String.valueOf(data.getId()))); + req.setExternalDeptCode(trimToNull(data.getSubcompanycode())); req.setExternalDeptName(data.getSubcompanyname()); return req; } @@ -464,7 +533,7 @@ public class IWorkSyncProcessorImpl implements IWorkSyncProcessor { req.setIsGroup(Boolean.FALSE); req.setDeptSource(DeptSourceEnum.IWORK.getSource()); req.setExternalSystemCode(ExternalPlatformEnum.IWORK.getCode()); - req.setExternalDeptCode(StrUtil.blankToDefault(trimToNull(data.getDepartmentcode()), String.valueOf(data.getId()))); + req.setExternalDeptCode(trimToNull(data.getDepartmentcode())); req.setExternalDeptName(data.getDepartmentname()); return req; } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java index 8954ea8f..b06959d6 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil; import com.zt.plat.framework.common.exception.util.ServiceExceptionUtil; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum; +import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService; @@ -31,6 +32,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private final IWorkOrgRestService orgRestService; private final IWorkSyncProcessor syncProcessor; + private final DeptService deptService; @Override public IWorkFullSyncRespVO fullSyncDepartments(IWorkFullSyncReqVO reqVO) { @@ -64,11 +66,14 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { boolean syncJobTitle = scopes.contains(IWorkSyncEntityTypeEnum.JOB_TITLE); int processedPages = 0; IWorkSyncProcessor.SyncOptions options = buildFullSyncOptions(reqVO); + IWorkSyncProcessor.DeptSyncContext deptSyncContext = (syncDepartments || syncSubcompanies) + ? new IWorkSyncProcessor.DeptSyncContext() + : null; if (syncSubcompanies) { - processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats); + processedPages += executeSubcompanyFullSync(reqVO, options, respVO.getSubcompanyStat(), batchStats, deptSyncContext); } if (syncDepartments) { - processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats); + processedPages += executeDepartmentFullSync(reqVO, options, respVO.getDepartmentStat(), batchStats, deptSyncContext); } if (syncJobTitle) { processedPages += executeJobTitleFullSync(reqVO, options, respVO.getJobTitleStat(), batchStats); @@ -76,6 +81,13 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { if (syncUsers) { processedPages += executeUserFullSync(reqVO, options, respVO.getUserStat(), batchStats); } + if (deptSyncContext != null) { + IWorkSyncProcessor.BatchResult flushResult = syncProcessor.flushDeptPending(deptSyncContext, options); + updateStat(respVO.getDepartmentStat(), flushResult, 0); + if (CollUtil.isNotEmpty(deptSyncContext.getPlaceholderDeptIds())) { + deptService.backfillMissingCodesWithoutEvent(deptSyncContext.getPlaceholderDeptIds()); + } + } respVO.setProcessedPages(processedPages); return respVO; } @@ -83,7 +95,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private int executeSubcompanyFullSync(IWorkFullSyncReqVO reqVO, IWorkSyncProcessor.SyncOptions options, IWorkSyncEntityStatVO stat, - List batches) { + List batches, + IWorkSyncProcessor.DeptSyncContext context) { return executePaged(reqVO, IWorkSyncEntityTypeEnum.SUBCOMPANY, batches, (page, pageSize) -> { IWorkSubcompanyQueryReqVO query = new IWorkSubcompanyQueryReqVO(); query.setCurpage(page); @@ -92,7 +105,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkHrSubcompanyPageRespVO pageResp = orgRestService.listSubcompanies(query); ensureIWorkSuccess("拉取分部", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); - IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options); + IWorkSyncProcessor.BatchResult result = syncProcessor.syncSubcompanies(dataList, options, context); updateStat(stat, result, dataList.size()); return new BatchExecution(result, dataList.size()); }); @@ -101,7 +114,8 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { private int executeDepartmentFullSync(IWorkFullSyncReqVO reqVO, IWorkSyncProcessor.SyncOptions options, IWorkSyncEntityStatVO stat, - List batches) { + List batches, + IWorkSyncProcessor.DeptSyncContext context) { return executePaged(reqVO, IWorkSyncEntityTypeEnum.DEPARTMENT, batches, (page, pageSize) -> { IWorkDepartmentQueryReqVO query = new IWorkDepartmentQueryReqVO(); query.setCurpage(page); @@ -110,7 +124,7 @@ public class IWorkSyncServiceImpl implements IWorkSyncService { IWorkHrDepartmentPageRespVO pageResp = orgRestService.listDepartments(query); ensureIWorkSuccess("拉取部门", pageResp.isSuccess(), pageResp.getMessage()); List dataList = CollUtil.emptyIfNull(pageResp.getDataList()); - IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options); + IWorkSyncProcessor.BatchResult result = syncProcessor.syncDepartments(dataList, options, context); updateStat(stat, result, dataList.size()); return new BatchExecution(result, dataList.size()); }); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java index 28a029fd..a1a88dd3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java @@ -1,6 +1,7 @@ package com.zt.plat.module.system.service.permission; import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import java.util.Collection; import java.util.Set; @@ -143,4 +144,12 @@ public interface PermissionService { */ DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + /** + * 获得用户的数据权限级别 + * + * @param userId 用户编号 + * @return 数据权限范围枚举 + */ + DataScopeEnum getUserDataPermissionLevel(Long userId); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java index ac7a1553..6bb37d18 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java @@ -27,6 +27,7 @@ import com.zt.plat.module.system.enums.permission.RoleTypeEnum; import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.user.AdminUserService; import com.zt.plat.module.system.service.userdept.UserDeptService; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +58,15 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.ROLE_CAN_NOT_UP @Slf4j public class PermissionServiceImpl implements PermissionService { + private static final List DATA_SCOPE_PRIORITY = Arrays.asList( + DataScopeEnum.ALL, + DataScopeEnum.COMPANY_AND_DEPT, + DataScopeEnum.DEPT_AND_CHILD, + DataScopeEnum.DEPT_ONLY, + DataScopeEnum.DEPT_CUSTOM, + DataScopeEnum.SELF + ); + @Resource private RoleMenuMapper roleMenuMapper; @Resource @@ -404,6 +414,40 @@ public class PermissionServiceImpl implements PermissionService { return result; } + @Override + @DataPermission(enable = false) + @TenantIgnore + public DataScopeEnum getUserDataPermissionLevel(Long userId) { + List roles = getEnableUserRoleListByUserIdFromCache(userId); + if (CollUtil.isEmpty(roles)) { + return DataScopeEnum.SELF; + } + + DataScopeEnum best = null; + for (RoleDO role : roles) { + DataScopeEnum scopeEnum = DataScopeEnum.findByScope(role.getDataScope()); + if (scopeEnum == null) { + continue; + } + if (best == null || compareScope(scopeEnum, best) < 0) { + best = scopeEnum; + if (DataScopeEnum.ALL.equals(best)) { + break; + } + } + } + return best != null ? best : DataScopeEnum.SELF; + } + + private int compareScope(DataScopeEnum left, DataScopeEnum right) { + return getScopePriority(left) - getScopePriority(right); + } + + private int getScopePriority(DataScopeEnum scope) { + int idx = DATA_SCOPE_PRIORITY.indexOf(scope); + return idx >= 0 ? idx : Integer.MAX_VALUE; + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsCodeServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsCodeServiceImpl.java index 9661a239..bc31fbad 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsCodeServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsCodeServiceImpl.java @@ -10,10 +10,10 @@ import com.zt.plat.module.system.dal.dataobject.sms.SmsCodeDO; import com.zt.plat.module.system.dal.mysql.sms.SmsCodeMapper; import com.zt.plat.module.system.enums.sms.SmsSceneEnum; import com.zt.plat.module.system.framework.sms.config.SmsCodeProperties; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import static cn.hutool.core.util.RandomUtil.randomInt; @@ -56,11 +56,11 @@ public class SmsCodeServiceImpl implements SmsCodeService { if (lastSmsCode != null) { if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis() < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁 - throw exception(SMS_CODE_SEND_TOO_FAST); + throw exception(SMS_CODE_SEND_TOO_FAST, smsCodeProperties.getSendFrequency().toMinutes()); } if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限 lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。 - throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY); + throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY, smsCodeProperties.getSendMaximumQuantityPerDay()); } // TODO ZT:提升,每个 IP 每天可发送数量 // TODO ZT:提升,每个 IP 每小时可发送数量 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java index 382a15fd..5ca6e39c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserService.java @@ -212,4 +212,8 @@ public interface AdminUserService { */ boolean isPasswordMatch(AdminUserDO user, String rawPassword); + // ========== 数据同步专用接口 ========== + + void syncUser(UserSaveReqVO syncReqVO); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java index 93058b6f..cd4480c4 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/user/AdminUserServiceImpl.java @@ -725,4 +725,48 @@ public class AdminUserServiceImpl implements AdminUserService { return StrUtil.isNotBlank(value) && value.startsWith("$2"); } + // ========== 数据同步专用接口 ========== + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncUser(UserSaveReqVO syncReqVO) { + if (syncReqVO.getId() == null) { + log.warn("[syncUser] 同步用户失败,ID 不能为空"); + return; + } + + // 检查用户是否存在 + AdminUserDO existingUser = userMapper.selectById(syncReqVO.getId()); + + // 转换为 DO(只同步用户主表,不处理关联关系) + AdminUserDO user = BeanUtils.toBean(syncReqVO, AdminUserDO.class); + user.setDeptIds(null); // 部门关联单独同步 + user.setPostIds(null); // 岗位关联单独同步 + + if (existingUser != null) { + // 用户存在,执行更新(不更新密码) + user.setPassword(null); + userMapper.updateById(user); + log.info("[syncUser] 用户同步-更新成功, userId={}, username={}", user.getId(), user.getUsername()); + } else { + // 用户不存在,执行插入 + // 设置默认状态 + if (user.getStatus() == null) { + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); + } + // 设置默认用户来源 + if (user.getUserSource() == null) { + user.setUserSource(UserSourceEnum.EXTERNAL.getSource()); + } + // 如果没有密码,设置默认密码 + if (StrUtil.isBlank(user.getPassword())) { + user.setPassword(passwordEncoder.encode("123456")); + } + userMapper.insert(user); + log.info("[syncUser] 用户同步-创建成功, userId={}, username={}", user.getId(), user.getUsername()); + } + + // 注意:不发布变更事件,避免循环同步 + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptService.java index 378637eb..26c728b7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptService.java @@ -76,5 +76,8 @@ public interface UserDeptService { */ void batchCreateUserDept(List createReqVOList); + // ========== 数据同步专用接口 ========== + + void syncUserDept(UserDeptDO syncReqVO); } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptServiceImpl.java index 3b699c91..8c71a020 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/userdept/UserDeptServiceImpl.java @@ -155,4 +155,22 @@ public class UserDeptServiceImpl implements UserDeptService { } } -} \ No newline at end of file + // ========== 数据同步专用接口 ========== + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncUserDept(UserDeptDO syncReqVO) { + if (syncReqVO.getId() == null) { + return; + } + + UserDeptDO existing = userDeptMapper.selectById(syncReqVO.getId()); + + if (existing != null) { + userDeptMapper.updateById(syncReqVO); + } else { + userDeptMapper.insert(syncReqVO); + } + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml index 2b0a2558..1b3abadf 100644 --- a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml +++ b/zt-module-system/zt-module-system-server/src/main/resources/application.yaml @@ -240,9 +240,9 @@ zt: sms-code: # 短信验证码相关的配置项 expire-times: 10m send-frequency: 1m - send-maximum-quantity-per-day: 10 - begin-code: 9999 # 这里配置 9999 的原因是,测试方便。 - end-code: 9999 # 这里配置 9999 的原因是,测试方便。 + send-maximum-quantity-per-day: 20 + begin-code: 100000 + end-code: 999999 # E办OAuth2配置文件 diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java new file mode 100644 index 00000000..a16f5f84 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncProcessorImplTest.java @@ -0,0 +1,141 @@ +package com.zt.plat.module.system.service.integration.iwork.impl; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrSubcompanyPageRespVO; +import com.zt.plat.module.system.dal.mysql.dept.PostMapper; +import com.zt.plat.module.system.dal.mysql.user.AdminUserMapper; +import com.zt.plat.module.system.service.dept.DeptService; +import com.zt.plat.module.system.service.dept.PostService; +import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor; +import com.zt.plat.module.system.service.user.AdminUserService; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests for cross-page pending handling and placeholder backfill in IWorkSyncProcessorImpl. + */ +class IWorkSyncProcessorImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private IWorkSyncProcessorImpl processor; + + @Mock + private DeptService deptService; + @Mock + private PostService postService; + @Mock + private PostMapper postMapper; + @Mock + private AdminUserService adminUserService; + @Mock + private AdminUserMapper adminUserMapper; + + @Test + void shouldProcessPendingChildWhenParentArrivesInLaterPage() { + IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext(); + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department(); + child.setId(200); + child.setDepartmentname("child"); + child.setSupdepid(100); // parent comes later + + IWorkHrDepartmentPageRespVO.Department parent = new IWorkHrDepartmentPageRespVO.Department(); + parent.setId(100); + parent.setDepartmentname("parent"); + parent.setSupdepid(0); // root + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(100L, 200L); + + processor.syncDepartments(List.of(child), options, context); + + verify(deptService, never()).createDept(any()); + assertEquals(1, context.getPendingDepartments().size()); + + processor.syncDepartments(List.of(parent), options, context); + + verify(deptService, times(2)).createDept(any()); + assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after parent processed"); + } + + @Test + void shouldInsertPlaceholderWhenParentMissingAfterFlush() { + IWorkSyncProcessor.DeptSyncContext context = new IWorkSyncProcessor.DeptSyncContext(); + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department child = new IWorkHrDepartmentPageRespVO.Department(); + child.setId(300); + child.setDepartmentname("orphan"); + child.setSupdepid(9999); // never provided + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(300L); + + processor.syncDepartments(List.of(child), options, context); + assertEquals(1, context.getPendingDepartments().size()); + + IWorkSyncProcessor.BatchResult flushResult = processor.flushDeptPending(context, options); + assertNotNull(flushResult); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO placeholderReq = captor.getValue(); + assertTrue(Boolean.TRUE.equals(placeholderReq.getDelayCodeGeneration())); + assertNull(placeholderReq.getCode()); + + assertTrue(context.getPendingDepartments().isEmpty(), "pending should be cleared after placeholder insert"); + assertTrue(context.getPlaceholderDeptIds().contains(300L)); + } + + @Test + void shouldKeepExternalCodeNullWhenDepartmentCodeBlank() { + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department(); + dept.setId(500); + dept.setDepartmentname("blank-code-dept"); + dept.setDepartmentcode(" "); + dept.setSupdepid(0); + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(500L); + + processor.syncDepartments(List.of(dept), options, null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO req = captor.getValue(); + assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is blank"); + } + + @Test + void shouldKeepExternalCodeNullWhenSubcompanyCodeBlank() { + IWorkSyncProcessor.SyncOptions options = IWorkSyncProcessor.SyncOptions.custom(true, true, true); + + IWorkHrSubcompanyPageRespVO.Subcompany subcompany = new IWorkHrSubcompanyPageRespVO.Subcompany(); + subcompany.setId(600); + subcompany.setSubcompanyname("blank-code-sub"); + subcompany.setSubcompanycode(null); + subcompany.setSupsubcomid(0); + + when(deptService.getDept(anyLong())).thenReturn(null); + when(deptService.createDept(any(DeptSaveReqVO.class))).thenReturn(600L); + + processor.syncSubcompanies(List.of(subcompany), options, null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(DeptSaveReqVO.class); + verify(deptService, times(1)).createDept(captor.capture()); + DeptSaveReqVO req = captor.getValue(); + assertNull(req.getExternalDeptCode(), "externalDeptCode should remain null when source code is null or blank"); + } +} diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java new file mode 100644 index 00000000..b8b1eb32 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkSyncServiceImplTest.java @@ -0,0 +1,62 @@ +package com.zt.plat.module.system.service.integration.iwork.impl; + +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkFullSyncReqVO; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkHrDepartmentPageRespVO; +import com.zt.plat.module.system.enums.integration.IWorkSyncEntityTypeEnum; +import com.zt.plat.module.system.service.dept.DeptService; +import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService; +import com.zt.plat.module.system.service.integration.iwork.IWorkSyncProcessor; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.stubbing.Answer; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class IWorkSyncServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private IWorkSyncServiceImpl syncService; + + @Mock + private IWorkOrgRestService orgRestService; + @Mock + private IWorkSyncProcessor syncProcessor; + @Mock + private DeptService deptService; + + @Test + void shouldBackfillCodesWhenPlaceholdersExistAfterFullSync() { + IWorkFullSyncReqVO reqVO = new IWorkFullSyncReqVO(); + reqVO.setPageSize(1); + reqVO.setMaxPages(1); + + IWorkHrDepartmentPageRespVO pageResp = new IWorkHrDepartmentPageRespVO(); + pageResp.setSuccess(true); + IWorkHrDepartmentPageRespVO.Department dept = new IWorkHrDepartmentPageRespVO.Department(); + dept.setId(1); + pageResp.setDataList(List.of(dept)); + when(orgRestService.listDepartments(any())).thenReturn(pageResp); + + // 在部门同步时标记占位 ID + doAnswer((Answer) invocation -> { + IWorkSyncProcessor.DeptSyncContext context = invocation.getArgument(2); + if (context != null) { + context.getPlaceholderDeptIds().add(500L); + } + return IWorkSyncProcessor.BatchResult.empty(); + }).when(syncProcessor).syncDepartments(any(), any(), any(IWorkSyncProcessor.DeptSyncContext.class)); + + when(syncProcessor.flushDeptPending(any(), any())).thenReturn(IWorkSyncProcessor.BatchResult.empty()); + + syncService.fullSyncDepartments(reqVO); + + verify(deptService, times(1)).backfillMissingCodesWithoutEvent(argThat(set -> set.contains(500L))); + } +} diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java index 73095709..9bfefac6 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/permission/PermissionServiceTest.java @@ -1,7 +1,9 @@ package com.zt.plat.module.system.service.permission; import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.framework.common.enums.CommonStatusEnum; import com.zt.plat.framework.test.core.ut.BaseDbUnitTest; +import com.zt.plat.framework.common.util.json.JsonUtils; import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import com.zt.plat.module.system.dal.dataobject.permission.RoleDO; import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO; @@ -11,6 +13,7 @@ import com.zt.plat.module.system.dal.mysql.permission.RoleMapper; import com.zt.plat.module.system.dal.mysql.permission.RoleMenuMapper; import com.zt.plat.module.system.dal.mysql.permission.UserRoleMapper; import com.zt.plat.module.system.dal.mysql.rolemenuexclusion.RoleMenuExclusionMapper; +import com.zt.plat.module.system.enums.permission.DataScopeEnum; import com.zt.plat.module.system.enums.permission.RoleTypeEnum; import com.zt.plat.module.system.service.dept.DeptService; import com.zt.plat.module.system.service.user.AdminUserService; @@ -408,4 +411,54 @@ public class PermissionServiceTest extends BaseDbUnitTest { assertEquals(1, exclusionDOS.size()); assertEquals(101L, exclusionDOS.get(0).getMenuId()); } + + @Test + public void testGetUserDataPermissionLevel_noRolesReturnSelf() { + Long userId = 1000L; + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.SELF, result); + } + + @Test + public void testGetUserDataPermissionLevel_pickHighestPriority() { + Long userId = 2000L; + RoleDO roleCustom = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope()) + .setId(110L) + .setTenantId(0L)); + roleMapper.insert(roleCustom); + RoleDO roleCompany = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.COMPANY_AND_DEPT.getScope()) + .setId(120L) + .setTenantId(0L)); + roleMapper.insert(roleCompany); + + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCustom.getId()))); + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleCompany.getId()))); + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.COMPANY_AND_DEPT, result); + } + + @Test + public void testGetUserDataPermissionLevel_serializeAsNumber() { + Long userId = 3000L; + RoleDO roleAll = randomPojo(RoleDO.class, o -> o + .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setDataScope(DataScopeEnum.ALL.getScope()) + .setId(210L) + .setTenantId(0L)); + roleMapper.insert(roleAll); + userRoleMapper.insert(randomPojo(UserRoleDO.class, o -> o.setUserId(userId).setRoleId(roleAll.getId()))); + + DataScopeEnum result = permissionService.getUserDataPermissionLevel(userId); + + assertEquals(DataScopeEnum.ALL, result); + assertEquals("1", JsonUtils.toJsonString(result)); + } }