Commit c24a3a02c28161e91672668037031c44bbf4f597

Authored by YunaiV
2 parents c48f52ec 97096216

Merge branch 'feature/im-dev' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

# Conflicts:
#	sql/mysql/ruoyi-vue-pro.sql
Showing 155 changed files with 2800 additions and 2541 deletions
sql/mysql/ruoyi-vue-pro.sql
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 Target Server Version : 80200 (8.2.0) 11 Target Server Version : 80200 (8.2.0)
12 File Encoding : 65001 12 File Encoding : 65001
13 13
14 - Date: 19/06/2026 18:41:15 14 + Date: 31/05/2026 22:29:18
15 */ 15 */
16 16
17 SET NAMES utf8mb4; 17 SET NAMES utf8mb4;
@@ -49,7 +49,7 @@ CREATE TABLE `infra_api_access_log` ( @@ -49,7 +49,7 @@ CREATE TABLE `infra_api_access_log` (
49 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 49 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
50 PRIMARY KEY (`id`) USING BTREE, 50 PRIMARY KEY (`id`) USING BTREE,
51 INDEX `idx_create_time`(`create_time` ASC) USING BTREE 51 INDEX `idx_create_time`(`create_time` ASC) USING BTREE
52 -) ENGINE = InnoDB AUTO_INCREMENT = 36333 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; 52 +) ENGINE = InnoDB AUTO_INCREMENT = 36301 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表';
53 53
54 -- ---------------------------- 54 -- ----------------------------
55 -- Records of infra_api_access_log 55 -- Records of infra_api_access_log
@@ -92,7 +92,7 @@ CREATE TABLE `infra_api_error_log` ( @@ -92,7 +92,7 @@ CREATE TABLE `infra_api_error_log` (
92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 92 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
93 PRIMARY KEY (`id`) USING BTREE, 93 PRIMARY KEY (`id`) USING BTREE,
94 INDEX `idx_create_time`(`create_time` ASC) USING BTREE 94 INDEX `idx_create_time`(`create_time` ASC) USING BTREE
95 -) ENGINE = InnoDB AUTO_INCREMENT = 24233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; 95 +) ENGINE = InnoDB AUTO_INCREMENT = 23958 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
96 96
97 -- ---------------------------- 97 -- ----------------------------
98 -- Records of infra_api_error_log 98 -- Records of infra_api_error_log
@@ -253,7 +253,7 @@ CREATE TABLE `infra_file` ( @@ -253,7 +253,7 @@ CREATE TABLE `infra_file` (
253 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 253 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
254 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 254 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
255 PRIMARY KEY (`id`) USING BTREE 255 PRIMARY KEY (`id`) USING BTREE
256 -) ENGINE = InnoDB AUTO_INCREMENT = 2260 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; 256 +) ENGINE = InnoDB AUTO_INCREMENT = 2233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
257 257
258 -- ---------------------------- 258 -- ----------------------------
259 -- Records of infra_file 259 -- Records of infra_file
@@ -462,7 +462,7 @@ CREATE TABLE `system_dict_data` ( @@ -462,7 +462,7 @@ CREATE TABLE `system_dict_data` (
462 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 462 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
463 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 463 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
464 PRIMARY KEY (`id`) USING BTREE 464 PRIMARY KEY (`id`) USING BTREE
465 -) ENGINE = InnoDB AUTO_INCREMENT = 1061137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; 465 +) ENGINE = InnoDB AUTO_INCREMENT = 1061122 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
466 466
467 -- ---------------------------- 467 -- ----------------------------
468 -- Records of system_dict_data 468 -- Records of system_dict_data
@@ -1374,14 +1374,14 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -1374,14 +1374,14 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1374 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 (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); 1374 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 (3443, 1, '草稿', '0', 'mes_wm_product_produce_status', 0, 'info', '', '草稿状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0');
1375 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 (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); 1375 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 (3444, 2, '已完成', '4', 'mes_wm_product_produce_status', 0, 'success', '', '已完成状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0');
1376 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 (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0'); 1376 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 (3445, 3, '已取消', '5', 'mes_wm_product_produce_status', 0, 'danger', '', '已取消状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0');
1377 -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 (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, 'default', '', 'PREPARE', '1', '2026-04-16 09:47:00', '1', '2026-06-13 10:32:58', b'0');  
1378 -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 (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, 'success', '', 'FINISHED', '1', '2026-04-16 09:47:00', '1', '2026-06-13 10:32:58', b'0');  
1379 -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 (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, 'danger', '', 'CANCELED', '1', '2026-04-16 09:47:00', '1', '2026-06-13 10:32:58', b'0');  
1380 -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 (3525, 1, '正常', '0', 'im_message_status', 0, 'success', '', '正常(初始状态)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-06-16 03:41:55', b'0');  
1381 -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 (3526, 2, '已撤回', '2', 'im_message_status', 0, 'danger', '', '已撤回', 'admin', '2026-04-30 15:14:36', 'admin', '2026-06-16 03:41:55', b'0');  
1382 -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 (3530, 1, '不需要回执', '0', 'im_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', '2026-04-30 11:35:07', 'admin', '2026-06-14 00:32:47', b'0');  
1383 -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 (3531, 2, '待完成', '1', 'im_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', '2026-04-30 11:35:07', 'admin', '2026-06-14 00:32:47', b'0');  
1384 -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 (3532, 3, '已完成', '2', 'im_message_receipt_status', 0, 'success', '', 'DONE', 'admin', '2026-04-30 11:35:07', 'admin', '2026-06-14 00:32:47', b'0'); 1377 +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 (3446, 0, '草稿', '0', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0');
  1378 +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 (3447, 1, '已完成', '4', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0');
  1379 +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 (3448, 2, '已取消', '5', 'mes_pro_task_status', 0, '', '', NULL, '1', '2026-04-16 09:47:00', '1', '2026-04-16 09:47:00', b'0');
  1380 +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 (3525, 1, '正常', '0', 'im_message_status', 0, 'success', '', '正常(初始状态)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0');
  1381 +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 (3526, 2, '已撤回', '2', 'im_message_status', 0, 'danger', '', '已撤回', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0');
  1382 +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 (3530, 1, '不需要回执', '0', 'im_message_receipt_status', 0, 'info', '', 'NO_RECEIPT', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
  1383 +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 (3531, 2, '待完成', '1', 'im_message_receipt_status', 0, 'warning', '', 'PENDING', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
  1384 +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 (3532, 3, '已完成', '2', 'im_message_receipt_status', 0, 'success', '', 'DONE', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
1385 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 (3540, 1, '正常', '0', 'im_friend_status', 0, 'success', '', '正常好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); 1385 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 (3540, 1, '正常', '0', 'im_friend_status', 0, 'success', '', '正常好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
1386 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 (3541, 2, '已删除', '1', 'im_friend_status', 0, 'danger', '', '已删除好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); 1386 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 (3541, 2, '已删除', '1', 'im_friend_status', 0, 'danger', '', '已删除好友关系', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
1387 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 (3550, 1, '正常', '0', 'im_group_status', 0, 'success', '', '群正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0'); 1387 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 (3550, 1, '正常', '0', 'im_group_status', 0, 'success', '', '群正常', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0');
@@ -1396,39 +1396,39 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -1396,39 +1396,39 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1396 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 (3559, 1, '等待验证', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); 1396 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 (3559, 1, '等待验证', '0', 'im_friend_request_handle_result', 0, 'warning', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0');
1397 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 (3560, 2, '已添加', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); 1397 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 (3560, 2, '已添加', '1', 'im_friend_request_handle_result', 0, 'success', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0');
1398 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 (3561, 3, '已拒绝', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0'); 1398 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 (3561, 3, '已拒绝', '2', 'im_friend_request_handle_result', 0, 'info', '', NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0');
1399 -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 (3562, 101, '文本', '101', 'im_content_type', 0, '', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1400 -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 (3563, 102, '图片', '102', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1401 -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 (3564, 103, '语音', '103', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1402 -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 (3565, 104, '视频', '104', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1403 -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 (3566, 105, '文件', '105', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1404 -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 (3568, 2101, '撤回', '2101', 'im_content_type', 0, 'danger', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1405 -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 (3569, 2200, '回执', '2200', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1406 -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 (3570, 2201, '已读', '2201', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1407 -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 (3571, 1501, '群创建', '1501', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1408 -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 (3572, 1502, '群信息变更', '1502', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1409 -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 (3573, 1503, '收到新的入群申请', '1503', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:47:00', b'0');  
1410 -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 (3574, 1504, '成员退群', '1504', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1411 -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 (3575, 1505, '入群申请被同意', '1505', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:47:00', b'0');  
1412 -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 (3576, 1506, '入群申请被拒绝', '1506', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:47:00', b'0');  
1413 -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 (3577, 1507, '群主转让', '1507', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1414 -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 (3578, 1508, '成员被移出', '1508', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1415 -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 (3579, 1509, '成员加入', '1509', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1416 -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 (3580, 1510, '自由进群', '1510', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1417 -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 (3581, 1511, '群解散', '1511', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1418 -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 (3582, 1512, '成员禁言', '1512', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1419 -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 (3583, 1513, '成员取消禁言', '1513', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1420 -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 (3584, 1514, '全群禁言', '1514', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1421 -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 (3585, 1515, '全群取消禁言', '1515', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1422 -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 (3586, 1516, '成员昵称变更', '1516', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1423 -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 (3587, 1517, '添加管理员', '1517', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1424 -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 (3588, 1518, '撤销管理员', '1518', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1425 -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 (3589, 1519, '群公告变更', '1519', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1426 -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 (3590, 1520, '群名变更', '1520', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1427 -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 (3591, 1531, '群消息置顶', '1531', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1428 -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 (3592, 1532, '群消息取消置顶', '1532', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1429 -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 (3593, 1533, '群封禁变更', '1533', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-06-16 03:41:55', b'0');  
1430 -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 (3594, 1204, '新增好友', '1204', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-06-16 03:41:55', b'0');  
1431 -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 (3595, 1205, '好友被删除', '1205', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-06-16 03:41:55', b'0'); 1399 +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 (3562, 101, '文本', '101', 'im_content_type', 0, '', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1400 +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 (3563, 102, '图片', '102', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1401 +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 (3564, 103, '语音', '103', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1402 +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 (3565, 104, '视频', '104', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1403 +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 (3566, 105, '文件', '105', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1404 +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 (3568, 2101, '撤回', '2101', 'im_content_type', 0, 'danger', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1405 +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 (3569, 2200, '回执', '2200', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1406 +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 (3570, 2201, '已读', '2201', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1407 +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 (3571, 1501, '群创建', '1501', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1408 +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 (3572, 1502, '群信息变更', '1502', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1409 +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 (3573, 1503, '入群申请', '1503', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1410 +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 (3574, 1504, '成员退群', '1504', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1411 +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 (3575, 1505, '入群申请通过', '1505', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1412 +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 (3576, 1506, '入群申请拒绝', '1506', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1413 +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 (3577, 1507, '群主转让', '1507', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1414 +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 (3578, 1508, '成员被移出', '1508', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1415 +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 (3579, 1509, '成员加入', '1509', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1416 +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 (3580, 1510, '自由进群', '1510', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1417 +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 (3581, 1511, '群解散', '1511', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1418 +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 (3582, 1512, '成员禁言', '1512', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1419 +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 (3583, 1513, '成员取消禁言', '1513', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1420 +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 (3584, 1514, '全群禁言', '1514', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1421 +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 (3585, 1515, '全群取消禁言', '1515', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1422 +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 (3586, 1516, '成员昵称变更', '1516', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1423 +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 (3587, 1517, '添加管理员', '1517', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1424 +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 (3588, 1518, '撤销管理员', '1518', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1425 +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 (3589, 1519, '群公告变更', '1519', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1426 +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 (3590, 1520, '群名变更', '1520', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1427 +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 (3591, 1531, '群消息置顶', '1531', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1428 +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 (3592, 1532, '群消息取消置顶', '1532', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1429 +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 (3593, 1533, '群封禁变更', '1533', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 11:52:30', 'admin', '2026-05-05 11:52:30', b'0');
  1430 +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 (3594, 1204, '新增好友', '1204', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0');
  1431 +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 (3595, 1205, '好友被删除', '1205', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-05-05 13:26:53', 'admin', '2026-05-05 13:26:53', b'0');
1432 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 (3596, 1, '搜索', '1', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); 1432 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 (3596, 1, '搜索', '1', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0');
1433 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 (3597, 2, '邀请', '2', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); 1433 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 (3597, 2, '邀请', '2', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0');
1434 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 (3598, 3, '扫码', '3', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0'); 1434 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 (3598, 3, '扫码', '3', 'im_group_add_source', 0, '', '', NULL, '', '2026-05-06 09:26:36', '', '2026-05-06 09:26:36', b'0');
@@ -1474,26 +1474,11 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -1474,26 +1474,11 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1474 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 (1061114, 3, '已拒绝', '30', 'im_rtc_participant_status', 0, 'warning', '', '接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); 1474 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 (1061114, 3, '已拒绝', '30', 'im_rtc_participant_status', 0, 'warning', '', '接通前点拒接', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0');
1475 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 (1061115, 4, '未应答', '40', 'im_rtc_participant_status', 0, 'info', '', '通话已结束仍未应答', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); 1475 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 (1061115, 4, '未应答', '40', 'im_rtc_participant_status', 0, 'info', '', '通话已结束仍未应答', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0');
1476 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 (1061116, 5, '已离开', '50', 'im_rtc_participant_status', 0, 'success', '', '接通后挂断 / 离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0'); 1476 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 (1061116, 5, '已离开', '50', 'im_rtc_participant_status', 0, 'success', '', '接通后挂断 / 离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0');
1477 -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 (1061117, 1610, '通话开始', '1610', 'im_content_type', 0, 'info', '', '入消息流;私聊定向通知,群聊全员广播', 'admin', '2026-05-18 03:36:12', 'admin', '2026-06-16 03:41:55', b'0');  
1478 -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 (1061118, 1611, '通话结束', '1611', 'im_content_type', 0, 'info', '', '入消息流;私聊准气泡,群聊系统 tip', 'admin', '2026-05-18 03:36:12', 'admin', '2026-06-16 03:41:55', b'0');  
1479 -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 (1061119, 125, '素材', '125', 'im_content_type', 0, 'success', '', '频道运营推送的图文卡片消息', '1', '2026-05-18 13:14:34', 'admin', '2026-06-16 03:41:55', b'0'); 1477 +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 (1061117, 1610, '通话开始', '1610', 'im_content_type', 0, 'info', '', '入消息流;私聊定向通知,群聊全员广播', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0');
  1478 +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 (1061118, 1611, '通话结束', '1611', 'im_content_type', 0, 'info', '', '入消息流;私聊准气泡,群聊系统 tip', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0');
  1479 +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 (1061119, 125, '素材', '125', 'im_content_type', 0, 'success', '', '频道运营推送的图文卡片消息', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', b'0');
1480 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 (1061120, 1, '富文本', '1', 'im_channel_material_type', 0, 'primary', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0'); 1480 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 (1061120, 1, '富文本', '1', 'im_channel_material_type', 0, 'primary', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0');
1481 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 (1061121, 2, '外链', '2', 'im_channel_material_type', 0, 'info', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0'); 1481 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 (1061121, 2, '外链', '2', 'im_channel_material_type', 0, 'info', '', '', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0');
1482 -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 (1061122, 7, 'SN 码', '109', 'mes_wm_barcode_biz_type', 0, 'default', '', 'SN', '1', '2026-06-13 10:30:30', '1', '2026-06-13 10:30:30', b'0');  
1483 -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 (1061123, 107, '合并转发', '107', 'im_content_type', 0, '', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1484 -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 (1061124, 108, '名片', '108', 'im_content_type', 0, '', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1485 -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 (1061125, 115, '表情', '115', 'im_content_type', 0, '', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1486 -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 (1061126, 1201, '好友申请被同意', '1201', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1487 -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 (1061127, 1202, '好友申请被拒绝', '1202', 'im_content_type', 0, 'warning', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1488 -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 (1061128, 1203, '收到新的好友申请', '1203', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1489 -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 (1061129, 1207, '加入黑名单', '1207', 'im_content_type', 0, 'danger', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1490 -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 (1061130, 1208, '移出黑名单', '1208', 'im_content_type', 0, 'success', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1491 -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 (1061131, 1209, '好友资料变更', '1209', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1492 -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 (1061132, 1210, '好友信息批量更新', '1210', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1493 -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 (1061133, 1530, '群成员个人设置变更', '1530', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1494 -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 (1061134, 1601, '通话信令', '1601', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1495 -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 (1061135, 1602, '通话参与者加入', '1602', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1496 -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 (1061136, 1603, '通话参与者离开', '1603', 'im_content_type', 0, 'info', '', NULL, 'admin', '2026-06-16 03:47:00', 'admin', '2026-06-16 03:47:00', b'0');  
1497 COMMIT; 1482 COMMIT;
1498 1483
1499 -- ---------------------------- 1484 -- ----------------------------
@@ -1651,7 +1636,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat @@ -1651,7 +1636,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
1651 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2029, 'MES 工单类型', 'mes_pro_work_order_type', 0, 'MES 生产管理 - 工单类型(1=自行生产,2=代工,3=采购)', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', b'0', NULL); 1636 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2029, 'MES 工单类型', 'mes_pro_work_order_type', 0, 'MES 生产管理 - 工单类型(1=自行生产,2=代工,3=采购)', '1', '2026-02-17 11:43:47', '1', '2026-02-17 11:43:47', b'0', NULL);
1652 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2036, 'MES 工序关系类型', 'mes_pro_link_type', 0, '工艺路线中工序之间的关系类型', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:05:07', b'0', NULL); 1637 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2036, 'MES 工序关系类型', 'mes_pro_link_type', 0, '工艺路线中工序之间的关系类型', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:05:07', b'0', NULL);
1653 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2037, 'MES 时间单位', 'mes_time_unit_type', 0, '生产时间的计量单位', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:04:57', b'0', NULL); 1638 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2037, 'MES 时间单位', 'mes_time_unit_type', 0, '生产时间的计量单位', '1', '2026-02-19 04:24:53', '1', '2026-04-05 15:04:57', b'0', NULL);
1654 -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2038, 'MES 生产任务状态', 'mes_pro_task_status', 0, 'MES 生产管理 - 任务状态(0=草稿,4=已完成,5=已取消)', '1', '2026-02-19 15:25:27', '1', '2026-06-13 10:32:58', b'0', NULL); 1639 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2038, 'MES 生产任务状态', 'mes_pro_task_status', 0, 'MES 生产管理 - 任务状态(0=草稿,1=进行中,2=暂停,3=已完成,4=已取消)', '1', '2026-02-19 15:25:27', '1', '2026-02-19 15:25:27', b'0', NULL);
1655 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2039, 'MES 点检保养项目类型', 'mes_dv_subject_type', 0, 'MES 设备管理 - 点检保养项目类型(1=设备点检,2=设备保养)', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', b'0', NULL); 1640 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2039, 'MES 点检保养项目类型', 'mes_dv_subject_type', 0, 'MES 设备管理 - 点检保养项目类型(1=设备点检,2=设备保养)', '1', '2026-02-20 01:42:58', '1', '2026-02-20 01:42:58', b'0', NULL);
1656 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2040, 'MES 保养记录状态', 'mes_mainten_record_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', b'0', NULL); 1641 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2040, 'MES 保养记录状态', 'mes_mainten_record_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', b'0', NULL);
1657 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2041, 'MES 保养结果', 'mes_mainten_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', b'0', NULL); 1642 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2041, 'MES 保养结果', 'mes_mainten_status', 0, NULL, 'admin', '2026-02-20 02:59:55', 'admin', '2026-02-20 02:59:55', b'0', NULL);
@@ -1704,11 +1689,11 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat @@ -1704,11 +1689,11 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
1704 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2133, 'MES 销售退货单状态', 'mes_wm_return_sales_status', 0, 'MES 销售退货单状态枚举', '1', '2026-04-03 17:20:25', '1', '2026-04-05 15:05:39', b'0', NULL); 1689 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2133, 'MES 销售退货单状态', 'mes_wm_return_sales_status', 0, 'MES 销售退货单状态枚举', '1', '2026-04-03 17:20:25', '1', '2026-04-05 15:05:39', b'0', NULL);
1705 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2135, 'MES 上下工状态类型', 'mes_pro_work_record_type', 0, 'MES 上下工状态类型', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', b'0', NULL); 1690 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2135, 'MES 上下工状态类型', 'mes_pro_work_record_type', 0, 'MES 上下工状态类型', '1', '2026-04-05 14:07:27', '1', '2026-04-05 14:07:27', b'0', NULL);
1706 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2138, 'MES 生产入库单状态', 'mes_wm_product_produce_status', 0, 'MES 生产入库单状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0', '1970-01-01 00:00:00'); 1691 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2138, 'MES 生产入库单状态', 'mes_wm_product_produce_status', 0, 'MES 生产入库单状态', '1', '2026-04-05 15:53:46', '1', '2026-04-05 15:53:46', b'0', '1970-01-01 00:00:00');
1707 -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2200, 'IM 内容类型', 'im_content_type', 0, '对应 ImContentTypeEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-06-16 03:41:55', b'0', NULL);  
1708 -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2202, 'IM 消息回执状态', 'im_message_receipt_status', 0, '对应 ImMessageReceiptStatusEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-06-16 03:41:55', b'0', NULL); 1692 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2200, 'IM 内容类型', 'im_content_type', 0, '对应 ImContentTypeEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL);
  1693 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2202, 'IM 消息回执状态', 'im_message_receipt_status', 0, '对应 ImMessageReceiptStatusEnum', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL);
1709 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2203, 'IM 好友状态', 'im_friend_status', 0, '0=正常 / 1=已删除', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); 1694 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2203, 'IM 好友状态', 'im_friend_status', 0, '0=正常 / 1=已删除', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL);
1710 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2204, 'IM 群状态', 'im_group_status', 0, '0=正常 / 1=已解散', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL); 1695 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2204, 'IM 群状态', 'im_group_status', 0, '0=正常 / 1=已解散', 'admin', '2026-04-30 11:35:07', 'admin', '2026-04-30 11:35:07', b'0', NULL);
1711 -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2205, 'IM 消息状态', 'im_message_status', 0, '对应 ImMessageStatusEnum;0=正常 / 2=已撤回(私聊 / 群聊共用)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-06-16 03:41:55', b'0', NULL); 1696 +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2205, 'IM 消息状态', 'im_message_status', 0, '对应 ImMessageStatusEnum;0=正常 / 2=已撤回(私聊 / 群聊共用)', 'admin', '2026-04-30 15:14:36', 'admin', '2026-04-30 15:14:36', b'0', NULL);
1712 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2206, 'IM 群成员角色', 'im_group_member_role', 0, NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', b'0', NULL); 1697 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2206, 'IM 群成员角色', 'im_group_member_role', 0, NULL, '1', '2026-05-02 02:14:12', '1', '2026-05-02 02:14:12', b'0', NULL);
1713 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2207, 'IM 好友添加来源', 'im_friend_add_source', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL); 1698 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2207, 'IM 好友添加来源', 'im_friend_add_source', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL);
1714 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2208, 'IM 好友申请处理结果', 'im_friend_request_handle_result', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL); 1699 INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (2208, 'IM 好友申请处理结果', 'im_friend_request_handle_result', 0, NULL, '1', '2026-05-04 02:43:41', '1', '2026-05-04 02:43:41', b'0', NULL);
@@ -1751,7 +1736,7 @@ CREATE TABLE `system_login_log` ( @@ -1751,7 +1736,7 @@ CREATE TABLE `system_login_log` (
1751 PRIMARY KEY (`id`) USING BTREE, 1736 PRIMARY KEY (`id`) USING BTREE,
1752 INDEX `idx_username`(`username` ASC) USING BTREE, 1737 INDEX `idx_username`(`username` ASC) USING BTREE,
1753 INDEX `idx_create_time`(`create_time` ASC) USING BTREE 1738 INDEX `idx_create_time`(`create_time` ASC) USING BTREE
1754 -) ENGINE = InnoDB AUTO_INCREMENT = 5226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; 1739 +) ENGINE = InnoDB AUTO_INCREMENT = 4697 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
1755 1740
1756 -- ---------------------------- 1741 -- ----------------------------
1757 -- Records of system_login_log 1742 -- Records of system_login_log
@@ -2229,7 +2214,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i @@ -2229,7 +2214,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
2229 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 (2156, '查询项目', 'report:go-view-project:query', 3, 0, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:25:53', '1', '2023-02-07 19:25:53', b'0'); 2214 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 (2156, '查询项目', 'report:go-view-project:query', 3, 0, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:25:53', '1', '2023-02-07 19:25:53', b'0');
2230 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 (2157, '使用 SQL 查询数据', 'report:go-view-data:get-by-sql', 3, 3, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:26:15', '1', '2023-02-07 19:26:15', b'0'); 2215 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 (2157, '使用 SQL 查询数据', 'report:go-view-data:get-by-sql', 3, 3, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:26:15', '1', '2023-02-07 19:26:15', b'0');
2231 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 (2158, '使用 HTTP 查询数据', 'report:go-view-data:get-by-http', 3, 4, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:26:35', '1', '2023-02-07 19:26:35', b'0'); 2216 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 (2158, '使用 HTTP 查询数据', 'report:go-view-data:get-by-http', 3, 4, 2153, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2023-02-07 19:26:35', '1', '2023-02-07 19:26:35', b'0');
2232 -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 (2159, 'Boot 开发文档', '', 1, 1, 0, 'https://doc.iocoder.cn/', 'ep:document', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-02-10 22:46:28', '1', '2026-06-12 00:05:19', b'0'); 2217 +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 (2159, 'Boot 开发文档', '', 1, 1, 0, 'https://doc.iocoder.cn/', 'ep:document', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-02-10 22:46:28', '1', '2026-01-05 19:31:07', b'0');
2233 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 (2160, 'Cloud 开发文档', '', 1, 2, 0, 'https://cloud.iocoder.cn', 'ep:document-copy', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-02-10 22:47:07', '1', '2023-12-02 21:32:29', b'0'); 2218 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 (2160, 'Cloud 开发文档', '', 1, 2, 0, 'https://cloud.iocoder.cn', 'ep:document-copy', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-02-10 22:47:07', '1', '2023-12-02 21:32:29', b'0');
2234 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 (2161, '接入示例', '', 1, 99, 1117, 'demo', 'fa-solid:dragon', 'pay/demo/index', NULL, 0, b'1', b'1', b'1', '', '2023-02-11 14:21:42', '1', '2024-01-18 23:50:00', b'0'); 2219 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 (2161, '接入示例', '', 1, 99, 1117, 'demo', 'fa-solid:dragon', 'pay/demo/index', NULL, 0, b'1', b'1', b'1', '', '2023-02-11 14:21:42', '1', '2024-01-18 23:50:00', b'0');
2235 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 (2162, '商品导出', 'product:spu:export', 3, 5, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0'); 2220 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 (2162, '商品导出', 'product:spu:export', 3, 5, 2014, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
@@ -3453,7 +3438,7 @@ CREATE TABLE `system_oauth2_access_token` ( @@ -3453,7 +3438,7 @@ CREATE TABLE `system_oauth2_access_token` (
3453 PRIMARY KEY (`id`) USING BTREE, 3438 PRIMARY KEY (`id`) USING BTREE,
3454 INDEX `idx_access_token`(`access_token` ASC) USING BTREE, 3439 INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
3455 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE 3440 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
3456 -) ENGINE = InnoDB AUTO_INCREMENT = 107056 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; 3441 +) ENGINE = InnoDB AUTO_INCREMENT = 57395 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
3457 3442
3458 -- ---------------------------- 3443 -- ----------------------------
3459 -- Records of system_oauth2_access_token 3444 -- Records of system_oauth2_access_token
@@ -3526,7 +3511,7 @@ BEGIN; @@ -3526,7 +3511,7 @@ BEGIN;
3526 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\",\"client_credentials\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-12-07 20:07:09', b'0'); 3511 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, 'default', 'admin123', '芋道源码', 'http://test.yudao.iocoder.cn/20250502/sort2_1746189740718.png', '我是描述', 0, 1800, 2592000, '[\"https://www.iocoder.cn\",\"https://doc.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\",\"refresh_token\",\"client_credentials\"]', '[\"user.read\",\"user.write\"]', '[]', '[\"user.read\",\"user.write\"]', '[]', '{}', '1', '2022-05-11 21:47:12', '1', '2025-12-07 20:07:09', b'0');
3527 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/20251227/javayuanma_1766829882970.jpg', '啦啦啦啦', 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-12-27 18:04:44', b'0'); 3512 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (40, 'test', 'test2', 'biubiu', 'http://test.yudao.iocoder.cn/20251227/javayuanma_1766829882970.jpg', '啦啦啦啦', 0, 1800, 43200, '[\"https://www.iocoder.cn\"]', '[\"password\",\"authorization_code\",\"implicit\"]', '[\"user_info\",\"projects\"]', '[\"user_info\"]', '[]', '[]', '{}', '1', '2022-05-12 00:28:20', '1', '2025-12-27 18:04:44', b'0');
3528 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', b'0'); 3513 INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (41, 'yudao-sso-demo-by-code', 'test', '基于授权码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/it/20250502/sign_1746181948685.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"authorization_code\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-09-29 13:28:31', '1', '2025-05-02 18:32:30', b'0');
3529 -INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/oauth2/20260615/iShot_2026-05-31_23.18.10.png', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2026-06-15 18:58:30', b'0'); 3514 +INSERT INTO `system_oauth2_client` (`id`, `client_id`, `secret`, `name`, `logo`, `description`, `status`, `access_token_validity_seconds`, `refresh_token_validity_seconds`, `redirect_uris`, `authorized_grant_types`, `scopes`, `auto_approve_scopes`, `authorities`, `resource_ids`, `additional_information`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (42, 'yudao-sso-demo-by-password', 'test', '基于密码模式,如何实现 SSO 单点登录?', 'http://test.yudao.iocoder.cn/20251025/images (3)_1761360515810.jpeg', NULL, 0, 1800, 43200, '[\"http://127.0.0.1:18080\"]', '[\"password\",\"refresh_token\"]', '[\"user.read\",\"user.write\"]', '[]', '[]', '[]', NULL, '1', '2022-10-04 17:40:16', '1', '2025-10-25 10:49:40', b'0');
3530 COMMIT; 3515 COMMIT;
3531 3516
3532 -- ---------------------------- 3517 -- ----------------------------
@@ -3579,7 +3564,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( @@ -3579,7 +3564,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
3579 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 3564 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3580 PRIMARY KEY (`id`) USING BTREE, 3565 PRIMARY KEY (`id`) USING BTREE,
3581 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE 3566 INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
3582 -) ENGINE = InnoDB AUTO_INCREMENT = 3244 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; 3567 +) ENGINE = InnoDB AUTO_INCREMENT = 2728 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
3583 3568
3584 -- ---------------------------- 3569 -- ----------------------------
3585 -- Records of system_oauth2_refresh_token 3570 -- Records of system_oauth2_refresh_token
@@ -3615,7 +3600,7 @@ CREATE TABLE `system_operate_log` ( @@ -3615,7 +3600,7 @@ CREATE TABLE `system_operate_log` (
3615 PRIMARY KEY (`id`) USING BTREE, 3600 PRIMARY KEY (`id`) USING BTREE,
3616 INDEX `idx_user_id`(`user_id` ASC) USING BTREE, 3601 INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
3617 INDEX `idx_create_time`(`create_time` ASC) USING BTREE 3602 INDEX `idx_create_time`(`create_time` ASC) USING BTREE
3618 -) ENGINE = InnoDB AUTO_INCREMENT = 9207 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; 3603 +) ENGINE = InnoDB AUTO_INCREMENT = 9195 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本';
3619 3604
3620 -- ---------------------------- 3605 -- ----------------------------
3621 -- Records of system_operate_log 3606 -- Records of system_operate_log
@@ -3641,7 +3626,7 @@ CREATE TABLE `system_post` ( @@ -3641,7 +3626,7 @@ CREATE TABLE `system_post` (
3641 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 3626 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
3642 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 3627 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3643 PRIMARY KEY (`id`) USING BTREE 3628 PRIMARY KEY (`id`) USING BTREE
3644 -) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表'; 3629 +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表';
3645 3630
3646 -- ---------------------------- 3631 -- ----------------------------
3647 -- Records of system_post 3632 -- Records of system_post
@@ -3685,7 +3670,7 @@ INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_sco @@ -3685,7 +3670,7 @@ INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_sco
3685 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', '2024-02-24 02:51:32', b'0', 1); 3670 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 'CRM 管理员', 'crm_admin', 2, 1, '', 0, 1, 'CRM 专属角色', '1', '2024-02-24 10:51:13', '1', '2024-02-24 02:51:32', b'0', 1);
3686 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121); 3671 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-02-22 00:56:14', '1', '2022-02-22 00:56:14', b'0', 121);
3687 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122); 3672 INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, '租户管理员', 'tenant_admin', 0, 1, '', 0, 1, '系统自动生成', '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
3688 -INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (155, '测试数据权限12', 'test-dp', 4, 2, '[112,100,102,103,104,105,107,108]', 0, 2, '1111', '1', '2025-03-31 14:58:06', '1', '2026-06-07 01:37:53', b'0', 1); 3673 +INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_scope_dept_ids`, `status`, `type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (155, '测试数据权限1', 'test-dp', 4, 2, '[112,100,102,103,104,105,107,108]', 0, 2, '1111', '1', '2025-03-31 14:58:06', '1', '2025-12-04 23:29:40', b'0', 1);
3689 COMMIT; 3674 COMMIT;
3690 3675
3691 -- ---------------------------- 3676 -- ----------------------------
@@ -3704,7 +3689,7 @@ CREATE TABLE `system_role_menu` ( @@ -3704,7 +3689,7 @@ CREATE TABLE `system_role_menu` (
3704 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 3689 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
3705 PRIMARY KEY (`id`) USING BTREE, 3690 PRIMARY KEY (`id`) USING BTREE,
3706 INDEX `idx_role_id`(`role_id` ASC) USING BTREE 3691 INDEX `idx_role_id`(`role_id` ASC) USING BTREE
3707 -) ENGINE = InnoDB AUTO_INCREMENT = 6692 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表'; 3692 +) ENGINE = InnoDB AUTO_INCREMENT = 6381 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
3708 3693
3709 -- ---------------------------- 3694 -- ----------------------------
3710 -- Records of system_role_menu 3695 -- Records of system_role_menu
@@ -3744,6 +3729,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -3744,6 +3729,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
3744 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (706, 2, 112, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); 3729 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (706, 2, 112, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1);
3745 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (707, 2, 113, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1); 3730 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (707, 2, 113, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0', 1);
3746 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1296, 110, 1, '110', '2022-02-23 00:23:55', '110', '2022-02-23 00:23:55', b'0', 121); 3731 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1296, 110, 1, '110', '2022-02-23 00:23:55', '110', '2022-02-23 00:23:55', b'0', 121);
  3732 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1578, 111, 1, '1', '2022-03-07 21:37:58', '1', '2022-03-07 21:37:58', b'0', 122);
3747 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1729, 109, 100, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3733 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1729, 109, 100, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
3748 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1730, 109, 101, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3734 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1730, 109, 101, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
3749 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1731, 109, 1063, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3735 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1731, 109, 1063, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
@@ -3761,6 +3747,23 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -3761,6 +3747,23 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
3761 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1743, 109, 1010, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3747 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1743, 109, 1010, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
3762 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1744, 109, 1011, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3748 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1744, 109, 1011, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
3763 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1745, 109, 1012, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121); 3749 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1745, 109, 1012, '1', '2022-09-21 22:08:51', '1', '2022-09-21 22:08:51', b'0', 121);
  3750 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1746, 111, 100, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3751 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1747, 111, 101, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3752 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1748, 111, 1063, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3753 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1749, 111, 1064, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3754 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1750, 111, 1001, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3755 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1751, 111, 1065, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3756 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1752, 111, 1002, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3757 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1753, 111, 1003, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3758 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1754, 111, 1004, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3759 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1755, 111, 1005, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3760 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1756, 111, 1006, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3761 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1757, 111, 1007, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3762 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1758, 111, 1008, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3763 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1759, 111, 1009, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3764 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1760, 111, 1010, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3765 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1761, 111, 1011, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
  3766 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1762, 111, 1012, '1', '2022-09-21 22:08:52', '1', '2022-09-21 22:08:52', b'0', 122);
3764 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1763, 109, 100, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3767 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1763, 109, 100, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
3765 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1764, 109, 101, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3768 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1764, 109, 101, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
3766 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1765, 109, 1063, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3769 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1765, 109, 1063, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
@@ -3778,6 +3781,23 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -3778,6 +3781,23 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
3778 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1777, 109, 1010, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3781 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1777, 109, 1010, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
3779 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1778, 109, 1011, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3782 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1778, 109, 1011, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
3780 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1779, 109, 1012, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121); 3783 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1779, 109, 1012, '1', '2022-09-21 22:08:53', '1', '2022-09-21 22:08:53', b'0', 121);
  3784 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1780, 111, 100, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3785 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1781, 111, 101, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3786 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1782, 111, 1063, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3787 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1783, 111, 1064, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3788 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1784, 111, 1001, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3789 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1785, 111, 1065, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3790 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1786, 111, 1002, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3791 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1787, 111, 1003, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3792 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1788, 111, 1004, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3793 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1789, 111, 1005, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3794 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1790, 111, 1006, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3795 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1791, 111, 1007, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3796 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1792, 111, 1008, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3797 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1793, 111, 1009, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3798 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1794, 111, 1010, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3799 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1795, 111, 1011, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
  3800 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1796, 111, 1012, '1', '2022-09-21 22:08:54', '1', '2022-09-21 22:08:54', b'0', 122);
3781 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1797, 109, 100, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3801 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1797, 109, 100, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
3782 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1798, 109, 101, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3802 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1798, 109, 101, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
3783 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1799, 109, 1063, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3803 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1799, 109, 1063, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
@@ -3795,16 +3815,43 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -3795,16 +3815,43 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
3795 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1811, 109, 1010, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3815 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1811, 109, 1010, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
3796 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1812, 109, 1011, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3816 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1812, 109, 1011, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
3797 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1813, 109, 1012, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121); 3817 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1813, 109, 1012, '1', '2022-09-21 22:08:55', '1', '2022-09-21 22:08:55', b'0', 121);
  3818 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1814, 111, 100, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3819 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1815, 111, 101, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3820 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1816, 111, 1063, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3821 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1817, 111, 1064, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3822 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1818, 111, 1001, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3823 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1819, 111, 1065, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3824 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1820, 111, 1002, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3825 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1821, 111, 1003, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3826 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1822, 111, 1004, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3827 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1823, 111, 1005, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3828 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1824, 111, 1006, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3829 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1825, 111, 1007, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3830 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1826, 111, 1008, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3831 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1827, 111, 1009, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3832 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1828, 111, 1010, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3833 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1829, 111, 1011, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
  3834 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1830, 111, 1012, '1', '2022-09-21 22:08:56', '1', '2022-09-21 22:08:56', b'0', 122);
3798 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1831, 109, 103, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); 3835 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1831, 109, 103, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121);
3799 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1832, 109, 1017, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); 3836 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1832, 109, 1017, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121);
3800 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1833, 109, 1018, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); 3837 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1833, 109, 1018, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121);
3801 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1834, 109, 1019, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); 3838 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1834, 109, 1019, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121);
3802 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1835, 109, 1020, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121); 3839 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1835, 109, 1020, '1', '2022-09-21 22:43:23', '1', '2022-09-21 22:43:23', b'0', 121);
  3840 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1836, 111, 103, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122);
  3841 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1837, 111, 1017, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122);
  3842 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1838, 111, 1018, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122);
  3843 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1839, 111, 1019, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122);
  3844 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1840, 111, 1020, '1', '2022-09-21 22:43:24', '1', '2022-09-21 22:43:24', b'0', 122);
3803 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1841, 109, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); 3845 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1841, 109, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121);
3804 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1842, 109, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); 3846 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1842, 109, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121);
3805 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1843, 109, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); 3847 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1843, 109, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121);
3806 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1844, 109, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); 3848 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1844, 109, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121);
3807 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1845, 109, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121); 3849 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1845, 109, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 121);
  3850 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1846, 111, 1036, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122);
  3851 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1847, 111, 1037, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122);
  3852 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1848, 111, 1038, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122);
  3853 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1849, 111, 1039, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122);
  3854 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1850, 111, 107, '1', '2022-09-21 22:48:13', '1', '2022-09-21 22:48:13', b'0', 122);
3808 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 3855 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
3809 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 3856 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
3810 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1); 3857 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
@@ -3974,6 +4021,18 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -3974,6 +4021,18 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
3974 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2938, 109, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); 4021 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2938, 109, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121);
3975 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2939, 109, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); 4022 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2939, 109, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121);
3976 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2940, 109, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121); 4023 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2940, 109, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 121);
  4024 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2941, 111, 1224, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4025 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2942, 111, 1225, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4026 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2943, 111, 1226, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4027 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2944, 111, 1227, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4028 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2945, 111, 1228, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4029 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2946, 111, 1229, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4030 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2947, 111, 1138, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4031 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2948, 111, 1139, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4032 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2949, 111, 1140, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4033 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2950, 111, 1141, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4034 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2951, 111, 1142, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
  4035 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2952, 111, 1143, '1', '2023-12-02 23:19:40', '1', '2023-12-02 23:19:40', b'0', 122);
3977 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2993, 109, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4036 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2993, 109, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
3978 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2994, 109, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4037 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2994, 109, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
3979 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2995, 109, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4038 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2995, 109, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
@@ -4049,11 +4108,91 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4049,11 +4108,91 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4049 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3066, 109, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4108 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3066, 109, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
4050 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3067, 109, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4109 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3067, 109, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
4051 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3068, 109, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121); 4110 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3068, 109, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 121);
  4111 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3069, 111, 2, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4112 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3070, 111, 1031, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4113 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3071, 111, 1032, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4114 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3072, 111, 1033, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4115 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3073, 111, 1034, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4116 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3074, 111, 1035, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4117 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3075, 111, 1050, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4118 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3076, 111, 1051, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4119 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3077, 111, 1052, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4120 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3078, 111, 1053, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4121 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3079, 111, 1054, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4122 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3080, 111, 1056, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4123 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3081, 111, 1057, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4124 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3082, 111, 1058, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4125 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3083, 111, 1059, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4126 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3084, 111, 1060, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4127 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3085, 111, 1066, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4128 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3086, 111, 1067, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4129 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3087, 111, 1070, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4130 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3088, 111, 1075, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4131 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3090, 111, 1077, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4132 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3091, 111, 1078, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4133 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3092, 111, 1082, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4134 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3093, 111, 1083, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4135 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3094, 111, 1084, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4136 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3095, 111, 1085, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4137 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3096, 111, 1086, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4138 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3097, 111, 1087, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4139 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3098, 111, 1088, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4140 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3099, 111, 1089, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4141 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3100, 111, 1090, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4142 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3101, 111, 1091, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4143 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3102, 111, 1092, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4144 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3103, 111, 106, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4145 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3104, 111, 110, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4146 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3105, 111, 111, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4147 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3106, 111, 112, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4148 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3107, 111, 113, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4149 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3108, 111, 114, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4150 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3109, 111, 115, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4151 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3110, 111, 116, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4152 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3111, 111, 2472, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4153 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3112, 111, 2478, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4154 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3113, 111, 2479, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4155 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3114, 111, 2480, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4156 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3115, 111, 2481, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4157 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3116, 111, 2482, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4158 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3117, 111, 2483, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4159 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3118, 111, 2484, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4160 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3119, 111, 2485, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4161 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3120, 111, 2486, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4162 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3121, 111, 2487, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4163 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3122, 111, 2488, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4164 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3123, 111, 2489, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4165 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3124, 111, 2490, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4166 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3125, 111, 2491, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4167 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3126, 111, 2492, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4168 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3127, 111, 2493, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4169 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3128, 111, 2494, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4170 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3129, 111, 2495, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4171 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3130, 111, 2497, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4172 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3131, 111, 1237, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4173 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3132, 111, 1238, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4174 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3133, 111, 1239, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4175 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3134, 111, 1240, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4176 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3135, 111, 1241, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4177 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3136, 111, 1242, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4178 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3137, 111, 1243, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4179 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3138, 111, 2525, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4180 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3139, 111, 1255, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4181 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3140, 111, 1256, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4182 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3141, 111, 1257, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4183 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3142, 111, 1258, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4184 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3143, 111, 1259, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
  4185 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3144, 111, 1260, '1', '2023-12-02 23:41:02', '1', '2023-12-02 23:41:02', b'0', 122);
4052 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3221, 109, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); 4186 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3221, 109, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121);
4053 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3222, 109, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); 4187 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3222, 109, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121);
4054 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3223, 109, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); 4188 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3223, 109, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121);
4055 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3224, 109, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); 4189 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3224, 109, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121);
4056 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3225, 109, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121); 4190 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3225, 109, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 121);
  4191 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3226, 111, 102, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122);
  4192 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3227, 111, 1013, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122);
  4193 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3228, 111, 1014, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122);
  4194 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3229, 111, 1015, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122);
  4195 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3230, 111, 1016, '1', '2023-12-30 11:42:36', '1', '2023-12-30 11:42:36', b'0', 122);
4057 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4163, 109, 5, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4196 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4163, 109, 5, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
4058 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4164, 109, 1118, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4197 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4164, 109, 1118, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
4059 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4165, 109, 1119, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4198 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4165, 109, 1119, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
@@ -4112,6 +4251,64 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4112,6 +4251,64 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4112 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4219, 109, 1220, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4251 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4219, 109, 1220, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
4113 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4220, 109, 1221, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4252 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4220, 109, 1221, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
4114 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4221, 109, 1222, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121); 4253 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4221, 109, 1222, '1', '2024-03-30 17:53:17', '1', '2024-03-30 17:53:17', b'0', 121);
  4254 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4222, 111, 5, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4255 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4223, 111, 1118, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4256 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4224, 111, 1119, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4257 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4225, 111, 1120, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4258 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4226, 111, 2713, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4259 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4227, 111, 2714, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4260 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4228, 111, 2715, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4261 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4229, 111, 2716, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4262 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4230, 111, 2717, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4263 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4231, 111, 2718, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4264 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4232, 111, 2720, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4265 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4233, 111, 1185, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4266 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4234, 111, 2721, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4267 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4235, 111, 1186, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4268 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4236, 111, 2722, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4269 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4237, 111, 1187, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4270 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4238, 111, 2723, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4271 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4239, 111, 1188, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4272 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4240, 111, 2724, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4273 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4241, 111, 1189, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4274 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4242, 111, 2725, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4275 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4243, 111, 1190, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4276 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4244, 111, 2726, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4277 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4245, 111, 1191, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4278 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4246, 111, 2727, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4279 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4247, 111, 1192, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4280 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4248, 111, 2728, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4281 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4249, 111, 1193, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4282 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4250, 111, 2729, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4283 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4251, 111, 1194, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4284 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4252, 111, 2730, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4285 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4253, 111, 1195, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4286 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4254, 111, 2731, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4287 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4256, 111, 2732, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4288 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4257, 111, 1197, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4289 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4258, 111, 2733, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4290 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4259, 111, 1198, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4291 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4260, 111, 2734, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4292 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4261, 111, 1199, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4293 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4262, 111, 2735, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4294 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4263, 111, 1200, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4295 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4264, 111, 1201, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4296 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4265, 111, 1202, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4297 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4266, 111, 1207, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4298 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4267, 111, 1208, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4299 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4268, 111, 1209, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4300 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4269, 111, 1210, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4301 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4270, 111, 1211, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4302 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4271, 111, 1212, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4303 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4272, 111, 1213, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4304 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4273, 111, 1215, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4305 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4274, 111, 1216, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4306 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4275, 111, 1217, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4307 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4276, 111, 1218, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4308 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4277, 111, 1219, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4309 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4278, 111, 1220, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4310 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4279, 111, 1221, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
  4311 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4280, 111, 1222, '1', '2024-03-30 17:53:18', '1', '2024-03-30 17:53:18', b'0', 122);
4115 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5779, 2, 2739, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); 4312 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5779, 2, 2739, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1);
4116 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5780, 2, 2740, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); 4313 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5780, 2, 2740, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1);
4117 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5781, 2, 2758, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); 4314 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5781, 2, 2758, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1);
@@ -4121,6 +4318,8 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4121,6 +4318,8 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4121 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5785, 2, 2030, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1); 4318 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5785, 2, 2030, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0', 1);
4122 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5789, 109, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121); 4319 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5789, 109, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121);
4123 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5790, 109, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121); 4320 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5790, 109, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 121);
  4321 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5791, 111, 2739, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 122);
  4322 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5792, 111, 2740, '1', '2024-07-13 22:37:24', '1', '2024-07-13 22:37:24', b'0', 122);
4124 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6053, 155, 4000, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', b'0', 1); 4323 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6053, 155, 4000, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', b'0', 1);
4125 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6097, 155, 4050, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', b'0', 1); 4324 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6097, 155, 4050, '1', '2025-04-01 13:48:26', '1', '2025-04-01 13:48:26', b'0', 1);
4126 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6104, 155, 4032, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', b'0', 1); 4325 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6104, 155, 4032, '1', '2025-04-01 13:49:30', '1', '2025-04-01 13:49:30', b'0', 1);
@@ -4195,6 +4394,45 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4195,6 +4394,45 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4195 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6175, 109, 2557, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121); 4394 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6175, 109, 2557, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121);
4196 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6176, 109, 2558, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121); 4395 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6176, 109, 2558, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121);
4197 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6177, 109, 2559, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121); 4396 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6177, 109, 2559, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 121);
  4397 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6178, 111, 1117, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4398 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6179, 111, 1126, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4399 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6180, 111, 1127, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4400 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6181, 111, 1128, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4401 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6182, 111, 1129, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4402 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6183, 111, 1130, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4403 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6184, 111, 1132, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4404 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6185, 111, 1133, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4405 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6186, 111, 1134, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4406 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6187, 111, 1135, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4407 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6188, 111, 1136, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4408 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6189, 111, 1137, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4409 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6190, 111, 2161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4410 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6191, 111, 1150, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4411 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6192, 111, 1161, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4412 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6193, 111, 1162, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4413 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6194, 111, 1166, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4414 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6195, 111, 1173, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4415 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6196, 111, 1174, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4416 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6197, 111, 1178, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4417 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6198, 111, 2745, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4418 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6199, 111, 2746, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4419 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6200, 111, 2747, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4420 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6201, 111, 2748, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4421 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6202, 111, 2301, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4422 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6203, 111, 2302, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4423 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6204, 111, 5011, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4424 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6205, 111, 5012, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4425 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6206, 111, 2549, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4426 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6207, 111, 2550, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4427 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6208, 111, 2551, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4428 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6209, 111, 2552, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4429 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6210, 111, 2553, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4430 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6211, 111, 2554, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4431 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6212, 111, 2555, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4432 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6213, 111, 2556, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4433 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6214, 111, 2557, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4434 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6215, 111, 2558, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
  4435 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6216, 111, 2559, '1', '2025-09-06 20:52:12', '1', '2025-09-06 20:52:12', b'0', 122);
4198 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6217, 109, 2756, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4436 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6217, 109, 2756, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
4199 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6218, 109, 2757, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4437 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6218, 109, 2757, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
4200 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6219, 109, 2262, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4438 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6219, 109, 2262, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
@@ -4233,6 +4471,44 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4233,6 +4471,44 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4233 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6252, 109, 2335, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4471 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6252, 109, 2335, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
4234 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6253, 109, 2363, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4472 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6253, 109, 2363, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
4235 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6254, 109, 2364, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121); 4473 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6254, 109, 2364, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 121);
  4474 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6255, 111, 2756, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4475 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6256, 111, 2757, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4476 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6257, 111, 2262, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4477 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6258, 111, 2275, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4478 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6259, 111, 2276, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4479 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6260, 111, 2277, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4480 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6261, 111, 2281, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4481 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6262, 111, 2282, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4482 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6263, 111, 2283, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4483 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6264, 111, 2284, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4484 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6265, 111, 2285, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4485 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6266, 111, 2287, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4486 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6267, 111, 2288, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4487 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6268, 111, 2293, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4488 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6269, 111, 2294, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4489 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6270, 111, 2297, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4490 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6271, 111, 2300, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4491 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6272, 111, 2317, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4492 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6273, 111, 2318, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4493 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6274, 111, 2319, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4494 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6275, 111, 2320, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4495 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6276, 111, 2321, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4496 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6277, 111, 2322, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4497 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6278, 111, 2323, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4498 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6279, 111, 2324, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4499 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6280, 111, 2325, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4500 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6281, 111, 2326, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4501 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6282, 111, 2327, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4502 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6283, 111, 2328, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4503 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6284, 111, 2329, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4504 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6285, 111, 2330, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4505 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6286, 111, 2331, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4506 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6287, 111, 2332, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4507 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6288, 111, 2333, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4508 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6289, 111, 2334, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4509 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6290, 111, 2335, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4510 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6291, 111, 2363, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
  4511 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6292, 111, 2364, '1', '2025-09-06 20:52:25', '1', '2025-09-06 20:52:25', b'0', 122);
4236 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6293, 2, 5, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1); 4512 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6293, 2, 5, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1);
4237 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6294, 2, 1118, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1); 4513 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6294, 2, 1118, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1);
4238 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6295, 2, 1119, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1); 4514 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6295, 2, 1119, '1', '2026-01-04 18:09:41', '1', '2026-01-04 18:09:41', b'0', 1);
@@ -4303,6 +4579,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4303,6 +4579,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4303 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6360, 2, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1); 4579 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6360, 2, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1);
4304 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6361, 3, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1); 4580 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6361, 3, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1);
4305 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6362, 109, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 121); 4581 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6362, 109, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 121);
  4582 +INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6363, 111, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 122);
4306 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6364, 155, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1); 4583 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6364, 155, 5811, '', '2026-03-30 03:13:05', '', '2026-03-30 03:13:05', b'0', 1);
4307 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6365, 2, 6400, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1); 4584 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6365, 2, 6400, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1);
4308 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6366, 2, 6401, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1); 4585 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6366, 2, 6401, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1);
@@ -4312,317 +4589,6 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t @@ -4312,317 +4589,6 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
4312 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6370, 2, 6405, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1); 4589 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6370, 2, 6405, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1);
4313 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6371, 2, 6406, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1); 4590 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6371, 2, 6406, '1', '2026-05-09 16:11:01', '1', '2026-05-09 16:11:01', b'0', 1);
4314 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6380, 2, 6490, '1', '2026-05-14 09:36:12', '1', '2026-05-14 09:36:12', b'0', 1); 4591 INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6380, 2, 6490, '1', '2026-05-14 09:36:12', '1', '2026-05-14 09:36:12', b'0', 1);
4315 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6381, 111, 1, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4316 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6382, 111, 2, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4317 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6383, 111, 5, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4318 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6384, 111, 1031, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4319 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6385, 111, 1032, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4320 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6386, 111, 1033, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4321 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6387, 111, 1034, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4322 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6388, 111, 1035, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4323 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6389, 111, 1036, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4324 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6390, 111, 1037, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4325 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6391, 111, 1038, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4326 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6392, 111, 1039, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4327 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6393, 111, 1050, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4328 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6394, 111, 1051, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4329 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6395, 111, 1052, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4330 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6396, 111, 1053, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4331 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6397, 111, 1054, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4332 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6398, 111, 1056, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4333 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6399, 111, 1057, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4334 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6400, 111, 1058, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4335 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6401, 111, 1059, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4336 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6402, 111, 1060, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4337 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6403, 111, 1063, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4338 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6404, 111, 1064, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4339 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6405, 111, 1065, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4340 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6406, 111, 1066, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4341 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6407, 111, 1067, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4342 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6408, 111, 1070, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4343 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6409, 111, 1075, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4344 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6410, 111, 1077, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4345 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6411, 111, 1078, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4346 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6412, 111, 1082, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4347 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6413, 111, 1083, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4348 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6414, 111, 1084, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4349 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6415, 111, 1085, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4350 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6416, 111, 1086, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4351 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6417, 111, 1087, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4352 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6418, 111, 1088, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4353 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6419, 111, 1089, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4354 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6420, 111, 1090, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4355 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6421, 111, 1091, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4356 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6422, 111, 1092, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4357 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6423, 111, 1117, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4358 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6424, 111, 1118, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4359 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6425, 111, 1119, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4360 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6426, 111, 1120, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4361 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6427, 111, 100, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4362 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6428, 111, 101, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4363 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6429, 111, 102, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4364 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6430, 111, 1126, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4365 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6431, 111, 103, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4366 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6432, 111, 1127, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4367 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6433, 111, 1128, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4368 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6434, 111, 1129, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4369 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6435, 111, 106, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4370 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6436, 111, 1130, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4371 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6437, 111, 107, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4372 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6438, 111, 1132, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4373 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6439, 111, 1133, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4374 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6440, 111, 110, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4375 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6441, 111, 1134, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4376 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6442, 111, 111, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4377 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6443, 111, 1135, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4378 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6444, 111, 112, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4379 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6445, 111, 1136, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4380 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6446, 111, 113, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4381 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6447, 111, 1137, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4382 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6448, 111, 2161, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4383 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6449, 111, 114, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4384 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6450, 111, 1138, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4385 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6451, 111, 1139, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4386 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6452, 111, 115, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4387 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6453, 111, 1140, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4388 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6454, 111, 116, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4389 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6455, 111, 1141, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4390 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6456, 111, 1142, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4391 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6457, 111, 1143, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4392 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6458, 111, 1150, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4393 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6459, 111, 1161, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4394 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6460, 111, 1162, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4395 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6461, 111, 1166, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4396 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6462, 111, 1173, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4397 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6463, 111, 1174, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4398 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6464, 111, 2713, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4399 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6465, 111, 2714, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4400 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6466, 111, 1178, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4401 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6467, 111, 2715, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4402 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6468, 111, 2716, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4403 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6469, 111, 2717, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4404 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6470, 111, 2718, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4405 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6471, 111, 2720, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4406 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6472, 111, 2721, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4407 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6473, 111, 1185, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4408 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6474, 111, 2722, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4409 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6475, 111, 1186, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4410 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6476, 111, 1187, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4411 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6477, 111, 2723, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4412 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6478, 111, 1188, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4413 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6479, 111, 2724, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4414 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6480, 111, 1189, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4415 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6481, 111, 2725, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4416 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6482, 111, 1190, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4417 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6483, 111, 2726, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4418 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6484, 111, 1191, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4419 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6485, 111, 2727, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4420 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6486, 111, 1192, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4421 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6487, 111, 2728, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4422 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6488, 111, 2729, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4423 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6489, 111, 1193, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4424 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6490, 111, 1194, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4425 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6491, 111, 2730, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4426 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6492, 111, 1195, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4427 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6493, 111, 2731, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4428 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6494, 111, 2732, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4429 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6495, 111, 1197, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4430 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6496, 111, 2733, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4431 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6497, 111, 1198, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4432 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6498, 111, 2734, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4433 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6499, 111, 1199, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4434 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6500, 111, 2735, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4435 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6501, 111, 1200, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4436 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6502, 111, 1201, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4437 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6503, 111, 1202, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4438 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6504, 111, 2739, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4439 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6505, 111, 2740, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4440 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6506, 111, 1207, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4441 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6507, 111, 1208, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4442 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6508, 111, 1209, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4443 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6509, 111, 2745, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4444 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6510, 111, 1210, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4445 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6511, 111, 2746, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4446 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6512, 111, 1211, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4447 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6513, 111, 2747, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4448 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6514, 111, 1212, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4449 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6515, 111, 2748, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4450 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6516, 111, 1213, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4451 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6517, 111, 1215, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4452 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6518, 111, 1216, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4453 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6519, 111, 1217, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4454 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6520, 111, 1218, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4455 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6521, 111, 1219, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4456 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6522, 111, 1220, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4457 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6523, 111, 2756, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4458 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6524, 111, 1221, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4459 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6525, 111, 2757, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4460 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6526, 111, 1222, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4461 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6527, 111, 1224, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4462 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6528, 111, 1225, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4463 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6529, 111, 1226, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4464 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6530, 111, 1227, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4465 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6531, 111, 1228, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4466 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6532, 111, 1229, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4467 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6533, 111, 1237, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4468 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6534, 111, 1238, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4469 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6535, 111, 2262, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4470 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6536, 111, 1239, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4471 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6537, 111, 1240, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4472 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6538, 111, 1241, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4473 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6539, 111, 1242, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4474 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6540, 111, 1243, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4475 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6541, 111, 2275, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4476 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6542, 111, 2276, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4477 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6543, 111, 2277, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4478 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6544, 111, 1255, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4479 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6545, 111, 1256, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4480 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6546, 111, 1257, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4481 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6547, 111, 2281, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4482 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6548, 111, 1258, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4483 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6549, 111, 2282, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4484 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6550, 111, 1259, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4485 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6551, 111, 2283, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4486 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6552, 111, 1260, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4487 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6553, 111, 2284, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4488 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6554, 111, 2285, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4489 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6555, 111, 2287, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4490 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6556, 111, 2288, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4491 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6557, 111, 2293, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4492 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6558, 111, 2294, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4493 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6559, 111, 2297, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4494 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6560, 111, 2300, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4495 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6561, 111, 2301, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4496 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6562, 111, 2302, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4497 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6563, 111, 2317, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4498 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6564, 111, 2318, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4499 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6565, 111, 2319, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4500 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6566, 111, 2320, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4501 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6567, 111, 2321, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4502 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6568, 111, 2322, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4503 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6569, 111, 2323, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4504 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6570, 111, 2324, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4505 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6571, 111, 2325, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4506 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6572, 111, 2326, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4507 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6573, 111, 2327, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4508 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6574, 111, 2328, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4509 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6575, 111, 2329, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4510 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6576, 111, 2330, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4511 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6577, 111, 2331, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4512 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6578, 111, 2332, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4513 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6579, 111, 2333, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4514 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6580, 111, 2334, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4515 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6581, 111, 2335, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4516 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6582, 111, 2363, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4517 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6583, 111, 2364, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4518 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6584, 111, 5011, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4519 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6585, 111, 5012, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4520 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6586, 111, 2472, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4521 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6587, 111, 2478, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4522 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6588, 111, 2479, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4523 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6589, 111, 2480, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4524 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6590, 111, 2481, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4525 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6591, 111, 2482, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4526 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6592, 111, 2483, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4527 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6593, 111, 2484, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4528 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6594, 111, 2485, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4529 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6595, 111, 2486, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4530 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6596, 111, 2487, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4531 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6597, 111, 2488, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4532 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6598, 111, 2489, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4533 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6599, 111, 2490, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4534 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6600, 111, 2491, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4535 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6601, 111, 2492, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4536 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6602, 111, 2493, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4537 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6603, 111, 2494, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4538 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6604, 111, 2495, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4539 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6605, 111, 2497, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4540 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6606, 111, 2525, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4541 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6607, 111, 1001, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4542 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6608, 111, 1002, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4543 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6609, 111, 1003, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4544 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6610, 111, 1004, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4545 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6611, 111, 1005, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4546 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6612, 111, 1006, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4547 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6613, 111, 1007, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4548 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6614, 111, 1008, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4549 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6615, 111, 1009, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4550 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6616, 111, 1010, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4551 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6617, 111, 1011, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4552 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6618, 111, 1012, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4553 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6619, 111, 1013, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4554 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6620, 111, 2549, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4555 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6621, 111, 1014, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4556 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6622, 111, 2550, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4557 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6623, 111, 1015, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4558 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6624, 111, 2551, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4559 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6625, 111, 1016, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4560 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6626, 111, 2552, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4561 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6627, 111, 1017, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4562 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6628, 111, 2553, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4563 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6629, 111, 1018, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4564 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6630, 111, 2554, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4565 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6631, 111, 1019, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4566 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6632, 111, 2555, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4567 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6633, 111, 1020, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4568 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6634, 111, 2556, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4569 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6635, 111, 2557, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4570 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6636, 111, 2558, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4571 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6637, 111, 2559, '1', '2026-06-01 23:29:35', '1', '2026-06-01 23:29:35', b'0', 122);  
4572 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6638, 155, 5780, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4573 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6639, 155, 2084, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4574 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6640, 155, 2085, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4575 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6641, 155, 2086, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4576 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6642, 155, 2087, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4577 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6643, 155, 2088, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4578 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6644, 155, 2089, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4579 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6645, 155, 2090, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4580 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6646, 155, 2091, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4581 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6647, 155, 2092, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4582 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6648, 155, 2093, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4583 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6649, 155, 5805, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4584 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6650, 155, 2094, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4585 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6651, 155, 2095, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4586 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6652, 155, 2096, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4587 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6653, 155, 2097, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4588 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6654, 155, 2098, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4589 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6655, 155, 5042, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4590 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6656, 155, 2099, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4591 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6657, 155, 5043, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4592 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6658, 155, 2100, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4593 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6659, 155, 5044, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4594 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6660, 155, 2101, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4595 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6661, 155, 5045, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4596 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6662, 155, 2102, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4597 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6663, 155, 5046, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4598 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6664, 155, 2103, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4599 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6665, 155, 2104, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4600 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6666, 155, 2105, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4601 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6667, 155, 2106, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4602 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6668, 155, 2107, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4603 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6669, 155, 2108, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4604 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6670, 155, 2109, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4605 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6671, 155, 2110, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4606 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6672, 155, 2111, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4607 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6673, 155, 2112, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4608 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6674, 155, 2113, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4609 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6675, 155, 2114, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4610 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6676, 155, 2115, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4611 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6677, 155, 2116, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4612 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6678, 155, 2117, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4613 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6679, 155, 2118, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4614 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6680, 155, 2119, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4615 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6681, 155, 2120, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4616 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6682, 155, 2121, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4617 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6683, 155, 2122, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4618 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6684, 155, 2123, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4619 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6685, 155, 2124, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4620 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6686, 155, 2125, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4621 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6687, 155, 2126, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4622 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6688, 155, 2127, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4623 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6689, 155, 2128, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4624 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6690, 155, 2129, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4625 -INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6691, 155, 5100, '1', '2026-06-10 09:04:18', '1', '2026-06-10 09:04:18', b'0', 1);  
4626 COMMIT; 4592 COMMIT;
4627 4593
4628 -- ---------------------------- 4594 -- ----------------------------
@@ -4677,7 +4643,7 @@ CREATE TABLE `system_sms_code` ( @@ -4677,7 +4643,7 @@ CREATE TABLE `system_sms_code` (
4677 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', 4643 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
4678 PRIMARY KEY (`id`) USING BTREE, 4644 PRIMARY KEY (`id`) USING BTREE,
4679 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号' 4645 INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
4680 -) ENGINE = InnoDB AUTO_INCREMENT = 694 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码'; 4646 +) ENGINE = InnoDB AUTO_INCREMENT = 690 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
4681 4647
4682 -- ---------------------------- 4648 -- ----------------------------
4683 -- Records of system_sms_code 4649 -- Records of system_sms_code
@@ -4718,7 +4684,7 @@ CREATE TABLE `system_sms_log` ( @@ -4718,7 +4684,7 @@ CREATE TABLE `system_sms_log` (
4718 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 4684 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
4719 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 4685 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
4720 PRIMARY KEY (`id`) USING BTREE 4686 PRIMARY KEY (`id`) USING BTREE
4721 -) ENGINE = InnoDB AUTO_INCREMENT = 1568 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; 4687 +) ENGINE = InnoDB AUTO_INCREMENT = 1553 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
4722 4688
4723 -- ---------------------------- 4689 -- ----------------------------
4724 -- Records of system_sms_log 4690 -- Records of system_sms_log
@@ -4895,7 +4861,7 @@ CREATE TABLE `system_tenant` ( @@ -4895,7 +4861,7 @@ CREATE TABLE `system_tenant` (
4895 BEGIN; 4861 BEGIN;
4896 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'www.iocoder.cn,127.0.0.1:3000,wxc4598c446f8a9cb3', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2025-08-19 05:18:41', b'0'); 4862 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '芋道源码', NULL, '芋艿', '17321315478', 0, 'www.iocoder.cn,127.0.0.1:3000,wxc4598c446f8a9cb3', 0, '2099-02-19 17:14:16', 9999, '1', '2021-01-05 17:03:47', '1', '2025-08-19 05:18:41', b'0');
4897 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'zsxq.iocoder.cn,123321', 111, '2026-07-10 00:00:00', 30, '1', '2022-02-22 00:56:14', '1', '2025-08-19 21:19:29', b'0'); 4863 INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (121, '小租户', 110, '小王2', '15601691300', 0, 'zsxq.iocoder.cn,123321', 111, '2026-07-10 00:00:00', 30, '1', '2022-02-22 00:56:14', '1', '2025-08-19 21:19:29', b'0');
4898 -INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'test.iocoder.cn,222,333', 111, '2023-04-29 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2026-06-01 23:29:35', b'0'); 4864 +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `websites`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (122, '测试租户', 113, '芋道', '15601691300', 0, 'test.iocoder.cn,222,333', 111, '2023-04-29 00:00:00', 50, '1', '2022-03-07 21:37:58', '1', '2025-12-21 09:50:00', b'0');
4899 COMMIT; 4865 COMMIT;
4900 4866
4901 -- ---------------------------- 4867 -- ----------------------------
@@ -4914,14 +4880,13 @@ CREATE TABLE `system_tenant_package` ( @@ -4914,14 +4880,13 @@ CREATE TABLE `system_tenant_package` (
4914 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 4880 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
4915 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', 4881 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
4916 PRIMARY KEY (`id`) USING BTREE 4882 PRIMARY KEY (`id`) USING BTREE
4917 -) ENGINE = InnoDB AUTO_INCREMENT = 114 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户套餐表'; 4883 +) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户套餐表';
4918 4884
4919 -- ---------------------------- 4885 -- ----------------------------
4920 -- Records of system_tenant_package 4886 -- Records of system_tenant_package
4921 -- ---------------------------- 4887 -- ----------------------------
4922 BEGIN; 4888 BEGIN;
4923 INSERT INTO `system_tenant_package` (`id`, `name`, `status`, `remark`, `menu_ids`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (111, '普通套餐', 0, '小功能', '[1,2,5,1031,1032,1033,1034,1035,1036,1037,1038,1039,1050,1051,1052,1053,1054,1056,1057,1058,1059,1060,1063,1064,1065,1066,1067,1070,1075,1077,1078,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1117,1118,1119,1120,100,101,102,1126,103,1127,1128,1129,106,1130,107,1132,1133,110,1134,111,1135,112,1136,113,1137,2161,114,1138,1139,115,1140,116,1141,1142,1143,1150,1161,1162,1166,1173,1174,2713,2714,1178,2715,2716,2717,2718,2720,2721,1185,2722,1186,1187,2723,1188,2724,1189,2725,1190,2726,1191,2727,1192,2728,2729,1193,1194,2730,1195,2731,2732,1197,2733,1198,2734,1199,2735,1200,1201,1202,2739,2740,1207,1208,1209,2745,1210,2746,1211,2747,1212,2748,1213,1215,1216,1217,1218,1219,1220,2756,1221,2757,1222,1224,1225,1226,1227,1228,1229,1237,1238,2262,1239,1240,1241,1242,1243,2275,2276,2277,1255,1256,1257,2281,1258,2282,1259,2283,1260,2284,2285,2287,2288,2293,2294,2297,2300,2301,2302,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2363,2364,5011,5012,2472,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2497,2525,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,2549,1014,2550,1015,2551,1016,2552,1017,2553,1018,2554,1019,2555,1020,2556,2557,2558,2559]', '1', '2022-02-22 00:54:00', '1', '2025-09-06 20:52:25', b'0'); 4889 INSERT INTO `system_tenant_package` (`id`, `name`, `status`, `remark`, `menu_ids`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (111, '普通套餐', 0, '小功能', '[1,2,5,1031,1032,1033,1034,1035,1036,1037,1038,1039,1050,1051,1052,1053,1054,1056,1057,1058,1059,1060,1063,1064,1065,1066,1067,1070,1075,1077,1078,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1117,1118,1119,1120,100,101,102,1126,103,1127,1128,1129,106,1130,107,1132,1133,110,1134,111,1135,112,1136,113,1137,2161,114,1138,1139,115,1140,116,1141,1142,1143,1150,1161,1162,1166,1173,1174,2713,2714,1178,2715,2716,2717,2718,2720,2721,1185,2722,1186,1187,2723,1188,2724,1189,2725,1190,2726,1191,2727,1192,2728,2729,1193,1194,2730,1195,2731,2732,1197,2733,1198,2734,1199,2735,1200,1201,1202,2739,2740,1207,1208,1209,2745,1210,2746,1211,2747,1212,2748,1213,1215,1216,1217,1218,1219,1220,2756,1221,2757,1222,1224,1225,1226,1227,1228,1229,1237,1238,2262,1239,1240,1241,1242,1243,2275,2276,2277,1255,1256,1257,2281,1258,2282,1259,2283,1260,2284,2285,2287,2288,2293,2294,2297,2300,2301,2302,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2363,2364,5011,5012,2472,2478,2479,2480,2481,2482,2483,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494,2495,2497,2525,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,2549,1014,2550,1015,2551,1016,2552,1017,2553,1018,2554,1019,2555,1020,2556,2557,2558,2559]', '1', '2022-02-22 00:54:00', '1', '2025-09-06 20:52:25', b'0');
4924 -INSERT INTO `system_tenant_package` (`id`, `name`, `status`, `remark`, `menu_ids`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (113, '测试套餐(啥都没有)', 0, '', '[2160,1254,2159]', '1', '2026-06-01 23:22:39', '1', '2026-06-09 14:44:00', b'0');  
4925 COMMIT; 4890 COMMIT;
4926 4891
4927 -- ---------------------------- 4892 -- ----------------------------
@@ -5039,26 +5004,26 @@ CREATE TABLE `system_users` ( @@ -5039,26 +5004,26 @@ CREATE TABLE `system_users` (
5039 -- Records of system_users 5004 -- Records of system_users
5040 -- ---------------------------- 5005 -- ----------------------------
5041 BEGIN; 5006 BEGIN;
5042 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道源码', '管理员', 103, '[1,2]', '13aoteman@126.com', '18818260272', 1, 'http://test.yudao.iocoder.cn/20260517/blob_1778998103688.png', 0, '0:0:0:0:0:0:0:1', '2026-06-19 18:38:00', 'admin', '2021-01-05 17:03:47', NULL, '2026-06-19 18:38:00', b'0', 1);  
5043 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, NULL, 0, '127.0.0.1', '2026-06-17 09:37:17', '', '2021-01-07 09:07:17', NULL, '2026-06-17 09:37:17', b'0', 1); 5007 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道源码', '管理员', 103, '[1,2]', '13aoteman@126.com', '18818260272', 1, 'http://test.yudao.iocoder.cn/20260517/blob_1778998103688.png', 0, '0:0:0:0:0:0:0:1', '2026-05-31 21:54:49', 'admin', '2021-01-05 17:03:47', NULL, '2026-05-31 21:54:49', b'0', 1);
  5008 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$.vd8nPeLwxt6hnSzmAoAyul8BOLX7Cib6QhcxRe30rfvrIPQHH1OG', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-19 17:40:55', '', '2021-01-07 09:07:17', NULL, '2026-04-19 17:40:55', b'0', 1);
5044 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$k/d6mc0nySN0i2udwcI8Ee8V5aM5OHixBRbQfXmPuFTUl3Zf/DBs.', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-27 13:19:27', '', '2021-01-13 23:50:35', NULL, '2026-04-27 13:19:27', b'0', 1); 5009 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$k/d6mc0nySN0i2udwcI8Ee8V5aM5OHixBRbQfXmPuFTUl3Zf/DBs.', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2026-04-27 13:19:27', '', '2021-01-13 23:50:35', NULL, '2026-04-27 13:19:27', b'0', 1);
5045 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-06-16 11:37:56', '', '2021-01-21 02:13:53', NULL, '2026-06-16 11:37:56', b'0', 1); 5010 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$BrwaYn303hjA/6TnXqdGoOLhyHOAA0bVrAFu6.1dJKycqKUnIoRz2', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-05-20 23:37:11', '', '2021-01-21 02:13:53', NULL, '2026-05-20 23:37:11', b'0', 1);
5046 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', b'0', 118); 5011 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (107, 'admin107', '$2a$10$dYOOBKMO93v/.ReCqzyFg.o67Tqk.bbc2bhrpyBGkIw9aypCtr2pm', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 22:59:33', '1', '2025-04-21 14:23:08', b'0', 118);
5047 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', b'0', 119); 5012 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (108, 'admin108', '$2a$10$y6mfvKoNYL1GXWak8nYwVOH.kCWqjactkzdoIDgiKl93WN3Ejg.Lu', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:00:50', '1', '2025-04-21 14:23:08', b'0', 119);
5048 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', b'0', 120); 5013 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (109, 'admin109', '$2a$10$JAqvH0tEc0I7dfDVBI7zyuB4E3j.uH6daIjV53.vUS6PknFkDJkuK', '芋艿', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '', NULL, '1', '2022-02-20 23:11:50', '1', '2025-04-21 14:23:08', b'0', 120);
5049 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', b'0', 121); 5014 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (110, 'admin110', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '小王', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2024-07-20 22:23:17', '1', '2022-02-22 00:56:14', NULL, '2025-04-21 14:23:08', b'0', 121);
5050 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', b'0', 121); 5015 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (111, 'test', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '测试用户', NULL, NULL, '[]', '', '', 0, NULL, 0, '0:0:0:0:0:0:0:1', '2023-12-30 11:42:17', '110', '2022-02-23 13:14:33', NULL, '2025-04-21 14:23:08', b'0', 121);
5051 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, 100, '[]', '', '15601691235', 1, NULL, 0, '127.0.0.1', '2026-06-17 09:07:20', '1', '2022-02-23 19:08:03', NULL, '2026-06-17 09:07:20', b'0', 1); 5016 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (112, 'newobject', '$2a$04$dB0z8Q819fJWz0hbaLe6B.VfHCjYgWx6LFfET5lyz3JwcqlyCkQ4C', '新对象', NULL, 100, '[]', '', '15601691235', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-03-16 23:11:38', '1', '2022-02-23 19:08:03', NULL, '2025-04-21 14:23:08', b'0', 1);
5052 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道1', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', b'0', 122); 5017 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (113, 'aoteman', '$2a$10$0acJOIk2D25/oC87nyclE..0lzeu9DtQ/n3geP4fkun/zIVRhHJIO', '芋道1', NULL, NULL, NULL, '', '15601691300', 0, NULL, 0, '127.0.0.1', '2022-03-19 18:38:51', '1', '2022-03-07 21:37:58', '1', '2025-05-05 15:30:53', b'0', 122);
5053 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-01-04 18:16:01', '1', '2022-03-19 21:50:58', NULL, '2026-01-04 18:16:01', b'0', 1); 5018 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (114, 'hrmgr', '$2a$10$TR4eybBioGRhBmDBWkqWLO6NIh3mzYa8KBKDDB5woiGYFVlRAi.fu', 'hr 小姐姐', NULL, NULL, '[5]', '', '15601691236', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2026-01-04 18:16:01', '1', '2022-03-19 21:50:58', NULL, '2026-01-04 18:16:01', b'0', 1);
5054 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', b'0', 1); 5019 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (115, 'aotemane', '$2a$04$GcyP0Vyzb2F2Yni5PuIK9ueGxM0tkZGMtDwVRwrNbtMvorzbpNsV2', '阿呆', '11222', 102, '[1,2]', '7648@qq.com', '15601691229', 2, NULL, 0, '', NULL, '1', '2022-04-30 02:55:43', '1', '2025-04-21 14:23:08', b'0', 1);
5055 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', 100, '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', '1', '2025-05-14 09:56:04', b'0', 1); 5020 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (117, 'admin123', '$2a$04$sEtimsHu9YCkYY4/oqElHem2Ijc9ld20eYO6lN.g/21NfLUTDLB9W', '测试号02', '1111', 100, '[2]', '', '15601691234', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-10-02 10:16:20', '1', '2022-07-09 17:40:26', '1', '2025-05-14 09:56:04', b'0', 1);
5056 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (118, 'goudan', '$2a$04$3suGZjnA6rM5bErf38u1felbgqbsPHGdRG3l9NkxPCEt2ah9Y6aJi', '狗蛋', NULL, 103, '[1]', '', '15601691239', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-11-23 15:28:25', '1', '2022-07-09 17:44:43', NULL, '2025-11-23 15:28:25', b'0', 1); 5021 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (118, 'goudan', '$2a$04$3suGZjnA6rM5bErf38u1felbgqbsPHGdRG3l9NkxPCEt2ah9Y6aJi', '狗蛋', NULL, 103, '[1]', '', '15601691239', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2025-11-23 15:28:25', '1', '2022-07-09 17:44:43', NULL, '2025-11-23 15:28:25', b'0', 1);
5057 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (139, 'wwbwwb', '$2a$04$FJLIyg8lbPytP29pbZaiU.LesJvCsYfEaHqQfB0pGQhK3e9BeZmLy', '小秃头', '123', 108, '[2,4]', '', '', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', '1', '2025-12-15 22:38:15', b'0', 1); 5022 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (139, 'wwbwwb', '$2a$04$FJLIyg8lbPytP29pbZaiU.LesJvCsYfEaHqQfB0pGQhK3e9BeZmLy', '小秃头', '123', 108, '[2,4]', '', '', 1, NULL, 0, '0:0:0:0:0:0:0:1', '2024-09-10 21:03:58', NULL, '2024-09-10 21:03:58', '1', '2025-12-15 22:38:15', b'0', 1);
5058 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, NULL, '', '', 0, '', 0, '127.0.0.1', '2026-06-17 09:37:18', '1', '2025-04-08 13:09:07', NULL, '2026-06-17 09:37:18', b'0', 1);  
5059 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (142, 'test01', '$2a$04$4bCYWZkjxxOC4QE0LY2M9uEEKWeJbLfs489NFtQoyidL5I0FndRaO', 'test01', '', NULL, '[]', '', '19021719925', 1, '', 0, '0:0:0:0:0:0:0:1', '2026-06-17 10:44:58', '1', '2025-07-09 21:07:10', NULL, '2026-06-17 10:44:58', b'0', 1); 5023 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (141, 'admin1', '$2a$04$oj6F6d7HrZ70kYVD3TNzEu.m3TPUzajOVuC66zdKna8KRerK1FmVa', '新用户', NULL, NULL, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-04-08 13:09:07', '1', '2025-04-08 13:09:07', '1', '2025-05-14 19:11:48', b'0', 1);
  5024 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (142, 'test01', '$2a$04$4bCYWZkjxxOC4QE0LY2M9uEEKWeJbLfs489NFtQoyidL5I0FndRaO', 'test01', '', NULL, '[]', '', '19021719925', 1, '', 0, '0:0:0:0:0:0:0:1', '2025-07-29 19:47:17', '1', '2025-07-09 21:07:10', NULL, '2025-12-02 13:23:11', b'0', 1);
5060 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (143, 'a00001', '$2a$04$GhVHFviOw/SsTmiQtifHJesDYFlHMeGK7OWh7aGCCjGGVCmbHVAwa', 'a00001', NULL, 104, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-12-01 16:10:13', NULL, '2025-12-01 16:10:13', '1', '2025-12-05 21:34:05', b'0', 1); 5025 INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (143, 'a00001', '$2a$04$GhVHFviOw/SsTmiQtifHJesDYFlHMeGK7OWh7aGCCjGGVCmbHVAwa', 'a00001', NULL, 104, NULL, '', '', 0, '', 0, '0:0:0:0:0:0:0:1', '2025-12-01 16:10:13', NULL, '2025-12-01 16:10:13', '1', '2025-12-05 21:34:05', b'0', 1);
5061 -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (144, 'aoteman001', '$2a$04$omQOmhz8OyUFBKw77nr8KOtMp6xdvoQ1gWStjk9r8.OYT3Bv6oEYe', 'aoteman00112', NULL, 104, NULL, '', '', 0, '', 1, '0:0:0:0:0:0:0:1', '2025-12-01 17:05:27', '1', '2025-12-01 17:05:27', '1', '2026-06-15 18:58:56', b'0', 1); 5026 +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (144, 'aoteman001', '$2a$04$omQOmhz8OyUFBKw77nr8KOtMp6xdvoQ1gWStjk9r8.OYT3Bv6oEYe', 'aoteman001', NULL, 104, NULL, '', '', 0, '', 1, '0:0:0:0:0:0:0:1', '2025-12-01 17:05:27', '1', '2025-12-01 17:05:27', '1', '2026-05-31 21:52:48', b'0', 1);
5062 COMMIT; 5027 COMMIT;
5063 5028
5064 -- ---------------------------- 5029 -- ----------------------------
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.conversation;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationReadRespVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
  7 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.Parameter;
  10 +import io.swagger.v3.oas.annotations.Parameters;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.constraints.Max;
  14 +import jakarta.validation.constraints.Min;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.GetMapping;
  17 +import org.springframework.web.bind.annotation.RequestMapping;
  18 +import org.springframework.web.bind.annotation.RequestParam;
  19 +import org.springframework.web.bind.annotation.RestController;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  25 +
  26 +@Tag(name = "管理后台 - IM 会话读位置")
  27 +@RestController
  28 +@RequestMapping("/im/conversation-read")
  29 +@Validated
  30 +public class ImConversationReadController {
  31 +
  32 + @Resource
  33 + private ImConversationReadService conversationReadService;
  34 +
  35 + @GetMapping("/pull")
  36 + @Operation(summary = "增量拉取当前用户的会话读位置(重连 / 离线补偿)")
  37 + @Parameters({
  38 + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"),
  39 + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"),
  40 + @Parameter(name = "limit", description = "单次拉取条数", required = true)
  41 + })
  42 + public CommonResult<List<ImConversationReadRespVO>> pullMyConversationRead(
  43 + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
  44 + @RequestParam(value = "lastId", required = false) Long lastId,
  45 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
  46 + List<ImConversationReadDO> list = conversationReadService.pullConversationReadList(
  47 + getLoginUserId(), lastUpdateTime, lastId, limit);
  48 + return success(BeanUtils.toBean(list, ImConversationReadRespVO.class));
  49 + }
  50 +
  51 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.conversation.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * IM 会话读位置 Response VO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Schema(description = "管理后台 - IM 会话读位置 Response VO")
  14 +@Data
  15 +public class ImConversationReadRespVO {
  16 +
  17 + @Schema(description = "读位置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private Long id;
  19 +
  20 + @Schema(description = "会话类型", example = "1")
  21 + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举
  22 +
  23 + @Schema(description = "目标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  24 + private Long targetId;
  25 +
  26 + @Schema(description = "最大已读消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
  27 + private Long messageId;
  28 +
  29 + @Schema(description = "最近更新时间(增量拉取游标用)")
  30 + private LocalDateTime updateTime;
  31 +
  32 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java
@@ -17,6 +17,8 @@ import io.swagger.v3.oas.annotations.Parameters; @@ -17,6 +17,8 @@ import io.swagger.v3.oas.annotations.Parameters;
17 import io.swagger.v3.oas.annotations.tags.Tag; 17 import io.swagger.v3.oas.annotations.tags.Tag;
18 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
19 import jakarta.validation.Valid; 19 import jakarta.validation.Valid;
  20 +import jakarta.validation.constraints.Max;
  21 +import jakarta.validation.constraints.Min;
20 import jakarta.validation.constraints.NotNull; 22 import jakarta.validation.constraints.NotNull;
21 import org.springframework.validation.annotation.Validated; 23 import org.springframework.validation.annotation.Validated;
22 import org.springframework.web.bind.annotation.*; 24 import org.springframework.web.bind.annotation.*;
@@ -46,11 +48,25 @@ public class ImFriendController { @@ -46,11 +48,25 @@ public class ImFriendController {
46 @GetMapping("/list") 48 @GetMapping("/list")
47 @Operation(summary = "获得当前登录用户的好友列表") 49 @Operation(summary = "获得当前登录用户的好友列表")
48 public CommonResult<List<ImFriendRespVO>> getMyFriendList() { 50 public CommonResult<List<ImFriendRespVO>> getMyFriendList() {
49 - // 含 DISABLE 历史好友:保留给前端展示「已删除好友」的历史对话信息;前端按 status 决定会话级联清理  
50 List<ImFriendDO> friends = friendService.getFriendList(getLoginUserId()); 51 List<ImFriendDO> friends = friendService.getFriendList(getLoginUserId());
51 return success(buildFriendRespVOList(friends)); 52 return success(buildFriendRespVOList(friends));
52 } 53 }
53 54
  55 + @GetMapping("/pull")
  56 + @Operation(summary = "增量拉取当前用户的好友关系(重连 / 离线补偿)")
  57 + @Parameters({
  58 + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"),
  59 + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"),
  60 + @Parameter(name = "limit", description = "单次拉取条数", required = true)
  61 + })
  62 + public CommonResult<List<ImFriendRespVO>> pullMyFriendList(
  63 + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
  64 + @RequestParam(value = "lastId", required = false) Long lastId,
  65 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
  66 + List<ImFriendDO> list = friendService.pullFriendList(getLoginUserId(), lastUpdateTime, lastId, limit);
  67 + return success(buildFriendRespVOList(list));
  68 + }
  69 +
54 @GetMapping("/get") 70 @GetMapping("/get")
55 @Operation(summary = "获得好友详情") 71 @Operation(summary = "获得好友详情")
56 @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") 72 @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048")
@@ -62,8 +78,8 @@ public class ImFriendController { @@ -62,8 +78,8 @@ public class ImFriendController {
62 @DeleteMapping("/delete") 78 @DeleteMapping("/delete")
63 @Operation(summary = "删除好友(单向软删除)") 79 @Operation(summary = "删除好友(单向软删除)")
64 @Parameters({ 80 @Parameters({
65 - @Parameter(description = "好友的用户编号", required = true, example = "2048"),  
66 - @Parameter(description = "是否级联清理本端相关数据(如私聊会话)") 81 + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048"),
  82 + @Parameter(name = "clear", description = "是否级联清理本端相关数据(如私聊会话)")
67 }) 83 })
68 public CommonResult<Boolean> deleteFriend( 84 public CommonResult<Boolean> deleteFriend(
69 @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId, 85 @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId,
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
13 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 13 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
14 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
15 import io.swagger.v3.oas.annotations.Parameter; 15 import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.Parameters;
16 import io.swagger.v3.oas.annotations.tags.Tag; 17 import io.swagger.v3.oas.annotations.tags.Tag;
17 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
18 import jakarta.validation.Valid; 19 import jakarta.validation.Valid;
@@ -79,15 +80,32 @@ public class ImFriendRequestController { @@ -79,15 +80,32 @@ public class ImFriendRequestController {
79 80
80 @GetMapping("/list") 81 @GetMapping("/list")
81 @Operation(summary = "查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)") 82 @Operation(summary = "查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)")
  83 + @Parameters({
  84 + @Parameter(name = "maxId", description = "当前列表最旧记录的 id;首页不传"),
  85 + @Parameter(name = "limit", description = "单次拉取条数", required = true)
  86 + })
82 public CommonResult<List<ImFriendRequestRespVO>> getMyFriendRequestList( 87 public CommonResult<List<ImFriendRequestRespVO>> getMyFriendRequestList(
83 - @Parameter(description = "当前列表最旧记录的 id;首页不传")  
84 @RequestParam(value = "maxId", required = false) Long maxId, 88 @RequestParam(value = "maxId", required = false) Long maxId,
85 - @Parameter(description = "单次拉取条数", required = true)  
86 @RequestParam("limit") @Min(1) @Max(200) Integer limit) { 89 @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
87 List<ImFriendRequestDO> list = friendRequestService.getMyFriendRequestList(getLoginUserId(), maxId, limit); 90 List<ImFriendRequestDO> list = friendRequestService.getMyFriendRequestList(getLoginUserId(), maxId, limit);
88 return success(buildList(list)); 91 return success(buildList(list));
89 } 92 }
90 93
  94 + @GetMapping("/pull")
  95 + @Operation(summary = "增量拉取「我相关」的好友申请(重连 / 离线补偿)")
  96 + @Parameters({
  97 + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"),
  98 + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"),
  99 + @Parameter(name = "limit", description = "单次拉取条数", required = true)
  100 + })
  101 + public CommonResult<List<ImFriendRequestRespVO>> pullMyFriendRequestList(
  102 + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
  103 + @RequestParam(value = "lastId", required = false) Long lastId,
  104 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
  105 + List<ImFriendRequestDO> list = friendRequestService.pullFriendRequestList(getLoginUserId(), lastUpdateTime, lastId, limit);
  106 + return success(buildList(list));
  107 + }
  108 +
91 @GetMapping("/get") 109 @GetMapping("/get")
92 @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)") 110 @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)")
93 @Parameter(name = "id", description = "申请记录编号", required = true) 111 @Parameter(name = "id", description = "申请记录编号", required = true)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java
@@ -47,6 +47,9 @@ public class ImFriendRespVO { @@ -47,6 +47,9 @@ public class ImFriendRespVO {
47 @Schema(description = "删除好友时间") 47 @Schema(description = "删除好友时间")
48 private LocalDateTime deleteTime; 48 private LocalDateTime deleteTime;
49 49
  50 + @Schema(description = "最近更新时间(增量拉取游标用)")
  51 + private LocalDateTime updateTime;
  52 +
50 // ========== 下面是聚合字段,方便前端显示 ========== 53 // ========== 下面是聚合字段,方便前端显示 ==========
51 54
52 @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道") 55 @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java
@@ -41,6 +41,9 @@ public class ImFriendRequestRespVO { @@ -41,6 +41,9 @@ public class ImFriendRequestRespVO {
41 @Schema(description = "申请创建时间") 41 @Schema(description = "申请创建时间")
42 private LocalDateTime createTime; 42 private LocalDateTime createTime;
43 43
  44 + @Schema(description = "最近更新时间(增量拉取游标用)")
  45 + private LocalDateTime updateTime;
  46 +
44 // ========== 下面是聚合字段,方便前端显示 ========== 47 // ========== 下面是聚合字段,方便前端显示 ==========
45 48
46 @Schema(description = "发起方昵称(实时聚合自 AdminUser)", example = "芋道") 49 @Schema(description = "发起方昵称(实时聚合自 AdminUser)", example = "芋道")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.controller.admin.group; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.controller.admin.group;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; 8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; 9 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
@@ -187,8 +188,10 @@ public class ImGroupController { @@ -187,8 +188,10 @@ public class ImGroupController {
187 return Collections.emptyList(); 188 return Collections.emptyList();
188 } 189 }
189 // 仅当前用户是有效成员的群才允许回填置顶消息 190 // 仅当前用户是有效成员的群才允许回填置顶消息
190 - Set<Long> activeGroupIds = convertSet(  
191 - groupMemberService.getActiveGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId); 191 + Map<Long, ImGroupMemberDO> memberMap = convertMap(
  192 + groupMemberService.getGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId);
  193 + Set<Long> activeGroupIds = convertSet(memberMap.values(), ImGroupMemberDO::getGroupId,
  194 + member -> CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus()));
192 Set<Long> allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId()) 195 Set<Long> allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId())
193 ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty()); 196 ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty());
194 Map<Long, ImGroupMessageDO> messageMap = groupMessageService.getGroupMessageMap(allMessageIds); 197 Map<Long, ImGroupMessageDO> messageMap = groupMessageService.getGroupMessageMap(allMessageIds);
@@ -198,6 +201,8 @@ public class ImGroupController { @@ -198,6 +201,8 @@ public class ImGroupController {
198 // 标记登录用户在该群的成员状态,供前端区分当前群与历史退群 201 // 标记登录用户在该群的成员状态,供前端区分当前群与历史退群
199 boolean joined = activeGroupIds.contains(group.getId()); 202 boolean joined = activeGroupIds.contains(group.getId());
200 groupVO.setJoinStatus(joined ? CommonStatusEnum.ENABLE.getStatus() : CommonStatusEnum.DISABLE.getStatus()); 203 groupVO.setJoinStatus(joined ? CommonStatusEnum.ENABLE.getStatus() : CommonStatusEnum.DISABLE.getStatus());
  204 + MapUtils.findAndThen(memberMap, group.getId(), member ->
  205 + groupVO.setGroupRemark(member.getGroupRemark()).setSilent(Boolean.TRUE.equals(member.getSilent())));
201 if (!joined || CollUtil.isEmpty(group.getPinnedMessageIds())) { 206 if (!joined || CollUtil.isEmpty(group.getPinnedMessageIds())) {
202 return groupVO; 207 return groupVO;
203 } 208 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java
@@ -21,6 +21,7 @@ import jakarta.validation.Valid; @@ -21,6 +21,7 @@ import jakarta.validation.Valid;
21 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
22 import org.springframework.web.bind.annotation.*; 22 import org.springframework.web.bind.annotation.*;
23 23
  24 +import java.util.Collections;
24 import java.util.List; 25 import java.util.List;
25 import java.util.Map; 26 import java.util.Map;
26 27
@@ -88,7 +89,7 @@ public class ImGroupMemberController { @@ -88,7 +89,7 @@ public class ImGroupMemberController {
88 } 89 }
89 90
90 @GetMapping("/list") 91 @GetMapping("/list")
91 - @Operation(summary = "获得指定群的成员列表") 92 + @Operation(summary = "获得指定群的成员列表(按群全量拉取,作为前端本地成员 cache 的完整基线)")
92 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") 93 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
93 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { 94 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
94 // 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤) 95 // 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤)
@@ -101,16 +102,30 @@ public class ImGroupMemberController { @@ -101,16 +102,30 @@ public class ImGroupMemberController {
101 throw exception(GROUP_MEMBER_NOT_IN_GROUP); 102 throw exception(GROUP_MEMBER_NOT_IN_GROUP);
102 } 103 }
103 104
104 - // 2.批量聚合 AdminUser 信息(昵称 / 头像) 105 + // 2. 批量聚合昵称 / 头像,并对非本人置空私人字段
  106 + return success(buildGroupMemberRespVOList(members));
  107 + }
  108 +
  109 + // ========== 私有方法:VO 组装 ==========
  110 +
  111 + /**
  112 + * 群成员列表批量转 VO + 关联回填昵称 / 头像,并对非本人置空私人字段
  113 + */
  114 + private List<ImGroupMemberRespVO> buildGroupMemberRespVOList(List<ImGroupMemberDO> members) {
  115 + if (CollUtil.isEmpty(members)) {
  116 + return Collections.emptyList();
  117 + }
  118 + Long loginUserId = getLoginUserId();
  119 + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
105 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( 120 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
106 convertList(members, ImGroupMemberDO::getUserId)); 121 convertList(members, ImGroupMemberDO::getUserId));
107 - return success(convertList(members, m -> {  
108 - ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class);  
109 - MapUtils.findAndThen(userMap, m.getUserId(), user -> 122 + return convertList(members, member -> {
  123 + ImGroupMemberRespVO vo = BeanUtils.toBean(member, ImGroupMemberRespVO.class);
  124 + MapUtils.findAndThen(userMap, member.getUserId(), user ->
110 vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())); 125 vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
111 - hidePrivateFieldsIfNotSelf(vo, m.getUserId(), loginUserId); 126 + hidePrivateFieldsIfNotSelf(vo, member.getUserId(), loginUserId);
112 return vo; 127 return vo;
113 - })); 128 + });
114 } 129 }
115 130
116 /** 131 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java
@@ -19,9 +19,12 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -19,9 +19,12 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
19 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 19 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
20 import io.swagger.v3.oas.annotations.Operation; 20 import io.swagger.v3.oas.annotations.Operation;
21 import io.swagger.v3.oas.annotations.Parameter; 21 import io.swagger.v3.oas.annotations.Parameter;
  22 +import io.swagger.v3.oas.annotations.Parameters;
22 import io.swagger.v3.oas.annotations.tags.Tag; 23 import io.swagger.v3.oas.annotations.tags.Tag;
23 import jakarta.annotation.Resource; 24 import jakarta.annotation.Resource;
24 import jakarta.validation.Valid; 25 import jakarta.validation.Valid;
  26 +import jakarta.validation.constraints.Max;
  27 +import jakarta.validation.constraints.Min;
25 import jakarta.validation.constraints.NotNull; 28 import jakarta.validation.constraints.NotNull;
26 import jakarta.validation.constraints.Size; 29 import jakarta.validation.constraints.Size;
27 import org.springframework.validation.annotation.Validated; 30 import org.springframework.validation.annotation.Validated;
@@ -88,6 +91,22 @@ public class ImGroupRequestController { @@ -88,6 +91,22 @@ public class ImGroupRequestController {
88 return success(buildVOList(list)); 91 return success(buildVOList(list));
89 } 92 }
90 93
  94 + @GetMapping("/pull")
  95 + @Operation(summary = "增量拉取「我管理的群」下的加群申请(重连 / 离线补偿)")
  96 + @Parameters({
  97 + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"),
  98 + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"),
  99 + @Parameter(name = "limit", description = "单次拉取条数", required = true)
  100 + })
  101 + public CommonResult<List<ImGroupRequestRespVO>> pullMyGroupRequestList(
  102 + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime,
  103 + @RequestParam(value = "lastId", required = false) Long lastId,
  104 + @RequestParam("limit") @Min(1) @Max(200) Integer limit) {
  105 + List<ImGroupRequestDO> list = groupRequestService.pullGroupRequestList(
  106 + getLoginUserId(), lastUpdateTime, lastId, limit);
  107 + return success(buildVOList(list));
  108 + }
  109 +
91 @GetMapping("/list-by-group") 110 @GetMapping("/list-by-group")
92 @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查") 111 @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查")
93 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") 112 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java
@@ -50,6 +50,12 @@ public class ImGroupRespVO { @@ -50,6 +50,12 @@ public class ImGroupRespVO {
50 @Schema(description = "当前登录用户在该群的成员状态:在群 / 已退群(历史退群群仍返回,供前端展示离线消息的群名 / 头像)") 50 @Schema(description = "当前登录用户在该群的成员状态:在群 / 已退群(历史退群群仍返回,供前端展示离线消息的群名 / 头像)")
51 private Integer joinStatus; // 参见 CommonStatusEnum 枚举 51 private Integer joinStatus; // 参见 CommonStatusEnum 枚举
52 52
  53 + @Schema(description = "当前登录用户对该群的备注")
  54 + private String groupRemark;
  55 +
  56 + @Schema(description = "当前登录用户是否免打扰")
  57 + private Boolean silent;
  58 +
53 @Schema(description = "群置顶消息列表,按 pin 顺序(最先置顶的在前);非该群有效成员时为空") 59 @Schema(description = "群置顶消息列表,按 pin 顺序(最先置顶的在前);非该群有效成员时为空")
54 private List<ImGroupMessageRespVO> pinnedMessages; 60 private List<ImGroupMessageRespVO> pinnedMessages;
55 61
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java
@@ -42,6 +42,9 @@ public class ImGroupRequestRespVO { @@ -42,6 +42,9 @@ public class ImGroupRequestRespVO {
42 @Schema(description = "申请创建时间") 42 @Schema(description = "申请创建时间")
43 private LocalDateTime createTime; 43 private LocalDateTime createTime;
44 44
  45 + @Schema(description = "申请更新时间;增量拉取游标")
  46 + private LocalDateTime updateTime;
  47 +
45 // ========== 下面是聚合字段,方便前端显示 ========== 48 // ========== 下面是聚合字段,方便前端显示 ==========
46 49
47 @Schema(description = "申请人 / 被邀请人昵称(实时聚合自 AdminUser)", example = "芋道") 50 @Schema(description = "申请人 / 被邀请人昵称(实时聚合自 AdminUser)", example = "芋道")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java
@@ -75,7 +75,6 @@ public class ImChannelManagerController { @@ -75,7 +75,6 @@ public class ImChannelManagerController {
75 @GetMapping("/simple-list") 75 @GetMapping("/simple-list")
76 @Operation(summary = "获得启用的频道精简列表;前端表单选择频道时调用") 76 @Operation(summary = "获得启用的频道精简列表;前端表单选择频道时调用")
77 public CommonResult<List<ImChannelRespVO>> getSimpleChannelList() { 77 public CommonResult<List<ImChannelRespVO>> getSimpleChannelList() {
78 - // TODO DONE @AI:getChannelListByStatus 统一命名  
79 List<ImChannelDO> list = channelService.getChannelListByStatus(CommonStatusEnum.ENABLE.getStatus()); 78 List<ImChannelDO> list = channelService.getChannelListByStatus(CommonStatusEnum.ENABLE.getStatus());
80 return success(BeanUtils.toBean(list, ImChannelRespVO.class)); 79 return success(BeanUtils.toBean(list, ImChannelRespVO.class));
81 } 80 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java
@@ -29,7 +29,7 @@ public class ImChannelMessageRespVO { @@ -29,7 +29,7 @@ public class ImChannelMessageRespVO {
29 private String materialCoverUrl; 29 private String materialCoverUrl;
30 30
31 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125") 31 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125")
32 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 32 + private Integer type; // 参见 ImContentTypeEnum 枚举类
33 33
34 @Schema(description = "消息内容;payload JSON 快照") 34 @Schema(description = "消息内容;payload JSON 快照")
35 private String content; 35 private String content;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java
@@ -22,7 +22,7 @@ public class ImGroupMessageManagerPageReqVO extends PageParam { @@ -22,7 +22,7 @@ public class ImGroupMessageManagerPageReqVO extends PageParam {
22 private Long senderId; 22 private Long senderId;
23 23
24 @Schema(description = "消息类型", example = "1") 24 @Schema(description = "消息类型", example = "1")
25 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 25 + private Integer type; // 参见 ImContentTypeEnum 枚举类
26 26
27 @Schema(description = "消息内容", example = "你好") 27 @Schema(description = "消息内容", example = "你好")
28 private String content; 28 private String content;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java
@@ -29,7 +29,7 @@ public class ImGroupMessageManagerRespVO { @@ -29,7 +29,7 @@ public class ImGroupMessageManagerRespVO {
29 private String senderNickname; 29 private String senderNickname;
30 30
31 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 31 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
32 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 32 + private Integer type; // 参见 ImContentTypeEnum 枚举类
33 33
34 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) 34 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
35 private String content; 35 private String content;
@@ -43,7 +43,7 @@ public class ImGroupMessageManagerRespVO { @@ -43,7 +43,7 @@ public class ImGroupMessageManagerRespVO {
43 private List<String> atUserNicknames; 43 private List<String> atUserNicknames;
44 44
45 @Schema(description = "回执状态", example = "0") 45 @Schema(description = "回执状态", example = "0")
46 - private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类 46 + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
47 47
48 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) 48 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
49 private LocalDateTime sendTime; 49 private LocalDateTime sendTime;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java
@@ -22,7 +22,7 @@ public class ImPrivateMessageManagerPageReqVO extends PageParam { @@ -22,7 +22,7 @@ public class ImPrivateMessageManagerPageReqVO extends PageParam {
22 private Long receiverId; 22 private Long receiverId;
23 23
24 @Schema(description = "消息类型", example = "1") 24 @Schema(description = "消息类型", example = "1")
25 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 25 + private Integer type; // 参见 ImContentTypeEnum 枚举类
26 26
27 @Schema(description = "消息内容", example = "你好") 27 @Schema(description = "消息内容", example = "你好")
28 private String content; 28 private String content;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java
@@ -28,7 +28,7 @@ public class ImPrivateMessageManagerRespVO { @@ -28,7 +28,7 @@ public class ImPrivateMessageManagerRespVO {
28 private String receiverNickname; 28 private String receiverNickname;
29 29
30 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 30 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
31 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 31 + private Integer type; // 参见 ImContentTypeEnum 枚举类
32 32
33 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) 33 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
34 private String content; 34 private String content;
@@ -36,6 +36,9 @@ public class ImPrivateMessageManagerRespVO { @@ -36,6 +36,9 @@ public class ImPrivateMessageManagerRespVO {
36 @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 36 @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
37 private Integer status; // 参见 ImMessageStatusEnum 枚举类 37 private Integer status; // 参见 ImMessageStatusEnum 枚举类
38 38
  39 + @Schema(description = "回执状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  40 + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
  41 +
39 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) 42 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
40 private LocalDateTime sendTime; 43 private LocalDateTime sendTime;
41 44
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java
@@ -112,7 +112,7 @@ public class ImStatisticsManagerController { @@ -112,7 +112,7 @@ public class ImStatisticsManagerController {
112 } 112 }
113 113
114 @GetMapping("/message-type-distribution") 114 @GetMapping("/message-type-distribution")
115 - @Operation(summary = "获得消息类型分布(最近 30 天)") 115 + @Operation(summary = "获得内容类型分布(最近 30 天)")
116 @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") 116 @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')")
117 public CommonResult<List<ImStatisticsManagerMessageTypeRespVO>> getMessageTypeDistribution() { 117 public CommonResult<List<ImStatisticsManagerMessageTypeRespVO>> getMessageTypeDistribution() {
118 LocalDateTime endTime = LocalDate.now().plusDays(1).atStartOfDay(); 118 LocalDateTime endTime = LocalDate.now().plusDays(1).atStartOfDay();
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java
@@ -3,12 +3,12 @@ package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo; @@ -3,12 +3,12 @@ package cn.iocoder.yudao.module.im.controller.admin.manager.statistics.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -@Schema(description = "管理后台 - IM 数据看板消息类型分布项 Response VO") 6 +@Schema(description = "管理后台 - IM 数据看板内容类型分布项 Response VO")
7 @Data 7 @Data
8 public class ImStatisticsManagerMessageTypeRespVO { 8 public class ImStatisticsManagerMessageTypeRespVO {
9 9
10 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 10 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
11 - private Integer type; // 参见 ImMessageTypeEnum 枚举类 11 + private Integer type; // 参见 ImContentTypeEnum 枚举类
12 12
13 @Schema(description = "消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000") 13 @Schema(description = "消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000")
14 private Long value; 14 private Long value;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO; 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO;
7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 7 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
8 -import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 8 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
9 import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService; 9 import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService;
10 import io.swagger.v3.oas.annotations.Operation; 10 import io.swagger.v3.oas.annotations.Operation;
11 import io.swagger.v3.oas.annotations.Parameter; 11 import io.swagger.v3.oas.annotations.Parameter;
@@ -40,25 +40,25 @@ public class ImChannelMessageController { @@ -40,25 +40,25 @@ public class ImChannelMessageController {
40 40
41 @GetMapping("/pull") 41 @GetMapping("/pull")
42 @Operation(summary = "拉取频道消息(离线增量);按 minId 游标分页") 42 @Operation(summary = "拉取频道消息(离线增量);按 minId 游标分页")
43 - public CommonResult<List<ImChannelMessagePullRespVO>> pull( 43 + public CommonResult<List<ImChannelMessagePullRespVO>> pullChannelMessageList(
44 @RequestParam(value = "minId", defaultValue = "0") @PositiveOrZero(message = "minId 不能小于 0") Long minId, 44 @RequestParam(value = "minId", defaultValue = "0") @PositiveOrZero(message = "minId 不能小于 0") Long minId,
45 @RequestParam(value = "size", defaultValue = "100") 45 @RequestParam(value = "size", defaultValue = "100")
46 @Min(value = 1, message = "size 必须大于 0") 46 @Min(value = 1, message = "size 必须大于 0")
47 @Max(value = 200, message = "size 一次最多 200 条") Integer size) { 47 @Max(value = 200, message = "size 一次最多 200 条") Integer size) {
48 // 1. 拉取消息列表 48 // 1. 拉取消息列表
49 Long userId = getLoginUserId(); 49 Long userId = getLoginUserId();
50 - List<ImChannelMessageDO> list = channelMessageService.getMessageListForPull(userId, minId, size); 50 + List<ImChannelMessageDO> list = channelMessageService.pullChannelMessageList(userId, minId, size);
51 if (CollUtil.isEmpty(list)) { 51 if (CollUtil.isEmpty(list)) {
52 return success(Collections.emptyList()); 52 return success(Collections.emptyList());
53 } 53 }
54 - // 2. 按 Redis 已读游标补 status;device A 已读后 device B 拉到这条不再算入未读 54 + // 2. 按已读游标补 receiptStatus(已读 DONE / 未读 PENDING)
55 Map<Long, Long> readMaxByChannel = channelMessageService.getChannelReadMaxMessageIdMap( 55 Map<Long, Long> readMaxByChannel = channelMessageService.getChannelReadMaxMessageIdMap(
56 userId, convertSet(list, ImChannelMessageDO::getChannelId)); 56 userId, convertSet(list, ImChannelMessageDO::getChannelId));
57 return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class, vo -> { 57 return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class, vo -> {
58 Long readMax = readMaxByChannel.get(vo.getChannelId()); 58 Long readMax = readMaxByChannel.get(vo.getChannelId());
59 - vo.setStatus(readMax != null && readMax >= vo.getId()  
60 - ? ImMessageStatusEnum.READ.getStatus()  
61 - : ImMessageStatusEnum.UNREAD.getStatus()); 59 + vo.setReceiptStatus(readMax != null && readMax >= vo.getId()
  60 + ? ImMessageReceiptStatusEnum.DONE.getStatus()
  61 + : ImMessageReceiptStatusEnum.PENDING.getStatus());
62 })); 62 }));
63 } 63 }
64 64
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java
@@ -61,7 +61,7 @@ public class ImPrivateMessageController { @@ -61,7 +61,7 @@ public class ImPrivateMessageController {
61 61
62 @GetMapping("/max-read-message-id") 62 @GetMapping("/max-read-message-id")
63 @Operation(summary = "查询对方已读到我发的最大消息 id", 63 @Operation(summary = "查询对方已读到我发的最大消息 id",
64 - description = "用于多端 / 离线场景下的已读位置补齐:进入会话或断线重连后调用,结果用于翻转本地自发消息状态") 64 + description = "用于多端 / 离线场景下的已读位置补齐:进入会话或断线重连后调用,据此把本地自发消息更新为已读")
65 @Parameter(name = "peerId", description = "对方用户编号", required = true, example = "2") 65 @Parameter(name = "peerId", description = "对方用户编号", required = true, example = "2")
66 public CommonResult<Long> getMaxReadMessageId(@RequestParam("peerId") Long peerId) { 66 public CommonResult<Long> getMaxReadMessageId(@RequestParam("peerId") Long peerId) {
67 return success(privateMessageService.getMaxReadMessageId(getLoginUserId(), peerId)); 67 return success(privateMessageService.getMaxReadMessageId(getLoginUserId(), peerId));
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java
@@ -24,8 +24,8 @@ public class ImChannelMessagePullRespVO { @@ -24,8 +24,8 @@ public class ImChannelMessagePullRespVO {
24 @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED) 24 @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED)
25 private String content; 25 private String content;
26 26
27 - @Schema(description = "当前用户的已读态;按 Redis 游标计算填充", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
28 - private Integer status; // 参见 ImMessageStatusEnum 枚举类 27 + @Schema(description = "当前用户回执 / 已读态;按 Redis 读位置计算(已读 DONE,未读 PENDING)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  28 + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
29 29
30 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) 30 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
31 private LocalDateTime sendTime; 31 private LocalDateTime sendTime;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java
1 package cn.iocoder.yudao.module.im.controller.admin.message.vo.group; 1 package cn.iocoder.yudao.module.im.controller.admin.message.vo.group;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 4 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
5 import com.fasterxml.jackson.annotation.JsonIgnore; 5 import com.fasterxml.jackson.annotation.JsonIgnore;
6 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
7 import jakarta.validation.constraints.AssertTrue; 7 import jakarta.validation.constraints.AssertTrue;
@@ -28,7 +28,7 @@ public class ImGroupMessageSendReqVO { @@ -28,7 +28,7 @@ public class ImGroupMessageSendReqVO {
28 28
29 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 29 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
30 @NotNull(message = "消息类型不能为空") 30 @NotNull(message = "消息类型不能为空")
31 - @InEnum(ImMessageTypeEnum.class) 31 + @InEnum(ImContentTypeEnum.class)
32 private Integer type; 32 private Integer type;
33 33
34 @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") 34 @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}")
@@ -47,7 +47,7 @@ public class ImGroupMessageSendReqVO { @@ -47,7 +47,7 @@ public class ImGroupMessageSendReqVO {
47 @AssertTrue(message = "消息类型不允许") 47 @AssertTrue(message = "消息类型不允许")
48 @JsonIgnore 48 @JsonIgnore
49 public boolean isTypeNormal() { 49 public boolean isTypeNormal() {
50 - return type == null || ImMessageTypeEnum.validate(type).isNormal(); 50 + return type == null || ImContentTypeEnum.validate(type).isNormal();
51 } 51 }
52 52
53 } 53 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java
@@ -31,7 +31,10 @@ public class ImPrivateMessageRespVO { @@ -31,7 +31,10 @@ public class ImPrivateMessageRespVO {
31 private String content; 31 private String content;
32 32
33 @Schema(description = "消息状态", example = "0") 33 @Schema(description = "消息状态", example = "0")
34 - private Integer status; 34 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  35 +
  36 + @Schema(description = "回执状态", example = "0")
  37 + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类
35 38
36 @Schema(description = "发送时间") 39 @Schema(description = "发送时间")
37 private LocalDateTime sendTime; 40 private LocalDateTime sendTime;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java
1 package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; 1 package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 4 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
5 import com.fasterxml.jackson.annotation.JsonIgnore; 5 import com.fasterxml.jackson.annotation.JsonIgnore;
6 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
7 import jakarta.validation.constraints.AssertTrue; 7 import jakarta.validation.constraints.AssertTrue;
@@ -26,20 +26,23 @@ public class ImPrivateMessageSendReqVO { @@ -26,20 +26,23 @@ public class ImPrivateMessageSendReqVO {
26 26
27 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 27 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
28 @NotNull(message = "消息类型不能为空") 28 @NotNull(message = "消息类型不能为空")
29 - @InEnum(ImMessageTypeEnum.class) 29 + @InEnum(ImContentTypeEnum.class)
30 private Integer type; 30 private Integer type;
31 31
32 @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") 32 @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}")
33 @NotEmpty(message = "消息内容不能为空") 33 @NotEmpty(message = "消息内容不能为空")
34 private String content; 34 private String content;
35 35
  36 + @Schema(description = "是否需要回执", example = "false")
  37 + private Boolean receipt;
  38 +
36 /** 39 /**
37 * 仅允许用户消息(normal)类型 40 * 仅允许用户消息(normal)类型
38 */ 41 */
39 @AssertTrue(message = "消息类型不允许") 42 @AssertTrue(message = "消息类型不允许")
40 @JsonIgnore 43 @JsonIgnore
41 public boolean isTypeNormal() { 44 public boolean isTypeNormal() {
42 - return type == null || ImMessageTypeEnum.validate(type).isNormal(); 45 + return type == null || ImContentTypeEnum.validate(type).isNormal();
43 } 46 }
44 47
45 } 48 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.dataobject.conversation;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
  5 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
  7 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
  8 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
  9 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  10 +import com.baomidou.mybatisplus.annotation.KeySequence;
  11 +import com.baomidou.mybatisplus.annotation.TableId;
  12 +import com.baomidou.mybatisplus.annotation.TableName;
  13 +import lombok.*;
  14 +
  15 +import java.time.LocalDateTime;
  16 +
  17 +/**
  18 + * IM 会话读位置 DO
  19 + * <p>
  20 + * 只表达「用户在某个会话的最大已读位置」,私聊 / 群聊 / 频道统一落这张表,是读位置的唯一权威。
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@TableName("im_conversation_read")
  25 +@KeySequence("im_conversation_read_seq")
  26 +@Data
  27 +@EqualsAndHashCode(callSuper = true)
  28 +@ToString(callSuper = true)
  29 +@Builder
  30 +@NoArgsConstructor
  31 +@AllArgsConstructor
  32 +public class ImConversationReadDO extends BaseDO {
  33 +
  34 + /**
  35 + * 编号
  36 + */
  37 + @TableId
  38 + private Long id;
  39 + /**
  40 + * 用户编号
  41 + *
  42 + * 关联 AdminUserDO 的 id 字段
  43 + */
  44 + private Long userId;
  45 + /**
  46 + * 会话类型
  47 + * <p>
  48 + * 枚举 {@link ImConversationTypeEnum}
  49 + */
  50 + private Integer conversationType;
  51 + /**
  52 + * 目标编号
  53 + * <p>
  54 + * 私聊关联 AdminUserDO 的 id;群聊关联 {@link ImGroupDO#getId()};频道关联 {@link ImChannelDO#getId()}
  55 + */
  56 + private Long targetId;
  57 + /**
  58 + * 最大已读消息编号
  59 + * <p>
  60 + * 关联 {@link ImPrivateMessageDO#getId()}、{@link ImGroupMessageDO#getId()}、{@link ImChannelMessageDO#getId()}
  61 + */
  62 + private Long messageId;
  63 + /**
  64 + * 最近已读时间
  65 + */
  66 + private LocalDateTime readTime;
  67 +
  68 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; 4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO;
6 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
7 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 7 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
8 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
9 import com.baomidou.mybatisplus.annotation.TableField; 9 import com.baomidou.mybatisplus.annotation.TableField;
10 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
@@ -54,7 +54,7 @@ public class ImChannelMessageDO extends BaseDO { @@ -54,7 +54,7 @@ public class ImChannelMessageDO extends BaseDO {
54 /** 54 /**
55 * 消息类型 55 * 消息类型
56 * <p> 56 * <p>
57 - * 枚举 {@link ImMessageTypeEnum} 57 + * 枚举 {@link ImContentTypeEnum}
58 */ 58 */
59 private Integer type; 59 private Integer type;
60 /** 60 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java
@@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.im.dal.dataobject.message; @@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.im.dal.dataobject.message;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; 4 import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
6 -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 6 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
7 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 7 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
8 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 8 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
9 import com.baomidou.mybatisplus.annotation.KeySequence; 9 import com.baomidou.mybatisplus.annotation.KeySequence;
10 import com.baomidou.mybatisplus.annotation.TableField; 10 import com.baomidou.mybatisplus.annotation.TableField;
11 import com.baomidou.mybatisplus.annotation.TableId; 11 import com.baomidou.mybatisplus.annotation.TableId;
@@ -54,7 +54,7 @@ public class ImGroupMessageDO extends BaseDO { @@ -54,7 +54,7 @@ public class ImGroupMessageDO extends BaseDO {
54 /** 54 /**
55 * 消息类型 55 * 消息类型
56 * <p> 56 * <p>
57 - * 枚举 {@link ImMessageTypeEnum} 57 + * 枚举 {@link ImContentTypeEnum}
58 */ 58 */
59 private Integer type; 59 private Integer type;
60 /** 60 /**
@@ -67,8 +67,6 @@ public class ImGroupMessageDO extends BaseDO { @@ -67,8 +67,6 @@ public class ImGroupMessageDO extends BaseDO {
67 * 消息状态 67 * 消息状态
68 * <p> 68 * <p>
69 * 枚举 {@link ImMessageStatusEnum} 69 * 枚举 {@link ImMessageStatusEnum}
70 - *  
71 - * 为什么没有 READ 状态?与单聊的差异:单聊用 UNREAD/READ 跟踪每条消息状态,群聊用 Redis 存储每个成员的已读位置(游标模型)  
72 */ 70 */
73 private Integer status; 71 private Integer status;
74 /** 72 /**
@@ -94,7 +92,7 @@ public class ImGroupMessageDO extends BaseDO { @@ -94,7 +92,7 @@ public class ImGroupMessageDO extends BaseDO {
94 /** 92 /**
95 * 回执状态 93 * 回执状态
96 * <p> 94 * <p>
97 - * 枚举 {@link ImGroupMessageReceiptStatusEnum} 95 + * 枚举 {@link ImMessageReceiptStatusEnum}
98 */ 96 */
99 private Integer receiptStatus; 97 private Integer receiptStatus;
100 98
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java
1 package cn.iocoder.yudao.module.im.dal.dataobject.message; 1 package cn.iocoder.yudao.module.im.dal.dataobject.message;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
4 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 5 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
5 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 6 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
7 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
8 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
@@ -49,7 +50,7 @@ public class ImPrivateMessageDO extends BaseDO { @@ -49,7 +50,7 @@ public class ImPrivateMessageDO extends BaseDO {
49 /** 50 /**
50 * 消息类型 51 * 消息类型
51 * <p> 52 * <p>
52 - * 枚举 {@link ImMessageTypeEnum} 53 + * 枚举 {@link ImContentTypeEnum}
53 */ 54 */
54 private Integer type; 55 private Integer type;
55 /** 56 /**
@@ -65,6 +66,12 @@ public class ImPrivateMessageDO extends BaseDO { @@ -65,6 +66,12 @@ public class ImPrivateMessageDO extends BaseDO {
65 */ 66 */
66 private Integer status; 67 private Integer status;
67 /** 68 /**
  69 + * 回执状态
  70 + * <p>
  71 + * 枚举 {@link ImMessageReceiptStatusEnum}
  72 + */
  73 + private Integer receiptStatus;
  74 + /**
68 * 发送时间 75 * 发送时间
69 */ 76 */
70 private LocalDateTime sendTime; 77 private LocalDateTime sendTime;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/CardMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; 3 import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
4 import lombok.Data; 4 import lombok.Data;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/LocationMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MaterialMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.experimental.Accessors; 4 import lombok.experimental.Accessors;
@@ -6,7 +6,7 @@ import lombok.experimental.Accessors; @@ -6,7 +6,7 @@ import lombok.experimental.Accessors;
6 /** 6 /**
7 * 频道素材消息 payload 7 * 频道素材消息 payload
8 * <p> 8 * <p>
9 - * 对应 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum#MATERIAL}(type=125)。 9 + * 对应 {@link cn.iocoder.yudao.module.im.enums.ImContentTypeEnum#MATERIAL}(type=125)。
10 * 客户端按本字段集渲染图文卡片; 10 * 客户端按本字段集渲染图文卡片;
11 * 富文本正文不在本 payload 中传递,点击详情时另调 /im/channel/material/get-content?id= 按需拉取。 11 * 富文本正文不在本 payload 中传递,点击详情时另调 /im/channel/material/get-content?id= 按需拉取。
12 */ 12 */
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MergeMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 3 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 import java.util.List; 6 import java.util.List;
@@ -47,7 +47,7 @@ public class MergeMessage { @@ -47,7 +47,7 @@ public class MergeMessage {
47 /** 47 /**
48 * 消息类型 48 * 消息类型
49 * <p> 49 * <p>
50 - * 枚举 {@link ImMessageTypeEnum} 50 + * 枚举 {@link ImContentTypeEnum}
51 */ 51 */
52 private Integer type; 52 private Integer type;
53 /** 53 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 3 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.experimental.Accessors; 5 import lombok.experimental.Accessors;
6 6
@@ -39,7 +39,7 @@ public class QuoteMessage { @@ -39,7 +39,7 @@ public class QuoteMessage {
39 /** 39 /**
40 * 被引用消息类型 40 * 被引用消息类型
41 * <p> 41 * <p>
42 - * 枚举 {@link ImMessageTypeEnum} 42 + * 枚举 {@link ImContentTypeEnum}
43 */ 43 */
44 private Integer type; 44 private Integer type;
45 /** 45 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/RecallMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.experimental.Accessors; 4 import lombok.experimental.Accessors;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.message; 1 +package cn.iocoder.yudao.module.im.dal.dataobject.message.content;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.mysql.conversation;
  2 +
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
  7 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.util.Collection;
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * IM 会话读位置 Mapper
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Mapper
  20 +public interface ImConversationReadMapper extends BaseMapperX<ImConversationReadDO> {
  21 +
  22 + default ImConversationReadDO selectByUserIdAndConversation(Long userId, Integer conversationType, Long conversationId) {
  23 + return selectOne(new LambdaQueryWrapperX<ImConversationReadDO>()
  24 + .eq(ImConversationReadDO::getUserId, userId)
  25 + .eq(ImConversationReadDO::getConversationType, conversationType)
  26 + .eq(ImConversationReadDO::getTargetId, conversationId));
  27 + }
  28 +
  29 + default List<ImConversationReadDO> selectListByConversation(Integer conversationType, Long conversationId) {
  30 + return selectList(new LambdaQueryWrapperX<ImConversationReadDO>()
  31 + .eq(ImConversationReadDO::getConversationType, conversationType)
  32 + .eq(ImConversationReadDO::getTargetId, conversationId));
  33 + }
  34 +
  35 + default List<ImConversationReadDO> selectListByUserIdAndConversations(Long userId, Integer conversationType,
  36 + Collection<Long> conversationIds) {
  37 + return selectList(new LambdaQueryWrapperX<ImConversationReadDO>()
  38 + .eq(ImConversationReadDO::getUserId, userId)
  39 + .eq(ImConversationReadDO::getConversationType, conversationType)
  40 + .in(ImConversationReadDO::getTargetId, conversationIds));
  41 + }
  42 +
  43 + /**
  44 + * 增量拉取当前用户的会话读位置(按 update_time + id 正向游标)
  45 + *
  46 + * @param userId 当前用户编号
  47 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
  48 + * @param lastId 上次拉取到的记录编号;首次拉取传 null
  49 + * @param limit 拉取数量
  50 + * @return 会话读位置列表
  51 + */
  52 + default List<ImConversationReadDO> selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  53 + LambdaQueryWrapperX<ImConversationReadDO> query = new LambdaQueryWrapperX<ImConversationReadDO>()
  54 + .eq(ImConversationReadDO::getUserId, userId);
  55 + if (lastUpdateTime != null && lastId != null) {
  56 + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
  57 + query.and(w -> w.gt(ImConversationReadDO::getUpdateTime, lastTime)
  58 + .or(n -> n.eq(ImConversationReadDO::getUpdateTime, lastTime).gt(ImConversationReadDO::getId, lastId)));
  59 + }
  60 + return selectList(query.orderByAsc(ImConversationReadDO::getUpdateTime).orderByAsc(ImConversationReadDO::getId)
  61 + .last("LIMIT " + limit));
  62 + }
  63 +
  64 + /**
  65 + * 单调递增更新读位置:仅当新位置更大时才更新
  66 + * <p>
  67 + * 通过 {@code read_message_id < messageId} 的 CAS 条件,保证乱序 / 并发上报时读位置不会回退。
  68 + *
  69 + * @param id 记录编号
  70 + * @param messageId 新的最大已读消息编号
  71 + * @param readTime 已读时间
  72 + * @return 影响行数;0 表示新位置不大于已有位置,未更新
  73 + */
  74 + default int updateReadMessageIdToLarger(Long id, Long messageId, LocalDateTime readTime) {
  75 + return update(null, Wrappers.<ImConversationReadDO>lambdaUpdate()
  76 + .set(ImConversationReadDO::getMessageId, messageId)
  77 + .set(ImConversationReadDO::getReadTime, readTime)
  78 + .set(ImConversationReadDO::getUpdateTime, readTime)
  79 + .eq(ImConversationReadDO::getId, id)
  80 + .lt(ImConversationReadDO::getMessageId, messageId));
  81 + }
  82 +
  83 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.friend; 1 package cn.iocoder.yudao.module.im.dal.mysql.friend;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -31,6 +32,28 @@ public interface ImFriendMapper extends BaseMapperX&lt;ImFriendDO&gt; { @@ -31,6 +32,28 @@ public interface ImFriendMapper extends BaseMapperX&lt;ImFriendDO&gt; {
31 .eq(ImFriendDO::getUserId, userId) 32 .eq(ImFriendDO::getUserId, userId)
32 .orderByDesc(ImFriendDO::getId)); 33 .orderByDesc(ImFriendDO::getId));
33 } 34 }
  35 +
  36 + /**
  37 + * 增量拉取当前用户的好友关系(含已删除,按 update_time + id 正向游标)
  38 + *
  39 + * @param userId 当前用户编号
  40 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
  41 + * @param lastId 上次拉取到的记录编号;首次拉取传 null
  42 + * @param limit 拉取数量
  43 + * @return 好友关系列表
  44 + */
  45 + default List<ImFriendDO> selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  46 + LambdaQueryWrapperX<ImFriendDO> query = new LambdaQueryWrapperX<ImFriendDO>()
  47 + .eq(ImFriendDO::getUserId, userId);
  48 + if (lastUpdateTime != null && lastId != null) {
  49 + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
  50 + query.and(w -> w.gt(ImFriendDO::getUpdateTime, lastTime)
  51 + .or(n -> n.eq(ImFriendDO::getUpdateTime, lastTime).gt(ImFriendDO::getId, lastId)));
  52 + }
  53 + return selectList(query.orderByAsc(ImFriendDO::getUpdateTime).orderByAsc(ImFriendDO::getId)
  54 + .last("LIMIT " + limit));
  55 + }
  56 +
34 default List<ImFriendDO> selectListByUserIdAndStatus(Long userId, Integer status) { 57 default List<ImFriendDO> selectListByUserIdAndStatus(Long userId, Integer status) {
35 return selectList(new LambdaQueryWrapperX<ImFriendDO>() 58 return selectList(new LambdaQueryWrapperX<ImFriendDO>()
36 .eq(ImFriendDO::getUserId, userId) 59 .eq(ImFriendDO::getUserId, userId)
@@ -68,12 +91,14 @@ public interface ImFriendMapper extends BaseMapperX&lt;ImFriendDO&gt; { @@ -68,12 +91,14 @@ public interface ImFriendMapper extends BaseMapperX&lt;ImFriendDO&gt; {
68 91
69 @SuppressWarnings("UnusedReturnValue") 92 @SuppressWarnings("UnusedReturnValue")
70 default int updateReAddFields(Long id, Integer status, LocalDateTime addTime, 93 default int updateReAddFields(Long id, Integer status, LocalDateTime addTime,
  94 + LocalDateTime updateTime,
71 Boolean silent, Boolean pinned, Boolean blocked, 95 Boolean silent, Boolean pinned, Boolean blocked,
72 String displayName, Integer addSource) { 96 String displayName, Integer addSource) {
73 return update(null, Wrappers.<ImFriendDO>lambdaUpdate() 97 return update(null, Wrappers.<ImFriendDO>lambdaUpdate()
74 .eq(ImFriendDO::getId, id) 98 .eq(ImFriendDO::getId, id)
75 .set(ImFriendDO::getStatus, status) 99 .set(ImFriendDO::getStatus, status)
76 .set(ImFriendDO::getAddTime, addTime) 100 .set(ImFriendDO::getAddTime, addTime)
  101 + .set(ImFriendDO::getUpdateTime, updateTime)
77 .set(ImFriendDO::getSilent, silent) 102 .set(ImFriendDO::getSilent, silent)
78 .set(ImFriendDO::getPinned, pinned) 103 .set(ImFriendDO::getPinned, pinned)
79 .set(ImFriendDO::getBlocked, blocked) 104 .set(ImFriendDO::getBlocked, blocked)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.friend; 1 package cn.iocoder.yudao.module.im.dal.mysql.friend;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -28,6 +29,12 @@ public interface ImFriendRequestMapper extends BaseMapperX&lt;ImFriendRequestDO&gt; { @@ -28,6 +29,12 @@ public interface ImFriendRequestMapper extends BaseMapperX&lt;ImFriendRequestDO&gt; {
28 29
29 /** 30 /**
30 * 拉取「我相关」的好友申请列表 31 * 拉取「我相关」的好友申请列表
  32 + *
  33 + * @param userId 当前用户编号
  34 + * @param maxRequestUpdateTime 起始更新时间;首次拉取传 null
  35 + * @param maxId 起始记录编号;首次拉取传 null
  36 + * @param limit 拉取数量
  37 + * @return 好友申请列表
31 */ 38 */
32 default List<ImFriendRequestDO> selectMyList(Long userId, LocalDateTime maxRequestUpdateTime, 39 default List<ImFriendRequestDO> selectMyList(Long userId, LocalDateTime maxRequestUpdateTime,
33 Long maxId, int limit) { 40 Long maxId, int limit) {
@@ -45,6 +52,28 @@ public interface ImFriendRequestMapper extends BaseMapperX&lt;ImFriendRequestDO&gt; { @@ -45,6 +52,28 @@ public interface ImFriendRequestMapper extends BaseMapperX&lt;ImFriendRequestDO&gt; {
45 return selectList(wrapper); 52 return selectList(wrapper);
46 } 53 }
47 54
  55 + /**
  56 + * 增量拉取「我相关」的好友申请(双向 OR,按 update_time + id 正向游标)
  57 + *
  58 + * @param userId 当前用户编号
  59 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
  60 + * @param lastId 上次拉取到的记录编号;首次拉取传 null
  61 + * @param limit 拉取数量
  62 + * @return 好友申请列表
  63 + */
  64 + default List<ImFriendRequestDO> selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  65 + LambdaQueryWrapperX<ImFriendRequestDO> query = new LambdaQueryWrapperX<>();
  66 + query.and(w -> w.eq(ImFriendRequestDO::getFromUserId, userId)
  67 + .or().eq(ImFriendRequestDO::getToUserId, userId));
  68 + if (lastUpdateTime != null && lastId != null) {
  69 + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
  70 + query.and(w -> w.gt(ImFriendRequestDO::getUpdateTime, lastTime)
  71 + .or(n -> n.eq(ImFriendRequestDO::getUpdateTime, lastTime).gt(ImFriendRequestDO::getId, lastId)));
  72 + }
  73 + return selectList(query.orderByAsc(ImFriendRequestDO::getUpdateTime).orderByAsc(ImFriendRequestDO::getId)
  74 + .last("LIMIT " + limit));
  75 + }
  76 +
48 default int updateByIdAndHandleResult(Long id, Integer handleResult, ImFriendRequestDO updateObj) { 77 default int updateByIdAndHandleResult(Long id, Integer handleResult, ImFriendRequestDO updateObj) {
49 return update(updateObj, new LambdaUpdateWrapper<ImFriendRequestDO>() 78 return update(updateObj, new LambdaUpdateWrapper<ImFriendRequestDO>()
50 .eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult)); 79 .eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult));
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.group; 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -59,20 +58,8 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; { @@ -59,20 +58,8 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
59 .eq(ImGroupMemberDO::getStatus, status)); 58 .eq(ImGroupMemberDO::getStatus, status));
60 } 59 }
61 60
62 - /**  
63 - * 查询用户已退群的成员记录  
64 - * <p>  
65 - * 当 {@code minQuitTime} 非空时额外按 {@code quitTime ≥ minQuitTime} 过滤。  
66 - *  
67 - * @param userId 用户编号  
68 - * @param minQuitTime 最早退群时间(含),可空  
69 - * @return 已退群成员记录列表  
70 - */  
71 - default List<ImGroupMemberDO> selectQuitListByUserId(Long userId, LocalDateTime minQuitTime) {  
72 - return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()  
73 - .eq(ImGroupMemberDO::getUserId, userId)  
74 - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.DISABLE.getStatus())  
75 - .geIfPresent(ImGroupMemberDO::getQuitTime, minQuitTime)); 61 + default List<ImGroupMemberDO> selectListByUserId(Long userId) {
  62 + return selectList(ImGroupMemberDO::getUserId, userId);
76 } 63 }
77 64
78 @SuppressWarnings("UnusedReturnValue") 65 @SuppressWarnings("UnusedReturnValue")
@@ -143,7 +130,8 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; { @@ -143,7 +130,8 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
143 .set(ImGroupMemberDO::getAddSource, addSource) 130 .set(ImGroupMemberDO::getAddSource, addSource)
144 .set(ImGroupMemberDO::getInviterUserId, inviterUserId) 131 .set(ImGroupMemberDO::getInviterUserId, inviterUserId)
145 .set(ImGroupMemberDO::getQuitTime, null) 132 .set(ImGroupMemberDO::getQuitTime, null)
146 - .set(ImGroupMemberDO::getMuteEndTime, null)); 133 + .set(ImGroupMemberDO::getMuteEndTime, null)
  134 + .set(ImGroupMemberDO::getUpdateTime, joinTime));
147 } 135 }
148 136
149 } 137 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.group; 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -43,6 +44,28 @@ public interface ImGroupRequestMapper extends BaseMapperX&lt;ImGroupRequestDO&gt; { @@ -43,6 +44,28 @@ public interface ImGroupRequestMapper extends BaseMapperX&lt;ImGroupRequestDO&gt; {
43 .orderByDesc(ImGroupRequestDO::getId)); 44 .orderByDesc(ImGroupRequestDO::getId));
44 } 45 }
45 46
  47 + /**
  48 + * 增量拉取「我管理的群」下的加群申请(含已处理,按 update_time + id 正向游标)
  49 + *
  50 + * @param groupIds 群编号集合
  51 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null
  52 + * @param lastId 上次拉取到的记录编号;首次拉取传 null
  53 + * @param limit 拉取数量
  54 + * @return 加群申请列表
  55 + */
  56 + default List<ImGroupRequestDO> selectPullListByGroupIds(Collection<Long> groupIds, Long lastUpdateTime,
  57 + Long lastId, Integer limit) {
  58 + LambdaQueryWrapperX<ImGroupRequestDO> query = new LambdaQueryWrapperX<ImGroupRequestDO>()
  59 + .in(ImGroupRequestDO::getGroupId, groupIds);
  60 + if (lastUpdateTime != null && lastId != null) {
  61 + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime);
  62 + query.and(w -> w.gt(ImGroupRequestDO::getUpdateTime, lastTime)
  63 + .or(n -> n.eq(ImGroupRequestDO::getUpdateTime, lastTime).gt(ImGroupRequestDO::getId, lastId)));
  64 + }
  65 + return selectList(query.orderByAsc(ImGroupRequestDO::getUpdateTime).orderByAsc(ImGroupRequestDO::getId)
  66 + .last("LIMIT " + limit));
  67 + }
  68 +
46 default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) { 69 default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) {
47 return update(updateObj, new LambdaUpdateWrapper<ImGroupRequestDO>() 70 return update(updateObj, new LambdaUpdateWrapper<ImGroupRequestDO>()
48 .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); 71 .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult));
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
@@ -4,13 +4,15 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -4,13 +4,15 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  7 +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
7 import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; 8 import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 9 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
9 -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 10 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
10 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 11 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
11 import org.apache.ibatis.annotations.Mapper; 12 import org.apache.ibatis.annotations.Mapper;
12 13
13 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
  15 +import java.util.Collection;
14 import java.util.List; 16 import java.util.List;
15 17
16 /** 18 /**
@@ -22,48 +24,22 @@ import java.util.List; @@ -22,48 +24,22 @@ import java.util.List;
22 public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> { 24 public interface ImGroupMessageMapper extends BaseMapperX<ImGroupMessageDO> {
23 25
24 /** 26 /**
25 - * 根据 minId + 时间窗口增量拉取群聊消息(在群成员使用) 27 + * 根据 minId + 时间窗口增量拉取群聊消息
26 * 28 *
27 - * @param groupIds 用户当前仍在群内的群编号列表 29 + * @param userId 当前用户编号
  30 + * @param groupIds 候选群编号集合(当前在群 ∪ 窗口内退群),不能为空
28 * @param minId 最小消息 id(不含) 31 * @param minId 最小消息 id(不含)
29 * @param minSendTime 最早发送时间(不含),限制离线消息时间窗口 32 * @param minSendTime 最早发送时间(不含),限制离线消息时间窗口
30 * @param size 拉取数量 33 * @param size 拉取数量
31 * @return 消息列表(按 id 升序) 34 * @return 消息列表(按 id 升序)
32 */ 35 */
33 - default List<ImGroupMessageDO> selectListByMinId(List<Long> groupIds, Long minId, 36 + default List<ImGroupMessageDO> selectListByMinId(Long userId, Collection<Long> groupIds, Long minId,
34 LocalDateTime minSendTime, Integer size) { 37 LocalDateTime minSendTime, Integer size) {
35 QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>(); 38 QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>();
36 wrapper.in("group_id", groupIds) 39 wrapper.in("group_id", groupIds)
37 .gt("id", minId) 40 .gt("id", minId)
38 .gt("send_time", minSendTime) 41 .gt("send_time", minSendTime)
39 - .orderByAsc("id");  
40 - wrapper.limitN(size);  
41 - return selectList(wrapper);  
42 - }  
43 -  
44 - /**  
45 - * 查询"退群前"的离线消息(退群成员使用)  
46 - * <p>  
47 - * 语义:用户已退出某群,但仍需把 {@code minId} 之后、{@code minSendTime} 之后、不晚于退群时间的消息补齐到本地,便于前端看到完整上下文。  
48 - * <p>  
49 - * 撤回消息(status=RECALL)保留返回,与在群成员的 {@link #selectListByMinId} 行为一致,由前端按撤回信号渲染「消息已撤回」气泡。  
50 - *  
51 - * @param groupId 群编号  
52 - * @param minId 最小消息 id(不含)  
53 - * @param minSendTime 最早发送时间(不含)  
54 - * @param quitTime 退群时间(含),仅返回退群当时已存在的消息  
55 - * @param size 拉取数量  
56 - * @return 消息列表(按 id 升序)  
57 - */  
58 - default List<ImGroupMessageDO> selectListByGroupIdAndMinIdAndQuitTimeBefore(Long groupId, Long minId,  
59 - LocalDateTime minSendTime,  
60 - LocalDateTime quitTime,  
61 - Integer size) {  
62 - QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>();  
63 - wrapper.eq("group_id", groupId)  
64 - .gt("id", minId)  
65 - .gt("send_time", minSendTime)  
66 - .le("send_time", quitTime) 42 + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)
67 .orderByAsc("id"); 43 .orderByAsc("id");
68 wrapper.limitN(size); 44 wrapper.limitN(size);
69 return selectList(wrapper); 45 return selectList(wrapper);
@@ -72,17 +48,17 @@ public interface ImGroupMessageMapper extends BaseMapperX&lt;ImGroupMessageDO&gt; { @@ -72,17 +48,17 @@ public interface ImGroupMessageMapper extends BaseMapperX&lt;ImGroupMessageDO&gt; {
72 /** 48 /**
73 * 查询群聊历史消息(游标拉取) 49 * 查询群聊历史消息(游标拉取)
74 * 50 *
75 - * @param groupId 群编号  
76 - * @param maxId 起始消息 id(不含),为空则从最新开始  
77 - * @param limit 拉取数量  
78 - * @param joinTime 入群时间,仅返回入群之后的消息 51 + * @param userId 当前用户编号
  52 + * @param groupId 群编号
  53 + * @param maxId 起始消息 id(不含),为空则从最新开始
  54 + * @param limit 拉取数量
79 * @return 消息列表(按 id 倒序) 55 * @return 消息列表(按 id 倒序)
80 */ 56 */
81 - default List<ImGroupMessageDO> selectHistoryList(Long groupId, Long maxId, Integer limit, LocalDateTime joinTime) { 57 + default List<ImGroupMessageDO> selectHistoryListByUser(Long userId, Long groupId, Long maxId, Integer limit) {
82 QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>(); 58 QueryWrapperX<ImGroupMessageDO> wrapper = new QueryWrapperX<>();
83 wrapper.eq("group_id", groupId) 59 wrapper.eq("group_id", groupId)
84 .lt(maxId != null, "id", maxId) 60 .lt(maxId != null, "id", maxId)
85 - .ge(joinTime != null, "send_time", joinTime) 61 + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId)
86 .orderByDesc("id"); 62 .orderByDesc("id");
87 wrapper.limitN(limit); 63 wrapper.limitN(limit);
88 return selectList(wrapper); 64 return selectList(wrapper);
@@ -108,7 +84,7 @@ public interface ImGroupMessageMapper extends BaseMapperX&lt;ImGroupMessageDO&gt; { @@ -108,7 +84,7 @@ public interface ImGroupMessageMapper extends BaseMapperX&lt;ImGroupMessageDO&gt; {
108 default List<ImGroupMessageDO> selectListByGroupIdAndPendingReceipt(Long groupId, Long minId, Long maxId) { 84 default List<ImGroupMessageDO> selectListByGroupIdAndPendingReceipt(Long groupId, Long minId, Long maxId) {
109 return selectList(new LambdaQueryWrapperX<ImGroupMessageDO>() 85 return selectList(new LambdaQueryWrapperX<ImGroupMessageDO>()
110 .eq(ImGroupMessageDO::getGroupId, groupId) 86 .eq(ImGroupMessageDO::getGroupId, groupId)
111 - .eq(ImGroupMessageDO::getReceiptStatus, ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) 87 + .eq(ImGroupMessageDO::getReceiptStatus, ImMessageReceiptStatusEnum.PENDING.getStatus())
112 .gt(minId != null, ImGroupMessageDO::getId, minId) 88 .gt(minId != null, ImGroupMessageDO::getId, minId)
113 .le(ImGroupMessageDO::getId, maxId) 89 .le(ImGroupMessageDO::getId, maxId)
114 .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); 90 .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus()));
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
@@ -67,23 +67,13 @@ public interface ImPrivateMessageMapper extends BaseMapperX&lt;ImPrivateMessageDO&gt; @@ -67,23 +67,13 @@ public interface ImPrivateMessageMapper extends BaseMapperX&lt;ImPrivateMessageDO&gt;
67 .eq(ImPrivateMessageDO::getClientMessageId, clientMessageId)); 67 .eq(ImPrivateMessageDO::getClientMessageId, clientMessageId));
68 } 68 }
69 69
70 - default Long selectMaxIdBySenderIdAndReceiverIdAndStatus(Long senderId, Long receiverId, Integer status) {  
71 - ImPrivateMessageDO message = selectOne(new LambdaQueryWrapperX<ImPrivateMessageDO>()  
72 - .eq(ImPrivateMessageDO::getSenderId, senderId)  
73 - .eq(ImPrivateMessageDO::getReceiverId, receiverId)  
74 - .eq(ImPrivateMessageDO::getStatus, status)  
75 - .orderByDesc(ImPrivateMessageDO::getId)  
76 - .last("LIMIT 1"));  
77 - return message != null ? message.getId() : null;  
78 - }  
79 -  
80 - default int updateBySenderIdAndReceiverIdAndIdLeAndStatus(Long senderId, Long receiverId, Long maxMessageId,  
81 - Integer whereStatus, ImPrivateMessageDO updateObj) { 70 + default int updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(Long senderId, Long receiverId, Long maxMessageId,
  71 + Integer whereReceiptStatus, ImPrivateMessageDO updateObj) {
82 return update(updateObj, new LambdaQueryWrapperX<ImPrivateMessageDO>() 72 return update(updateObj, new LambdaQueryWrapperX<ImPrivateMessageDO>()
83 .eq(ImPrivateMessageDO::getSenderId, senderId) 73 .eq(ImPrivateMessageDO::getSenderId, senderId)
84 .eq(ImPrivateMessageDO::getReceiverId, receiverId) 74 .eq(ImPrivateMessageDO::getReceiverId, receiverId)
85 .le(ImPrivateMessageDO::getId, maxMessageId) 75 .le(ImPrivateMessageDO::getId, maxMessageId)
86 - .eq(ImPrivateMessageDO::getStatus, whereStatus)); 76 + .eq(ImPrivateMessageDO::getReceiptStatus, whereReceiptStatus));
87 } 77 }
88 78
89 default PageResult<ImPrivateMessageDO> selectPage(ImPrivateMessageManagerPageReqVO reqVO) { 79 default PageResult<ImPrivateMessageDO> selectPage(ImPrivateMessageManagerPageReqVO reqVO) {
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
@@ -154,7 +154,7 @@ public interface ImStatisticsManagerMapper { @@ -154,7 +154,7 @@ public interface ImStatisticsManagerMapper {
154 @Param("endTime") LocalDateTime endTime); 154 @Param("endTime") LocalDateTime endTime);
155 155
156 /** 156 /**
157 - * 区间内消息类型分布(私聊+群聊合并) 157 + * 区间内内容类型分布(私聊+群聊合并)
158 * 158 *
159 * @return [{type: 0, count: 123}, ...] 159 * @return [{type: 0, count: 123}, ...]
160 */ 160 */
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
@@ -9,20 +9,6 @@ package cn.iocoder.yudao.module.im.dal.redis; @@ -9,20 +9,6 @@ package cn.iocoder.yudao.module.im.dal.redis;
9 public interface RedisKeyConstants { 9 public interface RedisKeyConstants {
10 10
11 /** 11 /**
12 - * 群消息已读位置  
13 - * KEY 格式: im:group:message:read:{groupId}  
14 - * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId)  
15 - */  
16 - String GROUP_MESSAGE_READ = "im:group:message:read:%s";  
17 -  
18 - /**  
19 - * 频道消息已读位置  
20 - * KEY 格式: im:channel:message:read:{channelId}  
21 - * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId)  
22 - */  
23 - String CHANNEL_MESSAGE_READ = "im:channel:message:read:%s";  
24 -  
25 - /**  
26 * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态) 12 * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态)
27 * <p> 13 * <p>
28 * KEY 格式:friend_state:{userId}_{friendUserId} 14 * KEY 格式:friend_state:{userId}_{friendUserId}
@@ -44,7 +30,7 @@ public interface RedisKeyConstants { @@ -44,7 +30,7 @@ public interface RedisKeyConstants {
44 * KEY 格式:group_member_ids:{groupId} 30 * KEY 格式:group_member_ids:{groupId}
45 * VALUE 数据类型:List<Long> 31 * VALUE 数据类型:List<Long>
46 * <p> 32 * <p>
47 - * 说明:只缓存轻量的 userId 列表,适合"群消息推送目标"这类只关心 userId 的场景。 33 + * 说明:只缓存轻量的 userId 列表,适合「群消息推送目标」这类只关心 userId 的场景
48 */ 34 */
49 String GROUP_MEMBER_IDS = "group_member_ids"; 35 String GROUP_MEMBER_IDS = "group_member_ids";
50 36
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java deleted
1 -package cn.iocoder.yudao.module.im.dal.redis.message;  
2 -  
3 -import cn.hutool.core.convert.Convert;  
4 -import jakarta.annotation.Resource;  
5 -import org.springframework.data.redis.core.StringRedisTemplate;  
6 -import org.springframework.stereotype.Repository;  
7 -  
8 -import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.CHANNEL_MESSAGE_READ;  
9 -  
10 -/**  
11 - * IM 频道消息已读位置 Redis DAO  
12 - *  
13 - * @author 芋道源码  
14 - */  
15 -@Repository  
16 -public class ImChannelMessageReadRedisDAO {  
17 -  
18 - @Resource  
19 - private StringRedisTemplate stringRedisTemplate;  
20 -  
21 - /**  
22 - * 更新用户在某频道的最大已读消息编号  
23 - *  
24 - * @param channelId 频道编号  
25 - * @param userId 用户编号  
26 - * @param maxMessageId 最大已读消息编号  
27 - */  
28 - public void updateReadMaxMessageId(Long channelId, Long userId, Long maxMessageId) {  
29 - String key = formatKey(channelId);  
30 - stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());  
31 - }  
32 -  
33 - /**  
34 - * 获取用户在某频道的最大已读消息编号  
35 - *  
36 - * @param channelId 频道编号  
37 - * @param userId 用户编号  
38 - * @return 最大已读消息编号;不存在则返回 null  
39 - */  
40 - public Long getReadMaxMessageId(Long channelId, Long userId) {  
41 - String key = formatKey(channelId);  
42 - Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());  
43 - return Convert.toLong(val);  
44 - }  
45 -  
46 - private static String formatKey(Long channelId) {  
47 - return String.format(CHANNEL_MESSAGE_READ, channelId);  
48 - }  
49 -  
50 -}  
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java deleted
1 -package cn.iocoder.yudao.module.im.dal.redis.message;  
2 -  
3 -import cn.hutool.core.convert.Convert;  
4 -import jakarta.annotation.Resource;  
5 -import org.springframework.data.redis.core.StringRedisTemplate;  
6 -import org.springframework.stereotype.Repository;  
7 -  
8 -import java.util.Collection;  
9 -import java.util.HashMap;  
10 -import java.util.Map;  
11 -  
12 -import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MESSAGE_READ;  
13 -  
14 -/**  
15 - * IM 群消息已读位置 Redis DAO  
16 - *  
17 - * @author 芋道源码  
18 - */  
19 -@Repository  
20 -public class ImGroupMessageReadRedisDAO {  
21 -  
22 - @Resource  
23 - private StringRedisTemplate stringRedisTemplate;  
24 -  
25 - /**  
26 - * 更新用户在某群的最大已读消息编号  
27 - *  
28 - * @param groupId 群编号  
29 - * @param userId 用户编号  
30 - * @param maxMessageId 最大已读消息编号  
31 - */  
32 - public void updateReadMaxMessageId(Long groupId, Long userId, Long maxMessageId) {  
33 - String key = formatKey(groupId);  
34 - stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString());  
35 - }  
36 -  
37 - /**  
38 - * 获取用户在某群的最大已读消息编号  
39 - *  
40 - * @param groupId 群编号  
41 - * @param userId 用户编号  
42 - * @return 最大已读消息编号,不存在则返回 null  
43 - */  
44 - public Long getReadMaxMessageId(Long groupId, Long userId) {  
45 - String key = formatKey(groupId);  
46 - Object val = stringRedisTemplate.opsForHash().get(key, userId.toString());  
47 - return Convert.toLong(val);  
48 - }  
49 -  
50 - /**  
51 - * 获取某群所有用户的已读位置  
52 - *  
53 - * @param groupId 群编号  
54 - * @return userId → maxReadMessageId 映射  
55 - */  
56 - public Map<Long, Long> getReadMaxMessageIdMap(Long groupId) {  
57 - String key = formatKey(groupId);  
58 - Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(key);  
59 - // 转换为 Long → Long 的 Map  
60 - Map<Long, Long> result = new HashMap<>(entries.size());  
61 - entries.forEach((k, v) -> result.put(Long.parseLong(k.toString()), Long.parseLong(v.toString())));  
62 - return result;  
63 - }  
64 -  
65 - /**  
66 - * 删除用户在某群的已读位置  
67 - *  
68 - * @param groupId 群编号  
69 - * @param userId 用户编号  
70 - */  
71 - public void deleteReadMaxMessageId(Long groupId, Long userId) {  
72 - String key = formatKey(groupId);  
73 - stringRedisTemplate.opsForHash().delete(key, userId.toString());  
74 - }  
75 -  
76 - /**  
77 - * 批量删除用户在某群的已读位置  
78 - *  
79 - * @param groupId 群编号  
80 - * @param userIds 用户编号集合  
81 - */  
82 - public void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds) {  
83 - String key = formatKey(groupId);  
84 - Object[] hashKeys = userIds.stream().map(String::valueOf).toArray();  
85 - stringRedisTemplate.opsForHash().delete(key, hashKeys);  
86 - }  
87 -  
88 - /**  
89 - * 删除某群所有用户的已读位置(整个 Hash Key)  
90 - *  
91 - * @param groupId 群编号  
92 - */  
93 - public void deleteReadMaxMessageIdMap(Long groupId) {  
94 - String key = formatKey(groupId);  
95 - stringRedisTemplate.delete(key);  
96 - }  
97 -  
98 - private static String formatKey(Long groupId) {  
99 - return String.format(GROUP_MESSAGE_READ, groupId);  
100 - }  
101 -  
102 -}  
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java
1 -package cn.iocoder.yudao.module.im.enums.message; 1 +package cn.iocoder.yudao.module.im.enums;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
@@ -11,13 +11,13 @@ import java.util.Arrays; @@ -11,13 +11,13 @@ import java.util.Arrays;
11 import java.util.Set; 11 import java.util.Set;
12 12
13 /** 13 /**
14 - * IM 消息类型枚举 14 + * IM 内容类型枚举
15 * 15 *
16 * @author 芋道源码 16 * @author 芋道源码
17 */ 17 */
18 @Getter 18 @Getter
19 @RequiredArgsConstructor 19 @RequiredArgsConstructor
20 -public enum ImMessageTypeEnum implements ArrayValuable<Integer> { 20 +public enum ImContentTypeEnum implements ArrayValuable<Integer> {
21 21
22 // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ========== 22 // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ==========
23 /** 23 /**
@@ -128,10 +128,6 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -128,10 +128,6 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
128 RTC_CALL_END(1611, "通话结束", true, false), 128 RTC_CALL_END(1611, "通话结束", true, false),
129 129
130 // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ========== 130 // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ==========
131 - // TODO @芋艿:FRIEND_REQUEST_* 与 GROUP_REQUEST_* 都是 persistent=false 的 SysMsg,离线 pull 拉不到,  
132 - // 目前可能丢失「实时 toast 提醒」体验(业务状态不丢,前端上线 fetch{Friend,Group}RequestList 能补回来);  
133 - // 未来思考下怎么优化:候选方案 1)改 persistent=true 入私聊消息流 + 让客户端按 type 自渲染;  
134 - // 2)服务端补一个「未读通知拉取」接口给前端冷启动调用。  
135 /** 131 /**
136 * 对应 OpenIM:FriendApplicationApprovedNotification 1201 132 * 对应 OpenIM:FriendApplicationApprovedNotification 1201
137 * 对应自己的类:FriendRequestApprovedNotification 133 * 对应自己的类:FriendRequestApprovedNotification
@@ -285,9 +281,9 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -285,9 +281,9 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
285 /** 281 /**
286 * 对应 OpenIM:sdkws.GroupMemberInfoSetTips(GroupMemberInfoSetNotification 1516,窄化到 displayUserName) 282 * 对应 OpenIM:sdkws.GroupMemberInfoSetTips(GroupMemberInfoSetNotification 1516,窄化到 displayUserName)
287 * 对应自己的类:GroupMemberNicknameUpdateNotification 283 * 对应自己的类:GroupMemberNicknameUpdateNotification
288 - * 场景:成员修改自己在群里的昵称,全员广播;前端按 displayUserName 局部更新对应 member 284 + * 场景:成员修改自己在群里的昵称,在线成员同步对应 member
289 */ 285 */
290 - GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", true, false), 286 + GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", false, false),
291 /** 287 /**
292 * 对应 OpenIM:GroupMemberSetToAdminNotification 1517 288 * 对应 OpenIM:GroupMemberSetToAdminNotification 1517
293 * 对应自己的类:GroupAdminAddNotification 289 * 对应自己的类:GroupAdminAddNotification
@@ -339,7 +335,7 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -339,7 +335,7 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
339 */ 335 */
340 GROUP_BANNED(1533, "群封禁变更", true, false); 336 GROUP_BANNED(1533, "群封禁变更", true, false);
341 337
342 - public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); 338 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImContentTypeEnum::getType).toArray(Integer[]::new);
343 339
344 private static final Set<Integer> FRIEND_NOTIFICATION_TYPES = CollUtil.newHashSet( 340 private static final Set<Integer> FRIEND_NOTIFICATION_TYPES = CollUtil.newHashSet(
345 FRIEND_REQUEST_APPROVED.type, 341 FRIEND_REQUEST_APPROVED.type,
@@ -396,8 +392,8 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -396,8 +392,8 @@ public enum ImMessageTypeEnum implements ArrayValuable&lt;Integer&gt; {
396 * @param type 消息类型 392 * @param type 消息类型
397 * @return 枚举实例 393 * @return 枚举实例
398 */ 394 */
399 - public static ImMessageTypeEnum validate(Integer type) {  
400 - ImMessageTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values()); 395 + public static ImContentTypeEnum validate(Integer type) {
  396 + ImContentTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values());
401 Assert.notNull(result, "未注册的消息类型 type={}", type); 397 Assert.notNull(result, "未注册的消息类型 type={}", type);
402 return result; 398 return result;
403 } 399 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java
@@ -16,6 +16,7 @@ import java.util.Objects; @@ -16,6 +16,7 @@ import java.util.Objects;
16 @Getter 16 @Getter
17 public enum ImConversationTypeEnum implements ArrayValuable<Integer> { 17 public enum ImConversationTypeEnum implements ArrayValuable<Integer> {
18 18
  19 + NONE(0, "无会话"), // 无会话
19 PRIVATE(1, "私聊"), // 私聊 20 PRIVATE(1, "私聊"), // 私聊
20 GROUP(2, "群聊"), // 群聊 21 GROUP(2, "群聊"), // 群聊
21 CHANNEL(3, "频道"); // 频道 / 公众号 22 CHANNEL(3, "频道"); // 频道 / 公众号
@@ -36,6 +37,10 @@ public enum ImConversationTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -36,6 +37,10 @@ public enum ImConversationTypeEnum implements ArrayValuable&lt;Integer&gt; {
36 return ARRAYS; 37 return ARRAYS;
37 } 38 }
38 39
  40 + public static boolean isNone(Integer type) {
  41 + return Objects.equals(NONE.type, type);
  42 + }
  43 +
39 public static boolean isPrivate(Integer type) { 44 public static boolean isPrivate(Integer type) {
40 return Objects.equals(PRIVATE.type, type); 45 return Objects.equals(PRIVATE.type, type);
41 } 46 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImGroupMessageReceiptStatusEnum.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java
@@ -7,19 +7,19 @@ import lombok.Getter; @@ -7,19 +7,19 @@ import lombok.Getter;
7 import java.util.Arrays; 7 import java.util.Arrays;
8 8
9 /** 9 /**
10 - * IM 群消息回执状态枚举 10 + * IM 消息回执状态枚举
11 * 11 *
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Getter 14 @Getter
15 @AllArgsConstructor 15 @AllArgsConstructor
16 -public enum ImGroupMessageReceiptStatusEnum implements ArrayValuable<Integer> { 16 +public enum ImMessageReceiptStatusEnum implements ArrayValuable<Integer> {
17 17
18 NO_RECEIPT(0, "不需要回执"), 18 NO_RECEIPT(0, "不需要回执"),
19 PENDING(1, "待完成"), 19 PENDING(1, "待完成"),
20 DONE(2, "已完成"); 20 DONE(2, "已完成");
21 21
22 - public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupMessageReceiptStatusEnum::getStatus).toArray(Integer[]::new); 22 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageReceiptStatusEnum::getStatus).toArray(Integer[]::new);
23 23
24 /** 24 /**
25 * 状态 25 * 状态
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java
@@ -8,9 +8,6 @@ import java.util.Arrays; @@ -8,9 +8,6 @@ import java.util.Arrays;
8 8
9 /** 9 /**
10 * IM 消息状态枚举 10 * IM 消息状态枚举
11 - * <p>  
12 - * 私聊:SENDING(-1, 仅客户端) / UNREAD(0) / RECALL(2) / READ(3)  
13 - * 群聊:SENDING(-1, 仅客户端) / UNREAD(0, 作为正常状态) / RECALL(2)  
14 * 11 *
15 * @author 芋道源码 12 * @author 芋道源码
16 */ 13 */
@@ -19,9 +16,8 @@ import java.util.Arrays; @@ -19,9 +16,8 @@ import java.util.Arrays;
19 public enum ImMessageStatusEnum implements ArrayValuable<Integer> { 16 public enum ImMessageStatusEnum implements ArrayValuable<Integer> {
20 17
21 SENDING(-1, "发送中"), // 仅客户端使用 18 SENDING(-1, "发送中"), // 仅客户端使用
22 - UNREAD(0, "未读"), // 私聊=未读,群聊=正常(初始状态)  
23 - RECALL(2, "已撤回"),  
24 - READ(3, "已读"); // 仅私聊使用;群聊已读通过 Redis 已读位置实现 19 + NORMAL(0, "正常"), // 私聊 / 群聊的正常(初始)状态
  20 + RECALL(2, "已撤回");
25 21
26 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageStatusEnum::getStatus).toArray(Integer[]::new); 22 public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageStatusEnum::getStatus).toArray(Integer[]::new);
27 23
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java
@@ -24,7 +24,6 @@ import java.util.Objects; @@ -24,7 +24,6 @@ import java.util.Objects;
24 @Getter 24 @Getter
25 public enum ImRtcParticipantStatusEnum implements ArrayValuable<Integer> { 25 public enum ImRtcParticipantStatusEnum implements ArrayValuable<Integer> {
26 26
27 - // TODO @芋艿:hand up 要不要也搞下。  
28 INVITING(10, "邀请中"), // 已发出 invite,等被叫响应 27 INVITING(10, "邀请中"), // 已发出 invite,等被叫响应
29 JOINED(20, "已加入"), // 已 connect 进 LiveKit 房间 28 JOINED(20, "已加入"), // 已 connect 进 LiveKit 房间
30 REJECTED(30, "已拒绝"), // 接通前点拒接 29 REJECTED(30, "已拒绝"), // 接通前点拒接
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.im.mq.consumer.friend; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.im.mq.consumer.friend;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
5 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 5 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  6 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
6 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 7 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
7 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 8 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
8 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
9 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendInfoUpdatedNotification; 9 +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendInfoUpdatedNotification;
10 import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; 10 import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage;
11 import jakarta.annotation.Resource; 11 import jakarta.annotation.Resource;
12 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
@@ -52,8 +52,9 @@ public class AdminUserProfileUpdateConsumer { @@ -52,8 +52,9 @@ public class AdminUserProfileUpdateConsumer {
52 try { 52 try {
53 FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification() 53 FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification()
54 .setOperatorUserId(userId).setFriendUserId(userId); 54 .setOperatorUserId(userId).setFriendUserId(userId);
55 - websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification(  
56 - ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload)); 55 + websocketService.sendNotificationAsync(friend.getFriendUserId(),
  56 + ImConversationTypeEnum.NONE.getType(),
  57 + ImContentTypeEnum.FRIEND_INFO_UPDATED.getType(), payload);
57 successCount++; 58 successCount++;
58 } catch (Exception e) { 59 } catch (Exception e) {
59 log.warn("[onMessage][userId({}) friendUserId({}) 推送失败]", 60 log.warn("[onMessage][userId({}) friendUserId({}) 推送失败]",
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.conversation;
  2 +
  3 +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
  4 +
  5 +import java.util.Collection;
  6 +import java.util.List;
  7 +import java.util.Map;
  8 +
  9 +/**
  10 + * IM 会话读位置 Service 接口
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public interface ImConversationReadService {
  15 +
  16 + /**
  17 + * 更新用户在某会话的最大已读位置(单调递增,乱序 / 并发上报不会回退)
  18 + *
  19 + * @param userId 用户编号
  20 + * @param conversationType 会话类型
  21 + * @param conversationId 会话编号
  22 + * @param readMessageId 已读到的最大消息编号
  23 + * @return 读位置是否前进;true 时调用方才需要下发已读 / 回执事件
  24 + */
  25 + boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId);
  26 +
  27 + /**
  28 + * 获取用户在某会话的最大已读位置
  29 + *
  30 + * @param userId 用户编号
  31 + * @param conversationType 会话类型
  32 + * @param conversationId 会话编号
  33 + * @return 最大已读消息编号;不存在则返回 null
  34 + */
  35 + Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId);
  36 +
  37 + /**
  38 + * 获取某会话内所有用户的读位置(用于群回执人数聚合)
  39 + *
  40 + * @param conversationType 会话类型
  41 + * @param conversationId 会话编号
  42 + * @return userId → 最大已读消息编号
  43 + */
  44 + Map<Long, Long> getUserReadMessageIdMap(Integer conversationType, Long conversationId);
  45 +
  46 + /**
  47 + * 批量获取某用户在多个会话的读位置(用于频道批量读位置、重连后按活跃会话补偿)
  48 + *
  49 + * @param userId 用户编号
  50 + * @param conversationType 会话类型
  51 + * @param conversationIds 会话编号集合
  52 + * @return conversationId → 最大已读消息编号
  53 + */
  54 + Map<Long, Long> getConversationReadMessageIdMap(Long userId, Integer conversationType, Collection<Long> conversationIds);
  55 +
  56 + /**
  57 + * 增量拉取当前用户的会话读位置(重连 / 离线补偿:按 update_time + id 游标)
  58 + *
  59 + * @param userId 用户编号
  60 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null
  61 + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null
  62 + * @param limit 单次拉取条数
  63 + * @return 会话读位置列表,按更新时间、id 正序
  64 + */
  65 + List<ImConversationReadDO> pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit);
  66 +
  67 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.conversation;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
  5 +import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationReadMapper;
  6 +import jakarta.annotation.Resource;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +import org.springframework.dao.DuplicateKeyException;
  9 +import org.springframework.stereotype.Service;
  10 +
  11 +import java.time.LocalDateTime;
  12 +import java.util.Collection;
  13 +import java.util.Collections;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  18 +
  19 +/**
  20 + * IM 会话读位置 Service 实现类
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +@Service
  25 +@Slf4j
  26 +public class ImConversationReadServiceImpl implements ImConversationReadService {
  27 +
  28 + @Resource
  29 + private ImConversationReadMapper conversationReadMapper;
  30 +
  31 + @Override
  32 + public boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId) {
  33 + LocalDateTime now = LocalDateTime.now();
  34 + // 1. 不存在则插入;并发下唯一键冲突,降级为回查 + CAS 更新
  35 + ImConversationReadDO existing = conversationReadMapper.selectByUserIdAndConversation(
  36 + userId, conversationType, conversationId);
  37 + if (existing == null) {
  38 + try {
  39 + conversationReadMapper.insert(new ImConversationReadDO().setUserId(userId)
  40 + .setConversationType(conversationType).setTargetId(conversationId)
  41 + .setMessageId(readMessageId).setReadTime(now));
  42 + return true;
  43 + } catch (DuplicateKeyException e) {
  44 + log.warn("[updateConversationReadPosition][userId({}) type({}) conversationId({}) 并发插入冲突,回查更新]",
  45 + userId, conversationType, conversationId);
  46 + existing = conversationReadMapper.selectByUserIdAndConversation(userId, conversationType, conversationId);
  47 + }
  48 + }
  49 + if (existing == null) {
  50 + return false;
  51 + }
  52 +
  53 + // 2. CAS 单调更新:mapper 内 WHERE message_id < ? 保证乱序 / 并发不回退;影响行数 > 0 即读位置前进
  54 + return conversationReadMapper.updateReadMessageIdToLarger(existing.getId(), readMessageId, now) > 0;
  55 + }
  56 +
  57 + @Override
  58 + public Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId) {
  59 + ImConversationReadDO read = conversationReadMapper.selectByUserIdAndConversation(
  60 + userId, conversationType, conversationId);
  61 + return read != null ? read.getMessageId() : null;
  62 + }
  63 +
  64 + @Override
  65 + public Map<Long, Long> getUserReadMessageIdMap(Integer conversationType, Long conversationId) {
  66 + return convertMap(conversationReadMapper.selectListByConversation(conversationType, conversationId),
  67 + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId);
  68 + }
  69 +
  70 + @Override
  71 + public Map<Long, Long> getConversationReadMessageIdMap(Long userId, Integer conversationType,
  72 + Collection<Long> conversationIds) {
  73 + if (CollUtil.isEmpty(conversationIds)) {
  74 + return Collections.emptyMap();
  75 + }
  76 + List<ImConversationReadDO> list = conversationReadMapper.selectListByUserIdAndConversations(
  77 + userId, conversationType, conversationIds);
  78 + return convertMap(list, ImConversationReadDO::getTargetId, ImConversationReadDO::getMessageId);
  79 + }
  80 +
  81 + @Override
  82 + public List<ImConversationReadDO> pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  83 + return conversationReadMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit);
  84 + }
  85 +
  86 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java
@@ -51,6 +51,17 @@ public interface ImFriendRequestService { @@ -51,6 +51,17 @@ public interface ImFriendRequestService {
51 List<ImFriendRequestDO> getMyFriendRequestList(Long userId, Long maxId, Integer limit); 51 List<ImFriendRequestDO> getMyFriendRequestList(Long userId, Long maxId, Integer limit);
52 52
53 /** 53 /**
  54 + * 增量拉取「我相关」的好友申请(重连 / 离线补偿:双向 OR,按 update_time + id 游标)
  55 + *
  56 + * @param userId 用户编号
  57 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null
  58 + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null
  59 + * @param limit 单次拉取条数
  60 + * @return 申请记录列表,按更新时间、id 正序
  61 + */
  62 + List<ImFriendRequestDO> pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit);
  63 +
  64 + /**
54 * 按 id 单查申请记录;通用读接口,调用方自行做越权过滤 65 * 按 id 单查申请记录;通用读接口,调用方自行做越权过滤
55 */ 66 */
56 ImFriendRequestDO getFriendRequest(Long id); 67 ImFriendRequestDO getFriendRequest(Long id);
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java
@@ -14,13 +14,13 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; @@ -14,13 +14,13 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO;
14 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper; 14 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper;
15 import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; 15 import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum;
16 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; 16 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
17 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 17 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  18 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
18 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 19 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 20 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
20 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
21 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestApprovedNotification;  
22 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestNotification;  
23 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestRejectedNotification; 21 +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestApprovedNotification;
  22 +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestNotification;
  23 +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestRejectedNotification;
24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
26 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
@@ -105,8 +105,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { @@ -105,8 +105,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
105 if (fromUser != null) { 105 if (fromUser != null) {
106 payload.setFromNickname(fromUser.getNickname()).setFromAvatar(fromUser.getAvatar()); 106 payload.setFromNickname(fromUser.getNickname()).setFromAvatar(fromUser.getAvatar());
107 } 107 }
108 - websocketService.sendPrivateMessageAsync(toUserId, ImPrivateMessageDTO.ofFriendNotification(  
109 - ImMessageTypeEnum.FRIEND_REQUEST_RECEIVED.getType(), fromUserId, toUserId, payload)); 108 + websocketService.sendNotificationAsync(toUserId, ImConversationTypeEnum.NONE.getType(),
  109 + ImContentTypeEnum.FRIEND_REQUEST_RECEIVED.getType(), payload);
110 110
111 // 4. 全局自动通过开关:注册 afterCommit 回调,事务提交后再走同意流程 111 // 4. 全局自动通过开关:注册 afterCommit 回调,事务提交后再走同意流程
112 // 回调内 try/catch 兜底 —— afterCommit 异常会被 Spring 静默吞掉,否则同意失败时申请方永远等不到 APPROVED 112 // 回调内 try/catch 兜底 —— afterCommit 异常会被 Spring 静默吞掉,否则同意失败时申请方永远等不到 APPROVED
@@ -172,8 +172,6 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { @@ -172,8 +172,6 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
172 public void agreeFriendRequest(Long userId, Long requestId) { 172 public void agreeFriendRequest(Long userId, Long requestId) {
173 // 1.1 校验申请存在、未处理、操作人是接收方 173 // 1.1 校验申请存在、未处理、操作人是接收方
174 ImFriendRequestDO request = validateRequestForHandle(userId, requestId); 174 ImFriendRequestDO request = validateRequestForHandle(userId, requestId);
175 - // 1.2 复验双方用户有效  
176 - adminUserApi.validateUserList(ListUtil.of(request.getFromUserId(), request.getToUserId()));  
177 175
178 // 2. 乐观锁更新申请处理结果 176 // 2. 乐观锁更新申请处理结果
179 ImFriendRequestDO updateObj = new ImFriendRequestDO() 177 ImFriendRequestDO updateObj = new ImFriendRequestDO()
@@ -192,8 +190,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { @@ -192,8 +190,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
192 FriendRequestApprovedNotification payload = (FriendRequestApprovedNotification) 190 FriendRequestApprovedNotification payload = (FriendRequestApprovedNotification)
193 new FriendRequestApprovedNotification().setRequestId(request.getId()) 191 new FriendRequestApprovedNotification().setRequestId(request.getId())
194 .setOperatorUserId(userId).setFriendUserId(userId); 192 .setOperatorUserId(userId).setFriendUserId(userId);
195 - websocketService.sendPrivateMessageAsync(request.getFromUserId(), ImPrivateMessageDTO.ofFriendNotification(  
196 - ImMessageTypeEnum.FRIEND_REQUEST_APPROVED.getType(), userId, request.getFromUserId(), payload)); 193 + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(),
  194 + ImContentTypeEnum.FRIEND_REQUEST_APPROVED.getType(), payload);
197 } 195 }
198 196
199 @Override 197 @Override
@@ -217,8 +215,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { @@ -217,8 +215,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
217 new FriendRequestRejectedNotification().setRequestId(request.getId()) 215 new FriendRequestRejectedNotification().setRequestId(request.getId())
218 .setHandleContent(handleContent) 216 .setHandleContent(handleContent)
219 .setOperatorUserId(userId).setFriendUserId(userId); 217 .setOperatorUserId(userId).setFriendUserId(userId);
220 - websocketService.sendPrivateMessageAsync(request.getFromUserId(), ImPrivateMessageDTO.ofFriendNotification(  
221 - ImMessageTypeEnum.FRIEND_REQUEST_REJECTED.getType(), userId, request.getFromUserId(), payload)); 218 + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(),
  219 + ImContentTypeEnum.FRIEND_REQUEST_REJECTED.getType(), payload);
222 } 220 }
223 221
224 @Override 222 @Override
@@ -234,6 +232,11 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { @@ -234,6 +232,11 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService {
234 } 232 }
235 233
236 @Override 234 @Override
  235 + public List<ImFriendRequestDO> pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  236 + return friendRequestMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit);
  237 + }
  238 +
  239 + @Override
237 public ImFriendRequestDO getFriendRequest(Long id) { 240 public ImFriendRequestDO getFriendRequest(Long id) {
238 return friendRequestMapper.selectById(id); 241 return friendRequestMapper.selectById(id);
239 } 242 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
@@ -43,6 +43,11 @@ public interface ImFriendService { @@ -43,6 +43,11 @@ public interface ImFriendService {
43 List<ImFriendDO> getFriendList(Long userId); 43 List<ImFriendDO> getFriendList(Long userId);
44 44
45 /** 45 /**
  46 + * 增量拉取当前用户的好友关系(重连 / 离线补偿:含已删除,按 update_time + id 游标)
  47 + */
  48 + List<ImFriendDO> pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit);
  49 +
  50 + /**
46 * 获得当前用户的有效好友列表(仅 ENABLE 状态) 51 * 获得当前用户的有效好友列表(仅 ENABLE 状态)
47 */ 52 */
48 List<ImFriendDO> getEnableFriendList(Long userId); 53 List<ImFriendDO> getEnableFriendList(Long userId);
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -11,12 +11,12 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; @@ -11,12 +11,12 @@ import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
11 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; 11 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO;
12 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; 12 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
13 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; 13 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
14 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 14 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  15 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
15 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; 16 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
16 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 17 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
17 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 18 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
18 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
19 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.*; 19 +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.*;
20 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 import org.springframework.cache.annotation.CacheEvict; 22 import org.springframework.cache.annotation.CacheEvict;
@@ -99,6 +99,11 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -99,6 +99,11 @@ public class ImFriendServiceImpl implements ImFriendService {
99 } 99 }
100 100
101 @Override 101 @Override
  102 + public List<ImFriendDO> pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  103 + return friendMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit);
  104 + }
  105 +
  106 + @Override
102 public List<ImFriendDO> getEnableFriendList(Long userId) { 107 public List<ImFriendDO> getEnableFriendList(Long userId) {
103 return friendMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); 108 return friendMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus());
104 } 109 }
@@ -155,8 +160,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -155,8 +160,8 @@ public class ImFriendServiceImpl implements ImFriendService {
155 FriendUpdateNotification payload = (FriendUpdateNotification) new FriendUpdateNotification() 160 FriendUpdateNotification payload = (FriendUpdateNotification) new FriendUpdateNotification()
156 .setDisplayName(reqVO.getDisplayName()).setSilent(reqVO.getSilent()).setPinned(reqVO.getPinned()) 161 .setDisplayName(reqVO.getDisplayName()).setSilent(reqVO.getSilent()).setPinned(reqVO.getPinned())
157 .setOperatorUserId(userId).setFriendUserId(reqVO.getFriendUserId()); 162 .setOperatorUserId(userId).setFriendUserId(reqVO.getFriendUserId());
158 - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification(  
159 - ImMessageTypeEnum.FRIEND_UPDATE.getType(), userId, userId, payload)); 163 + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(),
  164 + ImContentTypeEnum.FRIEND_UPDATE.getType(), payload);
160 } 165 }
161 166
162 @Override 167 @Override
@@ -178,7 +183,7 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -178,7 +183,7 @@ public class ImFriendServiceImpl implements ImFriendService {
178 FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() 183 FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification()
179 .setOperatorUserId(fromUserId).setFriendUserId(toUserId); 184 .setOperatorUserId(fromUserId).setFriendUserId(toUserId);
180 privateMessageService.sendPrivateMessage(fromUserId, new ImPrivateMessageSendDTO() 185 privateMessageService.sendPrivateMessage(fromUserId, new ImPrivateMessageSendDTO()
181 - .setReceiverId(toUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()).setContent(payload)); 186 + .setReceiverId(toUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType()).setContent(payload));
182 } 187 }
183 188
184 @Override 189 @Override
@@ -197,7 +202,7 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -197,7 +202,7 @@ public class ImFriendServiceImpl implements ImFriendService {
197 FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() 202 FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification()
198 .setOperatorUserId(friendUserId).setFriendUserId(friendUserId); 203 .setOperatorUserId(friendUserId).setFriendUserId(friendUserId);
199 privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() 204 privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO()
200 - .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()) 205 + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType())
201 .setContent(payload).setPersistent(false)); 206 .setContent(payload).setPersistent(false));
202 } 207 }
203 208
@@ -219,7 +224,7 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -219,7 +224,7 @@ public class ImFriendServiceImpl implements ImFriendService {
219 FriendDeleteNotification payload = ((FriendDeleteNotification) new FriendDeleteNotification() 224 FriendDeleteNotification payload = ((FriendDeleteNotification) new FriendDeleteNotification()
220 .setOperatorUserId(userId).setFriendUserId(friendUserId)).setClear(clear); 225 .setOperatorUserId(userId).setFriendUserId(friendUserId)).setClear(clear);
221 privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() 226 privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO()
222 - .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_DELETE.getType()) 227 + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_DELETE.getType())
223 .setContent(payload).setPersistent(false)); 228 .setContent(payload).setPersistent(false));
224 } 229 }
225 230
@@ -245,8 +250,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -245,8 +250,8 @@ public class ImFriendServiceImpl implements ImFriendService {
245 // 3. 推 FRIEND_BLOCK 给 A 多端 250 // 3. 推 FRIEND_BLOCK 给 A 多端
246 FriendBlockNotification payload = (FriendBlockNotification) new FriendBlockNotification() 251 FriendBlockNotification payload = (FriendBlockNotification) new FriendBlockNotification()
247 .setOperatorUserId(userId).setFriendUserId(friendUserId); 252 .setOperatorUserId(userId).setFriendUserId(friendUserId);
248 - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification(  
249 - ImMessageTypeEnum.FRIEND_BLOCK.getType(), userId, userId, payload)); 253 + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(),
  254 + ImContentTypeEnum.FRIEND_BLOCK.getType(), payload);
250 } 255 }
251 256
252 @Override 257 @Override
@@ -271,8 +276,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -271,8 +276,8 @@ public class ImFriendServiceImpl implements ImFriendService {
271 // 3. 推 FRIEND_UNBLOCK 给 A 多端 276 // 3. 推 FRIEND_UNBLOCK 给 A 多端
272 FriendUnblockNotification payload = (FriendUnblockNotification) new FriendUnblockNotification() 277 FriendUnblockNotification payload = (FriendUnblockNotification) new FriendUnblockNotification()
273 .setOperatorUserId(userId).setFriendUserId(friendUserId); 278 .setOperatorUserId(userId).setFriendUserId(friendUserId);
274 - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification(  
275 - ImMessageTypeEnum.FRIEND_UNBLOCK.getType(), userId, userId, payload)); 279 + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(),
  280 + ImContentTypeEnum.FRIEND_UNBLOCK.getType(), payload);
276 } 281 }
277 282
278 /** 283 /**
@@ -294,7 +299,8 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -294,7 +299,8 @@ public class ImFriendServiceImpl implements ImFriendService {
294 } 299 }
295 // 情况二:复用 DISABLE 旧记录 → 恢复 ENABLE + 重置 silent / pinned / blocked,对齐"重新加好友"语义 300 // 情况二:复用 DISABLE 旧记录 → 恢复 ENABLE + 重置 silent / pinned / blocked,对齐"重新加好友"语义
296 if (exists != null) { 301 if (exists != null) {
297 - friendMapper.updateReAddFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now(), 302 + LocalDateTime now = LocalDateTime.now();
  303 + friendMapper.updateReAddFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), now, now,
298 false, false, false, displayName, addSource); 304 false, false, false, displayName, addSource);
299 return; 305 return;
300 } 306 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
@@ -87,13 +87,12 @@ public interface ImGroupMemberService { @@ -87,13 +87,12 @@ public interface ImGroupMemberService {
87 List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId); 87 List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId);
88 88
89 /** 89 /**
90 - * 查询用户已退群的群成员记录(DISABLE 状态) 90 + * 查询用户曾经加入的所有群成员记录(含已退群)
91 * 91 *
92 - * @param userId 用户编号,必传  
93 - * @param minQuitTime 最早退群时间(含),可空  
94 - * @return 已退群成员记录列表 92 + * @param userId 用户编号
  93 + * @return 群成员记录列表
95 */ 94 */
96 - List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime); 95 + List<ImGroupMemberDO> getGroupMemberListByUserId(Long userId);
97 96
98 /** 97 /**
99 * 添加群成员(入群),角色默认 MEMBER 98 * 添加群成员(入群),角色默认 MEMBER
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
@@ -77,7 +77,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -77,7 +77,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
77 77
78 @Override 78 @Override
79 public List<ImGroupMemberDO> getGroupMemberListByOwnerAndAdmin(Long groupId) { 79 public List<ImGroupMemberDO> getGroupMemberListByOwnerAndAdmin(Long groupId) {
80 - // TODO DONE @AI:把条件往下传;这样减少加载数据量!  
81 return groupMemberMapper.selectListByGroupIdAndStatusAndRoles(groupId, CommonStatusEnum.ENABLE.getStatus(), 80 return groupMemberMapper.selectListByGroupIdAndStatusAndRoles(groupId, CommonStatusEnum.ENABLE.getStatus(),
82 ListUtil.of(ImGroupMemberRoleEnum.OWNER.getRole(), ImGroupMemberRoleEnum.ADMIN.getRole())); 81 ListUtil.of(ImGroupMemberRoleEnum.OWNER.getRole(), ImGroupMemberRoleEnum.ADMIN.getRole()));
83 } 82 }
@@ -106,8 +105,8 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -106,8 +105,8 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
106 } 105 }
107 106
108 @Override 107 @Override
109 - public List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) {  
110 - return groupMemberMapper.selectQuitListByUserId(userId, minQuitTime); 108 + public List<ImGroupMemberDO> getGroupMemberListByUserId(Long userId) {
  109 + return groupMemberMapper.selectListByUserId(userId);
111 } 110 }
112 111
113 @Override 112 @Override
@@ -259,7 +258,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -259,7 +258,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
259 .setId(member.getId()); 258 .setId(member.getId());
260 groupMemberMapper.updateById(updateObj); 259 groupMemberMapper.updateById(updateObj);
261 260
262 - // 3.1 displayUserName 是公开字段,单独走 GROUP_MEMBER_NICKNAME_UPDATE 广播给全员;空串视为「清空昵称」也要广播;与旧值相同跳过 261 + // 3.1 displayUserName 是公开字段,单独走 GROUP_MEMBER_NICKNAME_UPDATE 在线同步给全员;空串视为「清空昵称」也要同步;与旧值相同跳过
263 if (updateReqVO.getDisplayUserName() != null 262 if (updateReqVO.getDisplayUserName() != null
264 && ObjUtil.notEqual(updateReqVO.getDisplayUserName(), member.getDisplayUserName())) { 263 && ObjUtil.notEqual(updateReqVO.getDisplayUserName(), member.getDisplayUserName())) {
265 groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate( 264 groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate(
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java
@@ -64,6 +64,19 @@ public interface ImGroupRequestService { @@ -64,6 +64,19 @@ public interface ImGroupRequestService {
64 List<ImGroupRequestDO> getUnhandledRequestListByOwnerOrAdmin(Long userId); 64 List<ImGroupRequestDO> getUnhandledRequestListByOwnerOrAdmin(Long userId);
65 65
66 /** 66 /**
  67 + * 增量拉取「我管理的群」下的加群申请(重连 / 离线补偿:含已处理,按 update_time + id 游标)
  68 + * <p>
  69 + * 作用域与 {@link #getUnhandledRequestListByOwnerOrAdmin} 一致:按 ImGroupMember.role 取我作为 OWNER / ADMIN 的群
  70 + *
  71 + * @param userId 当前用户编号
  72 + * @param lastUpdateTime 游标:上次拉取的最后一条更新时间戳(毫秒)
  73 + * @param lastId 游标:上次拉取的最后一条申请编号
  74 + * @param limit 单次拉取条数
  75 + * @return 申请记录列表
  76 + */
  77 + List<ImGroupRequestDO> pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit);
  78 +
  79 + /**
67 * 拉取指定群下的全部加群申请(含已处理);仅群主 / 管理员可查 80 * 拉取指定群下的全部加群申请(含已处理);仅群主 / 管理员可查
68 * <p> 81 * <p>
69 * 用于群「进群申请」子页:最新一条卡片化突出 + 历史申请按 id 倒序 82 * 用于群「进群申请」子页:最新一条卡片化突出 + 历史申请按 id 倒序
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java
@@ -13,15 +13,15 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; @@ -13,15 +13,15 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper;
13 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; 13 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
14 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 14 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
15 import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; 15 import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum;
16 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 16 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  17 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
17 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; 18 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
18 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 19 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 20 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
20 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
21 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.BaseGroupNotification;  
22 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestApprovedNotification;  
23 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestReceivedNotification;  
24 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestRejectedNotification; 21 +import cn.iocoder.yudao.module.im.service.websocket.notification.group.BaseGroupNotification;
  22 +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestApprovedNotification;
  23 +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestReceivedNotification;
  24 +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestRejectedNotification;
25 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 25 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
27 import jakarta.annotation.Resource; 27 import jakarta.annotation.Resource;
@@ -97,12 +97,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -97,12 +97,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
97 // 3. 情况二:群开启了审批,创建或复用一条主动申请记录 97 // 3. 情况二:群开启了审批,创建或复用一条主动申请记录
98 ImGroupRequestDO request = createOrResetApplyRequest(groupId, userId, reqVO); 98 ImGroupRequestDO request = createOrResetApplyRequest(groupId, userId, reqVO);
99 99
100 - // 4. 1503 私聊定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像 100 + // 4. 1503 定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像
101 AdminUserRespDTO applyUser = adminUserApi.getUser(userId); 101 AdminUserRespDTO applyUser = adminUserApi.getUser(userId);
102 GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); 102 GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser);
103 for (Long receiverUserId : getGroupMemberListByOwnerAndAdminUserIds(group)) { 103 for (Long receiverUserId : getGroupMemberListByOwnerAndAdminUserIds(group)) {
104 - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification(  
105 - ImMessageTypeEnum.GROUP_REQUEST_RECEIVED.getType(), userId, receiverUserId, payload)); 104 + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(),
  105 + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload);
106 } 106 }
107 return request; 107 return request;
108 } 108 }
@@ -139,12 +139,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -139,12 +139,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
139 groupMemberService.addGroupMember(request.getGroupId(), request.getUserId(), 139 groupMemberService.addGroupMember(request.getGroupId(), request.getUserId(),
140 ImGroupMemberRoleEnum.NORMAL.getRole(), request.getAddSource(), request.getInviterUserId()); 140 ImGroupMemberRoleEnum.NORMAL.getRole(), request.getAddSource(), request.getInviterUserId());
141 141
142 - // 5.1 1505 私聊推送给申请人 + 群主 + 全部管理员(每端单推) 142 + // 5.1 1505 定向推送给申请人 + 群主 + 全部管理员(每端单推)
143 GroupRequestApprovedNotification payload = (GroupRequestApprovedNotification) new GroupRequestApprovedNotification() 143 GroupRequestApprovedNotification payload = (GroupRequestApprovedNotification) new GroupRequestApprovedNotification()
144 .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) 144 .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId())
145 .setOperatorUserId(userId); 145 .setOperatorUserId(userId);
146 broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, 146 broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload,
147 - ImMessageTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId); 147 + ImContentTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId);
148 // 5.2 群事件:主动申请 → 1510 自由进群;被邀请 → 1509 成员加入 148 // 5.2 群事件:主动申请 → 1510 自由进群;被邀请 → 1509 成员加入
149 if (request.getInviterUserId() == null) { 149 if (request.getInviterUserId() == null) {
150 groupMessageService.sendGroupMessage(userId, 150 groupMessageService.sendGroupMessage(userId,
@@ -174,12 +174,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -174,12 +174,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
174 throw exception(GROUP_REQUEST_HANDLED); 174 throw exception(GROUP_REQUEST_HANDLED);
175 } 175 }
176 176
177 - // 3. 1506 私聊推送给申请人 + 群主 + 全部管理员 177 + // 3. 1506 定向推送给申请人 + 群主 + 全部管理员
178 GroupRequestRejectedNotification payload = (GroupRequestRejectedNotification) new GroupRequestRejectedNotification() 178 GroupRequestRejectedNotification payload = (GroupRequestRejectedNotification) new GroupRequestRejectedNotification()
179 .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) 179 .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId())
180 .setHandleContent(handleContent).setOperatorUserId(userId); 180 .setHandleContent(handleContent).setOperatorUserId(userId);
181 broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, 181 broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload,
182 - ImMessageTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId); 182 + ImContentTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId);
183 } 183 }
184 184
185 @Override 185 @Override
@@ -201,8 +201,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -201,8 +201,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
201 AdminUserRespDTO applyUser = userMap.get(request.getUserId()); 201 AdminUserRespDTO applyUser = userMap.get(request.getUserId());
202 GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); 202 GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser);
203 for (Long receiverUserId : ownerAndAdmins) { 203 for (Long receiverUserId : ownerAndAdmins) {
204 - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification(  
205 - ImMessageTypeEnum.GROUP_REQUEST_RECEIVED.getType(), inviterUserId, receiverUserId, payload)); 204 + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(),
  205 + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload);
206 } 206 }
207 } 207 }
208 } 208 }
@@ -212,8 +212,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -212,8 +212,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
212 // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 212 // 1. 找出当前用户作为 OWNER / ADMIN 的所有群
213 List<ImGroupMemberDO> myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); 213 List<ImGroupMemberDO> myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId);
214 Set<Long> ownerOrAdminGroupIds = convertSet(myMembers, 214 Set<Long> ownerOrAdminGroupIds = convertSet(myMembers,
215 - ImGroupMemberDO::getGroupId,  
216 - m -> ImGroupMemberRoleEnum.isOwnerOrAdmin(m.getRole())); 215 + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()));
217 if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { 216 if (CollUtil.isEmpty(ownerOrAdminGroupIds)) {
218 return Collections.emptyList(); 217 return Collections.emptyList();
219 } 218 }
@@ -223,6 +222,19 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -223,6 +222,19 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
223 } 222 }
224 223
225 @Override 224 @Override
  225 + public List<ImGroupRequestDO> pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) {
  226 + // 1. 找出当前用户作为 OWNER / ADMIN 的所有群
  227 + List<ImGroupMemberDO> myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId);
  228 + Set<Long> ownerOrAdminGroupIds = convertSet(myMembers,
  229 + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole()));
  230 + if (CollUtil.isEmpty(ownerOrAdminGroupIds)) {
  231 + return Collections.emptyList();
  232 + }
  233 + // 2. 按游标增量拉取这些群下的申请
  234 + return groupRequestMapper.selectPullListByGroupIds(ownerOrAdminGroupIds, lastUpdateTime, lastId, limit);
  235 + }
  236 +
  237 + @Override
226 public List<ImGroupRequestDO> getGroupRequestListByGroupId(Long userId, Long groupId) { 238 public List<ImGroupRequestDO> getGroupRequestListByGroupId(Long userId, Long groupId) {
227 // 1. 校验群存在 + 当前用户是群主 / 管理员 239 // 1. 校验群存在 + 当前用户是群主 / 管理员
228 groupService.validateGroupExists(groupId); 240 groupService.validateGroupExists(groupId);
@@ -383,7 +395,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -383,7 +395,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
383 } 395 }
384 396
385 /** 397 /**
386 - * 1505 / 1506 受众:申请人 + 群主 + 全部管理员;每端单独推一帧,前端按 receiver 是否申请人区分文案 398 + * 1505 / 1506 受众:申请人 + 群主 + 全部管理员
387 */ 399 */
388 private void broadcastToOwnerAdminsAndApplicant(Long groupId, Long applicantUserId, BaseGroupNotification payload, 400 private void broadcastToOwnerAdminsAndApplicant(Long groupId, Long applicantUserId, BaseGroupNotification payload,
389 Integer messageType, Long operatorUserId) { 401 Integer messageType, Long operatorUserId) {
@@ -394,8 +406,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { @@ -394,8 +406,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService {
394 Set<Long> receivers = new LinkedHashSet<>(getGroupMemberListByOwnerAndAdminUserIds(group)); 406 Set<Long> receivers = new LinkedHashSet<>(getGroupMemberListByOwnerAndAdminUserIds(group));
395 receivers.add(applicantUserId); 407 receivers.add(applicantUserId);
396 for (Long receiverUserId : receivers) { 408 for (Long receiverUserId : receivers) {
397 - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification(  
398 - messageType, operatorUserId, receiverUserId, payload)); 409 + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(),
  410 + messageType, payload);
399 } 411 }
400 } 412 }
401 413
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
@@ -30,7 +30,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; @@ -30,7 +30,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
30 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; 30 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
31 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 31 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
32 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 32 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
33 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 33 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
34 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 34 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
35 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 35 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
36 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; 36 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
@@ -149,7 +149,9 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -149,7 +149,9 @@ public class ImGroupServiceImpl implements ImGroupService {
149 boolean nameChanged = StrUtil.isNotEmpty(updateReqVO.getName()); 149 boolean nameChanged = StrUtil.isNotEmpty(updateReqVO.getName());
150 boolean noticeChanged = updateReqVO.getNotice() != null; 150 boolean noticeChanged = updateReqVO.getNotice() != null;
151 boolean avatarChanged = StrUtil.isNotEmpty(updateReqVO.getAvatar()); 151 boolean avatarChanged = StrUtil.isNotEmpty(updateReqVO.getAvatar());
152 - if (nameChanged || noticeChanged || avatarChanged) { 152 + boolean joinApprovalChanged = updateReqVO.getJoinApproval() != null
  153 + && ObjUtil.notEqual(group.getJoinApproval(), updateReqVO.getJoinApproval());
  154 + if (nameChanged || noticeChanged || avatarChanged || joinApprovalChanged) {
153 List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId); 155 List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId);
154 if (nameChanged) { 156 if (nameChanged) {
155 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNameUpdate( 157 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNameUpdate(
@@ -161,7 +163,11 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -161,7 +163,11 @@ public class ImGroupServiceImpl implements ImGroupService {
161 } 163 }
162 if (avatarChanged) { 164 if (avatarChanged) {
163 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate( 165 groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate(
164 - groupId, userId, group.getAvatar(), updateReqVO.getAvatar())); 166 + groupId, userId, group.getAvatar(), updateReqVO.getAvatar(), null, null));
  167 + }
  168 + if (joinApprovalChanged) {
  169 + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate(
  170 + groupId, userId, null, null, group.getJoinApproval(), updateReqVO.getJoinApproval()));
165 } 171 }
166 } 172 }
167 173
@@ -193,8 +199,6 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -193,8 +199,6 @@ public class ImGroupServiceImpl implements ImGroupService {
193 .setStatus(CommonStatusEnum.DISABLE.getStatus()).setDissolvedTime(LocalDateTime.now())); 199 .setStatus(CommonStatusEnum.DISABLE.getStatus()).setDissolvedTime(LocalDateTime.now()));
194 // 2.2 移除全部群成员 200 // 2.2 移除全部群成员
195 groupMemberService.removeGroupMembersByGroupId(id); 201 groupMemberService.removeGroupMembersByGroupId(id);
196 - // 2.3 清理已读缓存  
197 - groupMessageService.deleteReadMaxMessageIdMap(id);  
198 } 202 }
199 203
200 // ==================== 群成员的写操作 ==================== 204 // ==================== 群成员的写操作 ====================
@@ -267,10 +271,8 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -267,10 +271,8 @@ public class ImGroupServiceImpl implements ImGroupService {
267 // 2. 先发广播,后移成员(见类 javadoc) 271 // 2. 先发广播,后移成员(见类 javadoc)
268 groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberQuit(groupId, userId)); 272 groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberQuit(groupId, userId));
269 273
270 - // 3.1 移除群成员 274 + // 3. 移除群成员
271 groupMemberService.removeGroupMember(groupId, userId); 275 groupMemberService.removeGroupMember(groupId, userId);
272 - // 3.2 清理已读缓存  
273 - groupMessageService.deleteReadMaxMessageId(groupId, userId);  
274 } 276 }
275 277
276 @Override 278 @Override
@@ -307,10 +309,8 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -307,10 +309,8 @@ public class ImGroupServiceImpl implements ImGroupService {
307 groupMessageService.sendGroupMessage(userId, 309 groupMessageService.sendGroupMessage(userId,
308 ImGroupMessageSendDTO.ofGroupMemberKick(groupId, userId, validTargetUserIds)); 310 ImGroupMessageSendDTO.ofGroupMemberKick(groupId, userId, validTargetUserIds));
309 311
310 - // 3.1 批量移除群成员 312 + // 3. 批量移除群成员;不清理读位置(保留退群前历史已读,供离线补偿)
311 groupMemberService.removeGroupMembers(groupId, validTargetUserIds); 313 groupMemberService.removeGroupMembers(groupId, validTargetUserIds);
312 - // 3.2 批量清理已读缓存  
313 - groupMessageService.deleteReadMaxMessageIds(groupId, validTargetUserIds);  
314 } 314 }
315 315
316 @Override 316 @Override
@@ -442,7 +442,7 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -442,7 +442,7 @@ public class ImGroupServiceImpl implements ImGroupService {
442 if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { 442 if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) {
443 throw exception(MESSAGE_NOT_IN_GROUP); 443 throw exception(MESSAGE_NOT_IN_GROUP);
444 } 444 }
445 - if (!ImMessageTypeEnum.validate(message.getType()).isNormal() 445 + if (!ImContentTypeEnum.validate(message.getType()).isNormal()
446 || ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { 446 || ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) {
447 throw exception(MESSAGE_NOT_IN_GROUP); 447 throw exception(MESSAGE_NOT_IN_GROUP);
448 } 448 }
@@ -601,11 +601,8 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -601,11 +601,8 @@ public class ImGroupServiceImpl implements ImGroupService {
601 601
602 @Override 602 @Override
603 public List<ImGroupDO> getMyGroupList(Long userId) { 603 public List<ImGroupDO> getMyGroupList(Long userId) {
604 - // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)  
605 - List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId);  
606 - // 1.2 再查最近 N 天(与群消息离线拉取窗口一致)内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存)  
607 - LocalDateTime minQuitTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays());  
608 - members.addAll(groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime)); 604 + // 1. 查用户曾经加入的所有群(含退群,前端按需过滤);退群前的离线消息也要能展示
  605 + List<ImGroupMemberDO> members = groupMemberService.getGroupMemberListByUserId(userId);
609 if (CollUtil.isEmpty(members)) { 606 if (CollUtil.isEmpty(members)) {
610 return Collections.emptyList(); 607 return Collections.emptyList();
611 } 608 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java
@@ -27,7 +27,7 @@ public interface ImChannelMessageService { @@ -27,7 +27,7 @@ public interface ImChannelMessageService {
27 * @param size 返回条数 27 * @param size 返回条数
28 * @return 频道消息列表;按 id 升序 28 * @return 频道消息列表;按 id 升序
29 */ 29 */
30 - List<ImChannelMessageDO> getMessageListForPull(Long userId, Long minId, Integer size); 30 + List<ImChannelMessageDO> pullChannelMessageList(Long userId, Long minId, Integer size);
31 31
32 /** 32 /**
33 * 上报频道消息已读位置;同步推 READ 事件给自己多端 33 * 上报频道消息已读位置;同步推 READ 事件给自己多端
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.message; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.message;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.extra.spring.SpringUtil; 6 import cn.hutool.extra.spring.SpringUtil;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -11,12 +12,14 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im @@ -11,12 +12,14 @@ import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.channel.Im
11 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; 12 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO;
12 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 13 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
13 import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; 14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper;
14 -import cn.iocoder.yudao.module.im.dal.redis.message.ImChannelMessageReadRedisDAO;  
15 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 15 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  16 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
16 import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; 17 import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
  18 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
17 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
18 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO;  
19 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.MaterialMessage; 20 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImChannelMessageNotification;
  21 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification;
  22 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.MaterialMessage;
20 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
21 import org.springframework.scheduling.annotation.Async; 24 import org.springframework.scheduling.annotation.Async;
22 import org.springframework.stereotype.Service; 25 import org.springframework.stereotype.Service;
@@ -25,7 +28,6 @@ import org.springframework.validation.annotation.Validated; @@ -25,7 +28,6 @@ import org.springframework.validation.annotation.Validated;
25 28
26 import java.time.LocalDateTime; 29 import java.time.LocalDateTime;
27 import java.util.Collection; 30 import java.util.Collection;
28 -import java.util.HashMap;  
29 import java.util.List; 31 import java.util.List;
30 import java.util.Map; 32 import java.util.Map;
31 33
@@ -50,39 +52,41 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { @@ -50,39 +52,41 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService {
50 private ImWebSocketService webSocketService; 52 private ImWebSocketService webSocketService;
51 53
52 @Resource 54 @Resource
53 - private ImChannelMessageReadRedisDAO channelMessageReadRedisDAO; 55 + private ImConversationReadService conversationReadService;
54 56
55 // ==================== 用户端 ==================== 57 // ==================== 用户端 ====================
56 58
57 @Override 59 @Override
58 - public List<ImChannelMessageDO> getMessageListForPull(Long userId, Long minId, Integer size) { 60 + public List<ImChannelMessageDO> pullChannelMessageList(Long userId, Long minId, Integer size) {
59 return channelMessageMapper.selectListByUserAndMinId(userId, minId, size); 61 return channelMessageMapper.selectListByUserAndMinId(userId, minId, size);
60 } 62 }
61 63
62 @Override 64 @Override
63 public Map<Long, Long> getChannelReadMaxMessageIdMap(Long userId, Collection<Long> channelIds) { 65 public Map<Long, Long> getChannelReadMaxMessageIdMap(Long userId, Collection<Long> channelIds) {
64 - Map<Long, Long> result = new HashMap<>(channelIds.size());  
65 - for (Long channelId : channelIds) {  
66 - Long max = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId);  
67 - if (max != null) {  
68 - result.put(channelId, max);  
69 - }  
70 - }  
71 - return result; 66 + return conversationReadService.getConversationReadMessageIdMap(
  67 + userId, ImConversationTypeEnum.CHANNEL.getType(), channelIds);
72 } 68 }
73 69
74 @Override 70 @Override
75 public void readChannelMessages(Long userId, Long channelId, Long messageId) { 71 public void readChannelMessages(Long userId, Long channelId, Long messageId) {
76 Assert.notNull(channelId, "频道编号不能为空"); 72 Assert.notNull(channelId, "频道编号不能为空");
77 Assert.notNull(messageId, "已读消息编号不能为空"); 73 Assert.notNull(messageId, "已读消息编号不能为空");
78 - // 1. 已读位置未前进,直接返回  
79 - Long prevMaxMessageId = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId);  
80 - if (prevMaxMessageId != null && prevMaxMessageId >= messageId) { 74 + // 1.1 校验消息真实存在且属于该频道,避免未来 / 伪造 messageId 污染读位置
  75 + ImChannelMessageDO message = channelMessageMapper.selectById(messageId);
  76 + if (message == null || ObjUtil.notEqual(message.getChannelId(), channelId)) {
  77 + return;
  78 + }
  79 + // 1.2 定向消息校验对当前用户可见(receiver_user_ids 为空表示全员可见)
  80 + if (CollUtil.isNotEmpty(message.getReceiverUserIds()) && !message.getReceiverUserIds().contains(userId)) {
81 return; 81 return;
82 } 82 }
83 83
84 - // 2. 更新 Redis 频道已读位置  
85 - channelMessageReadRedisDAO.updateReadMaxMessageId(channelId, userId, messageId); 84 + // 2. 更新频道已读位置;读位置未前进则不推
  85 + boolean advanced = conversationReadService.updateConversationReadPosition(
  86 + userId, ImConversationTypeEnum.CHANNEL.getType(), channelId, messageId);
  87 + if (!advanced) {
  88 + return;
  89 + }
86 90
87 // 3. 异步推 READ 事件给自己多端同步 91 // 3. 异步推 READ 事件给自己多端同步
88 getSelf().readChannelMessageEvent(userId, channelId, messageId); 92 getSelf().readChannelMessageEvent(userId, channelId, messageId);
@@ -93,7 +97,8 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { @@ -93,7 +97,8 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService {
93 */ 97 */
94 @Async 98 @Async
95 public void readChannelMessageEvent(Long userId, Long channelId, Long readId) { 99 public void readChannelMessageEvent(Long userId, Long channelId, Long readId) {
96 - webSocketService.sendChannelMessageAsync(userId, ImChannelMessageDTO.ofRead(channelId, readId)); 100 + webSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.CHANNEL.getType(),
  101 + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofChannel(channelId, readId));
97 } 102 }
98 103
99 private ImChannelMessageServiceImpl getSelf() { 104 private ImChannelMessageServiceImpl getSelf() {
@@ -113,15 +118,16 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { @@ -113,15 +118,16 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService {
113 String payloadJson = JsonUtils.toJsonString(payload); 118 String payloadJson = JsonUtils.toJsonString(payload);
114 // 2.2 落库 1 行 message;reqVO 同名字段(materialId / receiverUserIds)自动拷贝,剩余字段补 set 119 // 2.2 落库 1 行 message;reqVO 同名字段(materialId / receiverUserIds)自动拷贝,剩余字段补 set
115 ImChannelMessageDO message = BeanUtils.toBean(reqVO, ImChannelMessageDO.class).setChannelId(material.getChannelId()) 120 ImChannelMessageDO message = BeanUtils.toBean(reqVO, ImChannelMessageDO.class).setChannelId(material.getChannelId())
116 - .setType(ImMessageTypeEnum.MATERIAL.getType()).setContent(payloadJson).setSendTime(LocalDateTime.now()); 121 + .setType(ImContentTypeEnum.MATERIAL.getType()).setContent(payloadJson).setSendTime(LocalDateTime.now());
117 channelMessageMapper.insert(message); 122 channelMessageMapper.insert(message);
118 123
119 // 3. 异步推 WebSocket:指定用户走点对点;全员(receiverUserIds 为空)走广播 124 // 3. 异步推 WebSocket:指定用户走点对点;全员(receiverUserIds 为空)走广播
120 - ImChannelMessageDTO dto = ImChannelMessageDTO.ofSend(message); 125 + ImChannelMessageNotification dto = ImChannelMessageNotification.ofSend(message);
121 if (CollUtil.isNotEmpty(reqVO.getReceiverUserIds())) { 126 if (CollUtil.isNotEmpty(reqVO.getReceiverUserIds())) {
122 - webSocketService.sendChannelMessageAsync(reqVO.getReceiverUserIds(), dto); 127 + webSocketService.sendNotificationAsync(reqVO.getReceiverUserIds(), ImConversationTypeEnum.CHANNEL.getType(),
  128 + dto.getType(), dto);
123 } else { 129 } else {
124 - webSocketService.broadcastChannelMessageAsync(dto); 130 + webSocketService.broadcastNotificationAsync(ImConversationTypeEnum.CHANNEL.getType(), dto.getType(), dto);
125 } 131 }
126 return message.getId(); 132 return message.getId();
127 } 133 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
@@ -98,35 +98,6 @@ public interface ImGroupMessageService { @@ -98,35 +98,6 @@ public interface ImGroupMessageService {
98 */ 98 */
99 List<ImGroupMessageDO> getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO); 99 List<ImGroupMessageDO> getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO);
100 100
101 - /**  
102 - * 清理用户在某群的已读位置缓存  
103 - * <p>  
104 - * 用于成员退群场景  
105 - *  
106 - * @param groupId 群编号  
107 - * @param userId 用户编号  
108 - */  
109 - void deleteReadMaxMessageId(Long groupId, Long userId);  
110 -  
111 - /**  
112 - * 批量清理用户在某群的已读位置缓存  
113 - * <p>  
114 - * 用于批量踢出场景  
115 - *  
116 - * @param groupId 群编号  
117 - * @param userIds 用户编号集合  
118 - */  
119 - void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds);  
120 -  
121 - /**  
122 - * 清理某群所有用户的已读位置缓存  
123 - * <p>  
124 - * 用于群解散场景  
125 - *  
126 - * @param groupId 群编号  
127 - */  
128 - void deleteReadMaxMessageIdMap(Long groupId);  
129 -  
130 // ==================== 管理后台 ==================== 101 // ==================== 管理后台 ====================
131 102
132 /** 103 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
@@ -6,7 +6,6 @@ import cn.hutool.core.util.BooleanUtil; @@ -6,7 +6,6 @@ import cn.hutool.core.util.BooleanUtil;
6 import cn.hutool.core.util.IdUtil; 6 import cn.hutool.core.util.IdUtil;
7 import cn.hutool.core.util.ObjUtil; 7 import cn.hutool.core.util.ObjUtil;
8 import cn.hutool.extra.spring.SpringUtil; 8 import cn.hutool.extra.spring.SpringUtil;
9 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
10 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 10 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -17,23 +16,27 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; @@ -17,23 +16,27 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
17 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 16 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
18 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 17 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
19 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; 18 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
20 -import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO; 19 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
21 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 20 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
22 -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 21 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
23 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 22 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
24 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 23 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
25 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 24 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
  25 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
26 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; 26 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
27 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 27 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
28 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 28 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
29 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 29 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
30 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 30 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
31 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;  
32 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage;  
33 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; 31 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification;
  32 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification;
  33 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification;
  34 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage;
  35 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage;
34 import cn.iocoder.yudao.module.im.util.ImMessageUtils; 36 import cn.iocoder.yudao.module.im.util.ImMessageUtils;
35 import jakarta.annotation.Resource; 37 import jakarta.annotation.Resource;
36 import lombok.extern.slf4j.Slf4j; 38 import lombok.extern.slf4j.Slf4j;
  39 +import org.springframework.dao.DuplicateKeyException;
37 import org.springframework.scheduling.annotation.Async; 40 import org.springframework.scheduling.annotation.Async;
38 import org.springframework.stereotype.Service; 41 import org.springframework.stereotype.Service;
39 import org.springframework.transaction.annotation.Transactional; 42 import org.springframework.transaction.annotation.Transactional;
@@ -41,7 +44,6 @@ import org.springframework.validation.annotation.Validated; @@ -41,7 +44,6 @@ import org.springframework.validation.annotation.Validated;
41 44
42 import java.time.LocalDateTime; 45 import java.time.LocalDateTime;
43 import java.util.*; 46 import java.util.*;
44 -import java.util.stream.Collectors;  
45 47
46 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 48 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
47 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 49 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -57,17 +59,8 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @@ -57,17 +59,8 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
57 @Slf4j 59 @Slf4j
58 public class ImGroupMessageServiceImpl implements ImGroupMessageService { 60 public class ImGroupMessageServiceImpl implements ImGroupMessageService {
59 61
60 - /**  
61 - * 仅用于规避群消息 pull 的"假空页":  
62 - * 首批消息可能因入群时间或定向接收过滤后变成空列表,但后续更大的 id 仍然存在可见消息。  
63 - * 因此仅在过滤结果为空时,按本轮消息最大 id 向后再试几次。  
64 - */  
65 - private static final int PULL_GROUP_MESSAGE_EMPTY_RETRY_TIMES = 3;  
66 -  
67 @Resource 62 @Resource
68 private ImGroupMessageMapper groupMessageMapper; 63 private ImGroupMessageMapper groupMessageMapper;
69 - @Resource  
70 - private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO;  
71 64
72 @Resource 65 @Resource
73 private ImGroupService groupService; 66 private ImGroupService groupService;
@@ -75,6 +68,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -75,6 +68,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
75 private ImGroupMemberService groupMemberService; 68 private ImGroupMemberService groupMemberService;
76 @Resource 69 @Resource
77 private ImSensitiveWordService sensitiveWordService; 70 private ImSensitiveWordService sensitiveWordService;
  71 + @Resource
  72 + private ImConversationReadService conversationReadService;
78 73
79 @Resource 74 @Resource
80 private ImWebSocketService imWebSocketService; 75 private ImWebSocketService imWebSocketService;
@@ -100,34 +95,44 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -100,34 +95,44 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
100 // 1.4 禁言校验 95 // 1.4 禁言校验
101 validateMuteStatus(group, senderMember); 96 validateMuteStatus(group, senderMember);
102 // 1.5 文本消息敏感词过滤 97 // 1.5 文本消息敏感词过滤
103 - if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { 98 + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) {
104 sensitiveWordService.validateText(reqVO.getContent()); 99 sensitiveWordService.validateText(reqVO.getContent());
105 } 100 }
106 101
107 - // 2.1 引用 quote 消息规范化  
108 - reqVO.setContent(normalizeQuoteContent(reqVO, senderMember));  
109 - // 2.2 构建并保存消息 102 + // 2.1 固化发送当时可见成员快照:用户发送均为全员广播;getReceiverUserIds 兜底纳入发送者,钉死发送者必可见
  103 + List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(reqVO.getGroupId());
  104 + Set<Long> receiverUserIds = getReceiverUserIds(null, senderId, memberUserIds);
  105 + // 2.2 引用 quote 消息规范化(含可见性子集校验,防止定向消息内容被广播引用泄漏)
  106 + reqVO.setContent(normalizeQuoteContent(reqVO, senderMember, receiverUserIds));
  107 + // 2.3 构建并保存消息;唯一键冲突时回查已存在消息返回
110 ImGroupMessageDO message = BeanUtils.toBean(reqVO, ImGroupMessageDO.class, m -> m 108 ImGroupMessageDO message = BeanUtils.toBean(reqVO, ImGroupMessageDO.class, m -> m
111 - .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()) 109 + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now())
  110 + .setReceiverUserIds(new ArrayList<>(receiverUserIds))
112 .setReceiptStatus(resolveReceiptStatus(reqVO.getReceipt()))); 111 .setReceiptStatus(resolveReceiptStatus(reqVO.getReceipt())));
113 - groupMessageMapper.insert(message); 112 + try {
  113 + groupMessageMapper.insert(message);
  114 + } catch (DuplicateKeyException e) {
  115 + log.warn("[sendGroupMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]",
  116 + senderId, reqVO.getClientMessageId());
  117 + return groupMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId());
  118 + }
114 119
115 - // 3. WebSocket 异步推送(群内可见成员 + 发送方多端同步)  
116 - List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(message.getGroupId());  
117 - Set<Long> targetUserIds = getVisibleUserIds(message.getReceiverUserIds(), senderId, memberUserIds);  
118 - imWebSocketService.sendGroupMessageAsync(targetUserIds, ImGroupMessageDTO.ofSend(message)); 120 + // 3. WebSocket 异步推送(快照内可见成员 + 发送方多端同步)
  121 + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message);
  122 + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(),
  123 + notification.getType(), notification);
119 return message; 124 return message;
120 } 125 }
121 126
122 @Override 127 @Override
123 public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendDTO dto) { 128 public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendDTO dto) {
124 List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(dto.getGroupId()); 129 List<Long> memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(dto.getGroupId());
125 - Set<Long> targetUserIds = getVisibleUserIds(dto.getReceiverUserIds(), senderId, memberUserIds);  
126 - return sendGroupMessage(senderId, targetUserIds, dto); 130 + Set<Long> receiverUserIds = getReceiverUserIds(dto.getReceiverUserIds(), senderId, memberUserIds);
  131 + return sendGroupMessage(senderId, receiverUserIds, dto);
127 } 132 }
128 133
129 @Override 134 @Override
130 - public ImGroupMessageDO sendGroupMessage(Long senderId, Collection<Long> targetUserIds, ImGroupMessageSendDTO dto) { 135 + public ImGroupMessageDO sendGroupMessage(Long senderId, Collection<Long> receiverUserIds, ImGroupMessageSendDTO dto) {
131 // 1.1 content 序列化:null / String 透传,POJO 走 JSON 136 // 1.1 content 序列化:null / String 透传,POJO 走 JSON
132 Object payload = dto.getContent(); 137 Object payload = dto.getContent();
133 String contentString = payload == null || payload instanceof String 138 String contentString = payload == null || payload instanceof String
@@ -137,16 +142,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -137,16 +142,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
137 ImGroupMessageDO message = new ImGroupMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) 142 ImGroupMessageDO message = new ImGroupMessageDO().setClientMessageId(IdUtil.fastSimpleUUID())
138 .setSenderId(senderId).setGroupId(dto.getGroupId()) 143 .setSenderId(senderId).setGroupId(dto.getGroupId())
139 .setType(dto.getType()).setContent(contentString) 144 .setType(dto.getType()).setContent(contentString)
140 - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now())  
141 - .setAtUserIds(dto.getAtUserIds()).setReceiverUserIds(dto.getReceiverUserIds()) 145 + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now())
  146 + .setAtUserIds(dto.getAtUserIds()).setReceiverUserIds(new ArrayList<>(receiverUserIds))
142 .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())); 147 .setReceiptStatus(resolveReceiptStatus(dto.getReceipt()));
143 // 1.3 按 type.persistent 决定是否入库 148 // 1.3 按 type.persistent 决定是否入库
144 - if (ImMessageTypeEnum.validate(dto.getType()).isPersistent()) { 149 + if (ImContentTypeEnum.validate(dto.getType()).isPersistent()) {
145 groupMessageMapper.insert(message); 150 groupMessageMapper.insert(message);
146 } 151 }
147 152
148 // 2. WebSocket 异步推送 153 // 2. WebSocket 异步推送
149 - imWebSocketService.sendGroupMessageAsync(targetUserIds, ImGroupMessageDTO.ofSend(message)); 154 + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message);
  155 + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(),
  156 + notification.getType(), notification);
150 return message; 157 return message;
151 } 158 }
152 159
@@ -180,7 +187,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -180,7 +187,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
180 187
181 // 3. 发送撤回事件 188 // 3. 发送撤回事件
182 return sendGroupMessage(userId, new ImGroupMessageSendDTO().setGroupId(message.getGroupId()) 189 return sendGroupMessage(userId, new ImGroupMessageSendDTO().setGroupId(message.getGroupId())
183 - .setType(ImMessageTypeEnum.RECALL.getType()) 190 + .setType(ImContentTypeEnum.RECALL.getType())
184 .setReceiverUserIds(message.getReceiverUserIds()) 191 .setReceiverUserIds(message.getReceiverUserIds())
185 .setContent(new RecallMessage().setMessageId(messageId))); 192 .setContent(new RecallMessage().setMessageId(messageId)));
186 } 193 }
@@ -195,155 +202,53 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -195,155 +202,53 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
195 // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 202 // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送
196 LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); 203 LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays());
197 204
198 - // 1.1 主查询:仅用"当前仍在群"的成员记录驱动;若首批消息过滤后为空,则允许内部重试  
199 - List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByUserId(userId);  
200 - Map<Long, ImGroupMemberDO> memberMap = convertMap(activeMembers, ImGroupMemberDO::getGroupId);  
201 - List<ImGroupMessageDO> messages = new ArrayList<>();  
202 - if (CollUtil.isNotEmpty(activeMembers)) {  
203 - List<Long> groupIds = convertList(activeMembers, ImGroupMemberDO::getGroupId);  
204 - messages.addAll(pullActiveGroupMessageList(userId, groupIds, minId, size, minSendTime, memberMap));  
205 - }  
206 - // 1.2 补齐"退群前"的消息:  
207 - // - 继续基于用户原始 minId 单次查询,不能被主查询内部探测游标带着前进;  
208 - // - 若 minId > 0 且能查到对应消息,则进一步把下限抬到该消息的 sendTime,避免把客户端已拥有的老消息再次推送。  
209 - messages.addAll(pullQuitGroupMessageList(userId, minId, size, minSendTime, memberMap));  
210 -  
211 - // 2. 合并后统一过滤,得到当前用户可见的结果  
212 - List<ImGroupMessageDO> result = filterGroupMessageList(messages, memberMap, userId, size);  
213 -  
214 - // 3. 按当前用户补齐:消息已读态(相对 Redis 已读游标)、本人发送的回执消息的已读人数  
215 - appendMessageStatusAndReceipt(userId, result);  
216 - log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", userId, minId, size, result.size());  
217 - return result;  
218 - }  
219 -  
220 - /**  
221 - * 拉取当前仍在群的主路径消息。  
222 - *  
223 - * 仅当首批消息过滤后仍无可见消息时,才按消息最大 id 继续向后探测,  
224 - * 直到命中可见消息或确认该来源已耗尽。  
225 - */  
226 - private List<ImGroupMessageDO> pullActiveGroupMessageList(Long userId, List<Long> groupIds, Long minId,  
227 - Integer size, LocalDateTime minSendTime,  
228 - Map<Long, ImGroupMemberDO> memberMap) {  
229 - // 1. 主查询内部探测游标:仅用于向后探测,不代表客户端真实已送达边界  
230 - Long activeMinId = minId;  
231 - for (int retryCount = 0; retryCount <= PULL_GROUP_MESSAGE_EMPTY_RETRY_TIMES; retryCount++) {  
232 - // 2. 查询本轮消息;若已无更多消息,则当前主路径直接结束  
233 - List<ImGroupMessageDO> messages = groupMessageMapper.selectListByMinId(groupIds, activeMinId,  
234 - minSendTime, size);  
235 - if (CollUtil.isEmpty(messages)) {  
236 - return Collections.emptyList();  
237 - }  
238 - boolean hasVisibleMessage = CollUtil.anyMatch(messages,  
239 - message -> isMessageVisible(message, memberMap.get(message.getGroupId()), userId));  
240 - boolean sourceExhausted = messages.size() < size;  
241 - // 3. 本轮已命中可见消息,或主查询来源已耗尽,直接返回这一轮消息  
242 - if (hasVisibleMessage || sourceExhausted) {  
243 - return messages;  
244 - }  
245 -  
246 - // 4. 按本轮消息最大 id 推进内部游标,跳过这段不可见区间;若游标未前进则直接停止  
247 - Long maxMessageId = getMaxValue(messages, ImGroupMessageDO::getId);  
248 - if (maxMessageId == null || maxMessageId <= activeMinId) {  
249 - return Collections.emptyList();  
250 - }  
251 - activeMinId = maxMessageId; 205 + // 1. 候选群 = 用户曾经加入的所有群(含退群);可见性与时间窗都交给 SQL,退群群最多贡献 0 条
  206 + Set<Long> groupIds = convertSet(groupMemberService.getGroupMemberListByUserId(userId),
  207 + ImGroupMemberDO::getGroupId);
  208 + if (CollUtil.isEmpty(groupIds)) {
  209 + return Collections.emptyList();
252 } 210 }
253 - return Collections.emptyList();  
254 - }  
255 211
256 - /**  
257 - * 拉取已离开群路径的消息(退群前消息)  
258 - */  
259 - private List<ImGroupMessageDO> pullQuitGroupMessageList(Long userId, Long minId, Integer size,  
260 - LocalDateTime minSendTime,  
261 - Map<Long, ImGroupMemberDO> memberMap) {  
262 - // 1. 退群补齐始终基于用户原始 minId 计算时间边界,避免被主查询内部重试游标误伤  
263 - LocalDateTime minQuitTime = minSendTime;  
264 - if (minId != null && minId > 0) {  
265 - ImGroupMessageDO minMessage = groupMessageMapper.selectById(minId);  
266 - if (minMessage != null && minMessage.getSendTime() != null  
267 - && minMessage.getSendTime().isAfter(minSendTime)) {  
268 - minQuitTime = minMessage.getSendTime();  
269 - }  
270 - }  
271 - // 2. 查询用户离开的群记录;若原始 minId 对应消息仍在窗口内,则用它的发送时间抬升退群筛选下限  
272 - List<ImGroupMessageDO> messages = new ArrayList<>();  
273 - List<ImGroupMemberDO> quitMembers = groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime);  
274 - for (ImGroupMemberDO quitMember : quitMembers) {  
275 - // 3. 按原始 minId + 退群时间补齐该群退群前消息,并把成员记录写回 memberMap 供统一可见性过滤使用  
276 - List<ImGroupMessageDO> quitGroupMessages = groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(  
277 - quitMember.getGroupId(), minId, minSendTime, quitMember.getQuitTime(), size);  
278 - if (CollUtil.isEmpty(quitGroupMessages)) {  
279 - continue;  
280 - }  
281 - messages.addAll(quitGroupMessages);  
282 - memberMap.put(quitMember.getGroupId(), quitMember);  
283 - }  
284 - return messages;  
285 - } 212 + // 2. 按 receiver_user_ids 快照过滤可见性,结果按 id 升序
  213 + List<ImGroupMessageDO> messages = groupMessageMapper.selectListByMinId(userId, groupIds, minId, minSendTime, size);
286 214
287 - /**  
288 - * 过滤一批原始群消息,得到当前用户可见的返回结果  
289 - */  
290 - private List<ImGroupMessageDO> filterGroupMessageList(List<ImGroupMessageDO> messages,  
291 - Map<Long, ImGroupMemberDO> memberMap,  
292 - Long userId, Integer size) {  
293 - // 按可见性过滤(入群前不可见、定向消息排除),按 id 升序后仅取本页 size 条,  
294 - // 避免「在群 + 退群前」多路合并时一次响应跨度过大、游标直接跳到全局最大 id 而漏拉中间消息  
295 - return messages.stream()  
296 - .filter(msg -> isMessageVisible(msg, memberMap.get(msg.getGroupId()), userId))  
297 - .sorted(Comparator.comparing(ImGroupMessageDO::getId))  
298 - .limit(size)  
299 - .collect(Collectors.toList()); 215 + // 3. 补齐本人发送的回执消息的已读人数
  216 + appendMessageReceipt(userId, messages);
  217 + log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", userId, minId, size, messages.size());
  218 + return messages;
300 } 219 }
301 220
302 /** 221 /**
303 - * 补全消息已读态和回执已读人数  
304 - *  
305 - * 1. 消息已读态(status):根据 Redis 已读游标判断 READ / UNREAD  
306 - * 2. 回执已读人数(readCount):仅对本人发送的回执消息,计算可见成员中的已读人数 222 + * 补全本人发送消息的回执已读人数(readCount)
  223 + * <p>
  224 + * 仅对本人发送、且需要回执(非 NO_RECEIPT)的消息,按 receiver_user_ids 快照 ∩ 当前有效成员 - 发送者 算已读人数
307 */ 225 */
308 - @SuppressWarnings({"StatementWithEmptyBody", "DataFlowIssue"})  
309 - private void appendMessageStatusAndReceipt(Long userId, List<ImGroupMessageDO> messages) { 226 + @SuppressWarnings("DataFlowIssue")
  227 + private void appendMessageReceipt(Long userId, List<ImGroupMessageDO> messages) {
310 if (CollUtil.isEmpty(messages)) { 228 if (CollUtil.isEmpty(messages)) {
311 return; 229 return;
312 } 230 }
313 - // 群已读关闭:不查 Redis 已读游标,status 保持 DB 原值(含撤回),readCount 不补齐 231 + // 群已读关闭:不补 readCount
314 if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { 232 if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) {
315 return; 233 return;
316 } 234 }
317 - Map<Long, Long> readMaxMessageIdsByGroup = new HashMap<>(); // 群 → 已读位置  
318 Map<Long, Map<Long, Long>> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置) 235 Map<Long, Map<Long, Long>> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置)
319 Map<Long, List<ImGroupMemberDO>> membersByGroup = new HashMap<>(); // 群 → 全部成员列表 236 Map<Long, List<ImGroupMemberDO>> membersByGroup = new HashMap<>(); // 群 → 全部成员列表
320 for (ImGroupMessageDO message : messages) { 237 for (ImGroupMessageDO message : messages) {
321 - // 消息已读态(status):撤回 > 已读 > 未读  
322 - Long groupId = message.getGroupId();  
323 - long readMaxMessageId = readMaxMessageIdsByGroup.computeIfAbsent(groupId, gid -> {  
324 - Long readMaxMsgId = groupMessageReadRedisDAO.getReadMaxMessageId(gid, userId);  
325 - return readMaxMsgId != null ? readMaxMsgId : -1L;  
326 - });  
327 - if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) {  
328 - // 保持撤回态  
329 - } else if (readMaxMessageId >= message.getId()) {  
330 - message.setStatus(ImMessageStatusEnum.READ.getStatus());  
331 - } else {  
332 - message.setStatus(ImMessageStatusEnum.UNREAD.getStatus());  
333 - }  
334 -  
335 - // 回执消息的已读人数(readCount):仅补齐本人发送的,其他消息不处理(回执消息才关心已读人数,且只对发送者可见) 238 + // 仅补本人发送、且需要回执(非 NO_RECEIPT)的消息
336 if (ObjUtil.notEqual(message.getSenderId(), userId) 239 if (ObjUtil.notEqual(message.getSenderId(), userId)
337 - || ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus().equals(message.getReceiptStatus())) { 240 + || ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus().equals(message.getReceiptStatus())) {
338 continue; 241 continue;
339 } 242 }
  243 + Long groupId = message.getGroupId();
340 Map<Long, Long> positions = readPositionsByGroup.computeIfAbsent(groupId, 244 Map<Long, Long> positions = readPositionsByGroup.computeIfAbsent(groupId,
341 - gid -> groupMessageReadRedisDAO.getReadMaxMessageIdMap(gid));  
342 - List<ImGroupMemberDO> allMembers = membersByGroup.computeIfAbsent(groupId,  
343 - groupMemberService::getGroupMemberListByGroupId);  
344 - Set<Long> visibleUserIds = getVisibleUserIds(message, allMembers);  
345 - visibleUserIds.remove(message.getSenderId());  
346 - int readCount = getSumValue(visibleUserIds, 245 + gid -> conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), gid));
  246 + // 应读分母取当前有效成员(剔除已退群),与异步回执刷新、前端已读弹层口径一致
  247 + List<ImGroupMemberDO> activeMembers = membersByGroup.computeIfAbsent(groupId,
  248 + groupMemberService::getActiveGroupMemberListByGroupId);
  249 + Set<Long> receiverUserIds = getReceiverUserIds(message, activeMembers);
  250 + receiverUserIds.remove(message.getSenderId());
  251 + int readCount = getSumValue(receiverUserIds,
347 uid -> positions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, 252 uid -> positions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null,
348 Integer::sum, 0); 253 Integer::sum, 0);
349 message.setReadCount(readCount); 254 message.setReadCount(readCount);
@@ -357,25 +262,24 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -357,25 +262,24 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
357 throw exception(MESSAGE_GROUP_READ_DISABLED); 262 throw exception(MESSAGE_GROUP_READ_DISABLED);
358 } 263 }
359 Assert.notNull(messageId, "已读消息编号不能为空"); 264 Assert.notNull(messageId, "已读消息编号不能为空");
360 - // 1.1 获取群成员记录  
361 - ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId);  
362 - // 1.2 校验消息属于当前群,且对当前用户可见 265 + // 1. 校验消息属于当前群,且对当前用户可见(按快照)
363 ImGroupMessageDO message = groupMessageMapper.selectById(messageId); 266 ImGroupMessageDO message = groupMessageMapper.selectById(messageId);
364 if (message == null 267 if (message == null
365 || ObjUtil.notEqual(message.getGroupId(), groupId) 268 || ObjUtil.notEqual(message.getGroupId(), groupId)
366 - || !isMessageVisible(message, member, userId)) { 269 + || !isMessageReceived(message, userId)) {
367 throw exception(MESSAGE_NOT_IN_GROUP); 270 throw exception(MESSAGE_NOT_IN_GROUP);
368 } 271 }
369 272
370 - // 2. 已读位置未前进,直接返回  
371 - Long prevMaxMessageId = groupMessageReadRedisDAO.getReadMaxMessageId(groupId, userId);  
372 - if (prevMaxMessageId != null && prevMaxMessageId >= messageId) { 273 + // 2. 取旧读位置(用于回执刷新区间),再更新群已读位置;读位置未前进则不推
  274 + Long prevMaxMessageId = conversationReadService.getConversationReadMessageId(
  275 + userId, ImConversationTypeEnum.GROUP.getType(), groupId);
  276 + boolean advanced = conversationReadService.updateConversationReadPosition(
  277 + userId, ImConversationTypeEnum.GROUP.getType(), groupId, messageId);
  278 + if (!advanced) {
373 return; 279 return;
374 } 280 }
375 - // 3. 更新 Redis 群已读位置  
376 - groupMessageReadRedisDAO.updateReadMaxMessageId(groupId, userId, messageId);  
377 281
378 - // 4. 异步发送 READ 事件 + 刷新范围内的群回执 282 + // 3. 异步发送 READ 事件 + 刷新范围内的群回执
379 getSelf().readGroupMessageEvent(userId, groupId, prevMaxMessageId, messageId); 283 getSelf().readGroupMessageEvent(userId, groupId, prevMaxMessageId, messageId);
380 } 284 }
381 285
@@ -386,13 +290,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -386,13 +290,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
386 throw exception(MESSAGE_GROUP_READ_DISABLED); 290 throw exception(MESSAGE_GROUP_READ_DISABLED);
387 } 291 }
388 // 1.1 校验用户在群中(权限校验) 292 // 1.1 校验用户在群中(权限校验)
389 - ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); 293 + groupMemberService.validateMemberInGroup(groupId, userId);
390 // 1.2 获取消息;并校验消息归属于该群、对调用者可见、调用者是发送方 294 // 1.2 获取消息;并校验消息归属于该群、对调用者可见、调用者是发送方
391 ImGroupMessageDO message = groupMessageMapper.selectById(messageId); 295 ImGroupMessageDO message = groupMessageMapper.selectById(messageId);
392 if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { 296 if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) {
393 return Collections.emptyList(); 297 return Collections.emptyList();
394 } 298 }
395 - if (!isMessageVisible(message, operator, userId)) { 299 + if (!isMessageReceived(message, userId)) {
396 return Collections.emptyList(); 300 return Collections.emptyList();
397 } 301 }
398 // 1.3 仅消息发送方关心已读人数;非发送方查询直接返回空 302 // 1.3 仅消息发送方关心已读人数;非发送方查询直接返回空
@@ -400,18 +304,19 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -400,18 +304,19 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
400 return Collections.emptyList(); 304 return Collections.emptyList();
401 } 305 }
402 306
403 - // 2. 获取所有成员和已读位置  
404 - List<ImGroupMemberDO> allMembers = groupMemberService.getGroupMemberListByGroupId(groupId);  
405 - Map<Long, Long> allPositions = groupMessageReadRedisDAO.getReadMaxMessageIdMap(groupId); 307 + // 2. 获取当前有效成员和已读位置(剔除已退群,与回执分母口径一致)
  308 + List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId);
  309 + Map<Long, Long> allPositions = conversationReadService.getUserReadMessageIdMap(
  310 + ImConversationTypeEnum.GROUP.getType(), groupId);
406 311
407 // 3. 计算该消息的可见成员集合(排除发送者自己) 312 // 3. 计算该消息的可见成员集合(排除发送者自己)
408 - Set<Long> visibleUserIds = getVisibleUserIds(message, allMembers);  
409 - visibleUserIds.remove(message.getSenderId()); 313 + Set<Long> receiverUserIds = getReceiverUserIds(message, activeMembers);
  314 + receiverUserIds.remove(message.getSenderId());
410 315
411 // 4. 只返回在可见范围内且已读位置 >= messageId 的用户 316 // 4. 只返回在可见范围内且已读位置 >= messageId 的用户
412 List<Long> readUserIds = new ArrayList<>(); 317 List<Long> readUserIds = new ArrayList<>();
413 allPositions.forEach((uid, readMaxMessageId) -> { 318 allPositions.forEach((uid, readMaxMessageId) -> {
414 - if (visibleUserIds.contains(uid) && readMaxMessageId >= messageId) { 319 + if (receiverUserIds.contains(uid) && readMaxMessageId >= messageId) {
415 readUserIds.add(uid); 320 readUserIds.add(uid);
416 } 321 }
417 }); 322 });
@@ -420,15 +325,14 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -420,15 +325,14 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
420 325
421 @Override 326 @Override
422 public List<ImGroupMessageDO> getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO) { 327 public List<ImGroupMessageDO> getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO) {
423 - // 1. 校验用户在群中  
424 - ImGroupMemberDO member = groupMemberService.validateMemberInGroup(reqVO.getGroupId(), userId);  
425 -  
426 - // 2. 查询历史消息(仅入群之后)  
427 - List<ImGroupMessageDO> messages = groupMessageMapper.selectHistoryList(  
428 - reqVO.getGroupId(), reqVO.getMaxId(), reqVO.getLimit(), member.getJoinTime()); 328 + // 1. 校验用户曾经在群(当前在群或已退群),与 pull 退群窗口口径一致;内容仍由 receiver_user_ids 快照过滤
  329 + if (groupMemberService.getGroupMember(reqVO.getGroupId(), userId) == null) {
  330 + throw exception(GROUP_MEMBER_NOT_IN_GROUP);
  331 + }
429 332
430 - // 3. 过滤定向消息:仅保留当前用户可见的(receiverUserIds 为空 / 含当前用户 / 本人发送)  
431 - return filterList(messages, message -> isMessageVisible(message, member, userId)); 333 + // 2. 查询历史消息:SQL 已按 receiver_user_ids 快照过滤当前用户可见
  334 + return groupMessageMapper.selectHistoryListByUser(
  335 + userId, reqVO.getGroupId(), reqVO.getMaxId(), reqVO.getLimit());
432 } 336 }
433 337
434 // ========== 异步 WebSocket 推送 ========== 338 // ========== 异步 WebSocket 推送 ==========
@@ -445,8 +349,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -445,8 +349,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
445 @SuppressWarnings("DataFlowIssue") 349 @SuppressWarnings("DataFlowIssue")
446 public void readGroupMessageEvent(Long userId, Long groupId, Long prevMaxMessageId, Long newMaxMessageId) { 350 public void readGroupMessageEvent(Long userId, Long groupId, Long prevMaxMessageId, Long newMaxMessageId) {
447 // 1. 发送 READ 事件给自己的其他终端(多端同步) 351 // 1. 发送 READ 事件给自己的其他终端(多端同步)
448 - imWebSocketService.sendGroupMessageAsync(userId,  
449 - ImGroupMessageDTO.ofRead(userId, groupId, newMaxMessageId)); 352 + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.GROUP.getType(),
  353 + ImContentTypeEnum.READ.getType(),
  354 + ImMessageReadNotification.ofGroup(userId, groupId, newMaxMessageId));
450 355
451 // 2. 刷新 (prevMaxMessageId, newMaxMessageId] 区间内的待回执消息 356 // 2. 刷新 (prevMaxMessageId, newMaxMessageId] 区间内的待回执消息
452 List<ImGroupMessageDO> pendingMessages = groupMessageMapper.selectListByGroupIdAndPendingReceipt( 357 List<ImGroupMessageDO> pendingMessages = groupMessageMapper.selectListByGroupIdAndPendingReceipt(
@@ -455,28 +360,30 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -455,28 +360,30 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
455 return; 360 return;
456 } 361 }
457 List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); 362 List<ImGroupMemberDO> activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId);
458 - Map<Long, Long> allPositions = groupMessageReadRedisDAO.getReadMaxMessageIdMap(groupId); 363 + Map<Long, Long> allPositions = conversationReadService.getUserReadMessageIdMap(
  364 + ImConversationTypeEnum.GROUP.getType(), groupId);
459 for (ImGroupMessageDO message : pendingMessages) { 365 for (ImGroupMessageDO message : pendingMessages) {
460 - // 2.1.1 统计可见成员中的已读人数  
461 - Set<Long> visibleUserIds = getVisibleUserIds(message, activeMembers);  
462 - visibleUserIds.remove(message.getSenderId()); // 发送者自己不算已读  
463 - if (CollUtil.isEmpty(visibleUserIds)) { 366 + // 2.1.1 统计可见成员中的已读人数(应读分母 = 快照 ∩ 当前有效成员 - sender)
  367 + Set<Long> receiverUserIds = getReceiverUserIds(message, activeMembers);
  368 + receiverUserIds.remove(message.getSenderId()); // 发送者自己不算已读
  369 + if (CollUtil.isEmpty(receiverUserIds)) {
464 continue; 370 continue;
465 } 371 }
466 - int readCount = getSumValue(visibleUserIds, 372 + int readCount = getSumValue(receiverUserIds,
467 uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, 373 uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null,
468 Integer::sum, 0); 374 Integer::sum, 0);
469 // 2.1.2 全部已读 → 标记回执完成 375 // 2.1.2 全部已读 → 标记回执完成
470 - Integer newReceiptStatus = ImGroupMessageReceiptStatusEnum.PENDING.getStatus();  
471 - if (readCount >= visibleUserIds.size()) {  
472 - newReceiptStatus = ImGroupMessageReceiptStatusEnum.DONE.getStatus(); 376 + Integer newReceiptStatus = ImMessageReceiptStatusEnum.PENDING.getStatus();
  377 + if (readCount >= receiverUserIds.size()) {
  378 + newReceiptStatus = ImMessageReceiptStatusEnum.DONE.getStatus();
473 groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId()) 379 groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId())
474 .setReceiptStatus(newReceiptStatus)); 380 .setReceiptStatus(newReceiptStatus));
475 } 381 }
476 382
477 // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度) 383 // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度)
478 - imWebSocketService.sendGroupMessageAsync(message.getSenderId(),  
479 - ImGroupMessageDTO.ofReceipt(message.getId(), groupId, readCount, newReceiptStatus)); 384 + imWebSocketService.sendNotificationAsync(message.getSenderId(), ImConversationTypeEnum.GROUP.getType(),
  385 + ImContentTypeEnum.RECEIPT.getType(),
  386 + ImMessageReceiptNotification.ofGroup(message.getId(), groupId, readCount, newReceiptStatus));
480 } 387 }
481 } 388 }
482 389
@@ -487,9 +394,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -487,9 +394,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
487 * 394 *
488 * @param reqVO 发送请求 395 * @param reqVO 发送请求
489 * @param senderMember 发送人成员 396 * @param senderMember 发送人成员
  397 + * @param newAudience 新消息可见成员集合(发送当时快照)
490 * @return 规范化后的 content 398 * @return 规范化后的 content
491 */ 399 */
492 - private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember) { 400 + private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember,
  401 + Set<Long> newAudience) {
493 // 解析客户端 content 里的 quote.messageId 402 // 解析客户端 content 里的 quote.messageId
494 Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); 403 Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent());
495 404
@@ -508,12 +417,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -508,12 +417,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
508 if (ObjUtil.notEqual(original.getGroupId(), reqVO.getGroupId())) { 417 if (ObjUtil.notEqual(original.getGroupId(), reqVO.getGroupId())) {
509 throw exception(MESSAGE_QUOTE_INVALID); 418 throw exception(MESSAGE_QUOTE_INVALID);
510 } 419 }
511 - // 拒绝定向消息(仅发送人可见的内容若被全员广播 quote.content,会泄漏给原本看不到的成员)  
512 - if (CollUtil.isNotEmpty(original.getReceiverUserIds())) { 420 + // 校验对发送人可见(按快照)
  421 + if (!isMessageReceived(original, senderMember.getUserId())) {
513 throw exception(MESSAGE_QUOTE_INVALID); 422 throw exception(MESSAGE_QUOTE_INVALID);
514 } 423 }
515 - // 校验对发送人可见(入群时间 / 退群时间)  
516 - if (!isMessageVisible(original, senderMember, senderMember.getUserId())) { 424 + // 防泄漏:新消息可见集合必须是被引用消息可见集合的子集,否则禁止引用;
  425 + // 否则只对部分成员可见的定向消息内容,会随广播引用泄漏给原本看不到的成员
  426 + if (!new HashSet<>(original.getReceiverUserIds()).containsAll(newAudience)) {
517 throw exception(MESSAGE_QUOTE_INVALID); 427 throw exception(MESSAGE_QUOTE_INVALID);
518 } 428 }
519 // 构建 quote 对象并注入 content 429 // 构建 quote 对象并注入 content
@@ -537,87 +447,48 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -537,87 +447,48 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
537 } 447 }
538 448
539 /** 449 /**
540 - * 判断一条群消息对某个群成员是否可见 450 + * 判断一条群消息是否被某个用户接收到
  451 + * <p>
  452 + * 接收范围以发送当时固化的 receiver_user_ids 快照为准;发送者也在快照内。
541 * 453 *
542 - * @param msg 消息  
543 - * @param member 群成员  
544 - * @param userId 当前用户编号(用于定向消息过滤)  
545 - * @return 是否可见 454 + * @param message 消息
  455 + * @param userId 当前用户编号
  456 + * @return 是否接收到
546 */ 457 */
547 - private boolean isMessageVisible(ImGroupMessageDO msg, ImGroupMemberDO member, Long userId) {  
548 - if (member == null) {  
549 - return false;  
550 - }  
551 - // 1. 入群时间晚于消息发送时间 → 不可见  
552 - if (member.getJoinTime() != null && msg.getSendTime().isBefore(member.getJoinTime())) {  
553 - return false;  
554 - }  
555 - // 2. 已退群且退群时间早于消息发送时间 → 不可见  
556 - if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())  
557 - && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) {  
558 - return false;  
559 - }  
560 - // 3.1 无定向接收列表 → 全员可见  
561 - if (CollUtil.isEmpty(msg.getReceiverUserIds())) {  
562 - return true;  
563 - }  
564 - // 3.2 当前用户在定向列表中,或本人即发送者 → 可见  
565 - return msg.getReceiverUserIds().contains(userId)  
566 - || ObjUtil.equal(msg.getSenderId(), userId); 458 + @SuppressWarnings("BooleanMethodIsAlwaysInverted")
  459 + private boolean isMessageReceived(ImGroupMessageDO message, Long userId) {
  460 + return CollUtil.contains(message.getReceiverUserIds(), userId);
567 } 461 }
568 462
569 /** 463 /**
570 - * 计算一条群消息的可见成员集合(含发送者) 464 + * 计算一条群消息的可见成员集合(快照与传入成员的交集,含发送者)
571 */ 465 */
572 - private Set<Long> getVisibleUserIds(ImGroupMessageDO message, List<ImGroupMemberDO> members) {  
573 - return convertSet(members, ImGroupMemberDO::getUserId,  
574 - member -> isMessageVisible(message, member, member.getUserId())); 466 + private Set<Long> getReceiverUserIds(ImGroupMessageDO message, List<ImGroupMemberDO> members) {
  467 + if (CollUtil.isEmpty(message.getReceiverUserIds())) {
  468 + return new HashSet<>();
  469 + }
  470 + Set<Long> snapshot = new HashSet<>(message.getReceiverUserIds());
  471 + return convertSet(members, ImGroupMemberDO::getUserId, member -> snapshot.contains(member.getUserId()));
575 } 472 }
576 473
577 /** 474 /**
578 - * 基于群成员 userId 列表,过滤出一条新消息的可见成员集合(含发送者)。 475 + * 基于群成员 userId 列表,计算一条新消息的可见成员快照(含发送者)。
579 * <p> 476 * <p>
580 - * 仅适用于「新消息」推送场景({@code sendTime = now}),不涉及 joinTime / quitTime 判定,  
581 - * 只应用 {@code receiverUserIds} 定向过滤;语义与  
582 - * {@link #isMessageVisible(ImGroupMessageDO, ImGroupMemberDO, Long)} 的第 3 步保持一致。 477 + * 仅适用于「新消息」发送场景:无定向接收列表则全员可见,否则取定向用户与当前成员的交集;
  478 + * 发送者始终纳入快照,保证 receiver_user_ids 非空且发送方可见。
583 */ 479 */
584 - private Set<Long> getVisibleUserIds(List<Long> receiverUserIds, Long senderId, Collection<Long> memberUserIds) {  
585 - if (CollUtil.isEmpty(memberUserIds)) {  
586 - return new HashSet<>();  
587 - }  
588 - // 无定向接收列表 → 全员可见  
589 - if (CollUtil.isEmpty(receiverUserIds)) {  
590 - return new HashSet<>(memberUserIds);  
591 - }  
592 - // 有定向接收列表 → 仅定向用户可见;发送者自己也能看到自己的消息(多端同步)  
593 - Set<Long> allowed = new HashSet<>(receiverUserIds); 480 + private Set<Long> getReceiverUserIds(List<Long> directedUserIds, Long senderId, Collection<Long> memberUserIds) {
  481 + // 无定向接收列表 → 全员可见;否则取定向用户与当前成员的交集
  482 + Set<Long> result = CollUtil.isEmpty(directedUserIds)
  483 + ? new HashSet<>(memberUserIds)
  484 + : new HashSet<>(CollUtil.intersection(memberUserIds, directedUserIds));
  485 + // 发送者始终可见(多端同步),即便成员缓存暂未包含
594 if (senderId != null) { 486 if (senderId != null) {
595 - allowed.add(senderId);  
596 - }  
597 - Set<Long> result = new HashSet<>();  
598 - for (Long userId : memberUserIds) {  
599 - if (allowed.contains(userId)) {  
600 - result.add(userId);  
601 - } 487 + result.add(senderId);
602 } 488 }
603 return result; 489 return result;
604 } 490 }
605 491
606 - @Override  
607 - public void deleteReadMaxMessageId(Long groupId, Long userId) {  
608 - groupMessageReadRedisDAO.deleteReadMaxMessageId(groupId, userId);  
609 - }  
610 -  
611 - @Override  
612 - public void deleteReadMaxMessageIds(Long groupId, Collection<Long> userIds) {  
613 - groupMessageReadRedisDAO.deleteReadMaxMessageIds(groupId, userIds);  
614 - }  
615 -  
616 - @Override  
617 - public void deleteReadMaxMessageIdMap(Long groupId) {  
618 - groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId);  
619 - }  
620 -  
621 // ==================== 管理后台 ==================== 492 // ==================== 管理后台 ====================
622 493
623 @Override 494 @Override
@@ -652,11 +523,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @@ -652,11 +523,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
652 */ 523 */
653 private Integer resolveReceiptStatus(Boolean receipt) { 524 private Integer resolveReceiptStatus(Boolean receipt) {
654 if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { 525 if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) {
655 - return ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); 526 + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus();
656 } 527 }
657 return BooleanUtil.isTrue(receipt) 528 return BooleanUtil.isTrue(receipt)
658 - ? ImGroupMessageReceiptStatusEnum.PENDING.getStatus()  
659 - : ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); 529 + ? ImMessageReceiptStatusEnum.PENDING.getStatus()
  530 + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus();
660 } 531 }
661 532
662 /** 533 /**
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
@@ -59,8 +59,8 @@ public interface ImPrivateMessageService { @@ -59,8 +59,8 @@ public interface ImPrivateMessageService {
59 /** 59 /**
60 * 标记私聊消息已读 60 * 标记私聊消息已读
61 * <p> 61 * <p>
62 - * 语义:将「对方发给当前用户、id <= messageId 的未读消息」一次性翻转为已读,  
63 - * 与群聊 readGroupMessages 对称,避免"select-then-update"两步式带来的竞态。 62 + * 语义:把「对方发给当前用户、id <= messageId 的待回执消息」一次性更新为已完成(DONE)并前进读位置,
  63 + * 与群聊 readGroupMessages 对称,避免「select-then-update」两步式带来的竞态。
64 * 64 *
65 * @param userId 当前用户编号 65 * @param userId 当前用户编号
66 * @param receiverId 接收方用户编号(对方) 66 * @param receiverId 接收方用户编号(对方)
@@ -72,7 +72,7 @@ public interface ImPrivateMessageService { @@ -72,7 +72,7 @@ public interface ImPrivateMessageService {
72 * 查询对方已读到我发的最大消息 id 72 * 查询对方已读到我发的最大消息 id
73 * <p> 73 * <p>
74 * 用于多端 / 离线场景下的已读位置补齐:客户端进入会话或断线重连后, 74 * 用于多端 / 离线场景下的已读位置补齐:客户端进入会话或断线重连后,
75 - * 调用此接口拿到对方的 maxReadId,再按 id <= maxReadId 翻转本地自发消息为已读,弥补离线期间错过的 RECEIPT 推送事件。 75 + * 调用此接口拿到对方的 maxReadId,再按 id <= maxReadId 更新本地自发消息的回执状态,弥补离线期间错过的 RECEIPT 推送事件。
76 * 76 *
77 * @param userId 当前用户编号 77 * @param userId 当前用户编号
78 * @param peerId 对方用户编号 78 * @param peerId 对方用户编号
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
@@ -12,19 +12,25 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate @@ -12,19 +12,25 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate
12 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; 12 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
13 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 13 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
  15 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  16 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
15 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 17 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
16 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 18 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
17 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 19 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
  20 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
18 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 21 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
19 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 22 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
20 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 23 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
21 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 24 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
22 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
23 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage;  
24 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; 25 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification;
  26 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification;
  27 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification;
  28 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage;
  29 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage;
25 import cn.iocoder.yudao.module.im.util.ImMessageUtils; 30 import cn.iocoder.yudao.module.im.util.ImMessageUtils;
26 import jakarta.annotation.Resource; 31 import jakarta.annotation.Resource;
27 import lombok.extern.slf4j.Slf4j; 32 import lombok.extern.slf4j.Slf4j;
  33 +import org.springframework.dao.DuplicateKeyException;
28 import org.springframework.stereotype.Service; 34 import org.springframework.stereotype.Service;
29 import org.springframework.transaction.annotation.Transactional; 35 import org.springframework.transaction.annotation.Transactional;
30 import org.springframework.validation.annotation.Validated; 36 import org.springframework.validation.annotation.Validated;
@@ -52,6 +58,8 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -52,6 +58,8 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
52 private ImFriendService friendService; 58 private ImFriendService friendService;
53 @Resource 59 @Resource
54 private ImSensitiveWordService sensitiveWordService; 60 private ImSensitiveWordService sensitiveWordService;
  61 + @Resource
  62 + private ImConversationReadService conversationReadService;
55 63
56 @Resource 64 @Resource
57 private ImWebSocketService imWebSocketService; 65 private ImWebSocketService imWebSocketService;
@@ -74,21 +82,32 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -74,21 +82,32 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
74 // 1.3 好友校验 82 // 1.3 好友校验
75 friendService.validateFriend(senderId, reqVO.getReceiverId()); 83 friendService.validateFriend(senderId, reqVO.getReceiverId());
76 // 1.4 文本消息敏感词过滤 84 // 1.4 文本消息敏感词过滤
77 - if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { 85 + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) {
78 sensitiveWordService.validateText(reqVO.getContent()); 86 sensitiveWordService.validateText(reqVO.getContent());
79 } 87 }
80 88
81 // 2.1 引用 quote 消息规范化 89 // 2.1 引用 quote 消息规范化
82 reqVO.setContent(normalizeQuoteContent(reqVO, senderId)); 90 reqVO.setContent(normalizeQuoteContent(reqVO, senderId));
83 - // 2.2 构建并保存消息 91 + // 2.2 构建并保存消息;唯一键冲突时回查已存在消息返回
  92 + // 用户私聊消息默认需要回执(receipt 不传按 true);系统通知走 DTO 通道,默认不回执
  93 + Boolean receipt = reqVO.getReceipt() != null ? reqVO.getReceipt() : Boolean.TRUE;
84 ImPrivateMessageDO message = BeanUtils.toBean(reqVO, ImPrivateMessageDO.class, m -> m 94 ImPrivateMessageDO message = BeanUtils.toBean(reqVO, ImPrivateMessageDO.class, m -> m
85 - .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()));  
86 - privateMessageMapper.insert(message); 95 + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus())
  96 + .setReceiptStatus(resolveReceiptStatus(receipt)).setSendTime(LocalDateTime.now()));
  97 + try {
  98 + privateMessageMapper.insert(message);
  99 + } catch (DuplicateKeyException e) {
  100 + log.warn("[sendPrivateMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]",
  101 + senderId, reqVO.getClientMessageId());
  102 + return privateMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId());
  103 + }
87 104
88 // 3. WebSocket 异步推送:接收方 + 发送方多端同步 105 // 3. WebSocket 异步推送:接收方 + 发送方多端同步
89 - ImPrivateMessageDTO websocketMessage = ImPrivateMessageDTO.ofSend(message);  
90 - imWebSocketService.sendPrivateMessageAsync(message.getReceiverId(), websocketMessage);  
91 - imWebSocketService.sendPrivateMessageAsync(senderId, websocketMessage); 106 + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message);
  107 + imWebSocketService.sendNotificationAsync(message.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(),
  108 + notification.getType(), notification);
  109 + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(),
  110 + notification.getType(), notification);
92 return message; 111 return message;
93 } 112 }
94 113
@@ -103,24 +122,39 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -103,24 +122,39 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
103 ImPrivateMessageDO message = new ImPrivateMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) 122 ImPrivateMessageDO message = new ImPrivateMessageDO().setClientMessageId(IdUtil.fastSimpleUUID())
104 .setSenderId(senderId).setReceiverId(dto.getReceiverId()) 123 .setSenderId(senderId).setReceiverId(dto.getReceiverId())
105 .setType(dto.getType()).setContent(contentString) 124 .setType(dto.getType()).setContent(contentString)
106 - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()); 125 + .setStatus(ImMessageStatusEnum.NORMAL.getStatus())
  126 + .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())).setSendTime(LocalDateTime.now());
107 // 1.3 决定是否持久化:dto.persistent 优先;为 null 时按 type 默认 127 // 1.3 决定是否持久化:dto.persistent 优先;为 null 时按 type 默认
108 boolean persistent = dto.getPersistent() != null 128 boolean persistent = dto.getPersistent() != null
109 ? dto.getPersistent() 129 ? dto.getPersistent()
110 - : ImMessageTypeEnum.validate(dto.getType()).isPersistent(); 130 + : ImContentTypeEnum.validate(dto.getType()).isPersistent();
111 if (persistent) { 131 if (persistent) {
112 privateMessageMapper.insert(message); 132 privateMessageMapper.insert(message);
113 } 133 }
114 134
115 // 2. WebSocket 异步推送:双向(默认);单边语义(persistent=false)下仅推 sender 多端,对方不感知 135 // 2. WebSocket 异步推送:双向(默认);单边语义(persistent=false)下仅推 sender 多端,对方不感知
116 - ImPrivateMessageDTO websocketMessage = ImPrivateMessageDTO.ofSend(message); 136 + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message);
117 if (persistent) { 137 if (persistent) {
118 - imWebSocketService.sendPrivateMessageAsync(dto.getReceiverId(), websocketMessage); 138 + imWebSocketService.sendNotificationAsync(dto.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(),
  139 + notification.getType(), notification);
119 } 140 }
120 - imWebSocketService.sendPrivateMessageAsync(senderId, websocketMessage); 141 + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(),
  142 + notification.getType(), notification);
121 return message; 143 return message;
122 } 144 }
123 145
  146 + /**
  147 + * 计算私聊消息回执 status:私聊已读关闭时强制 NO_RECEIPT,忽略发送方传入的 receipt(receipt 为 null 等价 false)
  148 + */
  149 + private Integer resolveReceiptStatus(Boolean receipt) {
  150 + if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) {
  151 + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus();
  152 + }
  153 + return BooleanUtil.isTrue(receipt)
  154 + ? ImMessageReceiptStatusEnum.PENDING.getStatus()
  155 + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus();
  156 + }
  157 +
124 @Override 158 @Override
125 @Transactional(rollbackFor = Exception.class) 159 @Transactional(rollbackFor = Exception.class)
126 public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) { 160 public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) {
@@ -149,7 +183,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -149,7 +183,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
149 183
150 // 3. 发送撤回事件 184 // 3. 发送撤回事件
151 return sendPrivateMessage(userId, new ImPrivateMessageSendDTO().setReceiverId(message.getReceiverId()) 185 return sendPrivateMessage(userId, new ImPrivateMessageSendDTO().setReceiverId(message.getReceiverId())
152 - .setType(ImMessageTypeEnum.RECALL.getType()).setContent(new RecallMessage().setMessageId(messageId))); 186 + .setType(ImContentTypeEnum.RECALL.getType()).setContent(new RecallMessage().setMessageId(messageId)));
153 } 187 }
154 188
155 /** 189 /**
@@ -211,20 +245,26 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -211,20 +245,26 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
211 throw exception(MESSAGE_PRIVATE_READ_DISABLED); 245 throw exception(MESSAGE_PRIVATE_READ_DISABLED);
212 } 246 }
213 Assert.notNull(messageId, "已读消息编号不能为空"); 247 Assert.notNull(messageId, "已读消息编号不能为空");
214 - // 2. 把 (receiverId → userId) 这条会话上、id <= messageId 的未读消息一步更新为已读  
215 - // 仅 UNREAD 行被命中,避免覆盖已撤回/已读的状态;select-then-update 合成单条 SQL 后也消除了竞态窗口  
216 - int updated = privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus(  
217 - receiverId, userId, messageId, ImMessageStatusEnum.UNREAD.getStatus(),  
218 - new ImPrivateMessageDO().setStatus(ImMessageStatusEnum.READ.getStatus()));  
219 - if (updated == 0) { 248 +
  249 + // 2. 回执置 DONE:把 (receiverId → userId) 上 id <= messageId、待回执(PENDING) 的消息标记为已完成
  250 + // status 不再表达已读(保持 NORMAL);只翻 PENDING 行,避免覆盖 NO_RECEIPT / 已 DONE
  251 + privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(
  252 + receiverId, userId, messageId, ImMessageReceiptStatusEnum.PENDING.getStatus(),
  253 + new ImPrivateMessageDO().setReceiptStatus(ImMessageReceiptStatusEnum.DONE.getStatus()));
  254 +
  255 + // 3. 同步写 im_conversation_read(读位置唯一权威,单调递增);读位置前进才下发事件
  256 + boolean advanced = conversationReadService.updateConversationReadPosition(
  257 + userId, ImConversationTypeEnum.PRIVATE.getType(), receiverId, messageId);
  258 + if (!advanced) {
220 return; 259 return;
221 } 260 }
222 261
223 - // 3. 异步发送 READ + RECEIPT 事件(已读位置以前端上报为准,与多端 / 对方 UI 显示一致)  
224 - imWebSocketService.sendPrivateMessageAsync(userId,  
225 - ImPrivateMessageDTO.ofRead(userId, receiverId, messageId));  
226 - imWebSocketService.sendPrivateMessageAsync(receiverId,  
227 - ImPrivateMessageDTO.ofReceipt(userId, receiverId, messageId)); 262 + // 4. 异步发送 READ + RECEIPT 事件(已读位置以前端上报为准,与多端 / 对方 UI 显示一致)
  263 + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.PRIVATE.getType(),
  264 + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofPrivate(userId, receiverId, messageId));
  265 + imWebSocketService.sendNotificationAsync(receiverId, ImConversationTypeEnum.PRIVATE.getType(),
  266 + ImContentTypeEnum.RECEIPT.getType(),
  267 + ImMessageReceiptNotification.ofPrivate(userId, receiverId, messageId));
228 } 268 }
229 269
230 @Override 270 @Override
@@ -232,8 +272,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @@ -232,8 +272,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
232 if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { 272 if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) {
233 throw exception(MESSAGE_PRIVATE_READ_DISABLED); 273 throw exception(MESSAGE_PRIVATE_READ_DISABLED);
234 } 274 }
235 - return privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus(  
236 - userId, peerId, ImMessageStatusEnum.READ.getStatus()); 275 + // 对端 peer 在「与 userId 的会话」里的读位置 = peer 把 userId 发的消息读到哪
  276 + return conversationReadService.getConversationReadMessageId(
  277 + peerId, ImConversationTypeEnum.PRIVATE.getType(), userId);
237 } 278 }
238 279
239 @Override 280 @Override
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java
1 package cn.iocoder.yudao.module.im.service.message.dto; 1 package cn.iocoder.yudao.module.im.service.message.dto;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
4 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;  
5 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.*; 4 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  5 +import cn.iocoder.yudao.module.im.service.websocket.notification.group.*;
6 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
7 import lombok.Data; 7 import lombok.Data;
8 8
@@ -26,7 +26,7 @@ public class ImGroupMessageSendDTO { @@ -26,7 +26,7 @@ public class ImGroupMessageSendDTO {
26 /** 26 /**
27 * 消息类型 27 * 消息类型
28 * <p> 28 * <p>
29 - * 枚举 {@link ImMessageTypeEnum} 29 + * 枚举 {@link ImContentTypeEnum}
30 */ 30 */
31 @NotNull(message = "消息类型不能为空") 31 @NotNull(message = "消息类型不能为空")
32 private Integer type; 32 private Integer type;
@@ -53,30 +53,34 @@ public class ImGroupMessageSendDTO { @@ -53,30 +53,34 @@ public class ImGroupMessageSendDTO {
53 */ 53 */
54 private Boolean receipt; 54 private Boolean receipt;
55 55
56 - // ========== 群广播事件静态工厂(对应 ImMessageTypeEnum 群事件) ========== 56 + // ========== 群广播事件静态工厂(对应 ImContentTypeEnum 群事件) ==========
57 57
58 public static ImGroupMessageSendDTO ofGroupCreate(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) { 58 public static ImGroupMessageSendDTO ofGroupCreate(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) {
59 GroupCreateNotification notification = new GroupCreateNotification(); 59 GroupCreateNotification notification = new GroupCreateNotification();
60 notification.setOperatorUserId(operatorUserId); 60 notification.setOperatorUserId(operatorUserId);
61 notification.setMemberUserIds(new ArrayList<>(memberUserIds)); 61 notification.setMemberUserIds(new ArrayList<>(memberUserIds));
62 return new ImGroupMessageSendDTO().setGroupId(groupId) 62 return new ImGroupMessageSendDTO().setGroupId(groupId)
63 - .setType(ImMessageTypeEnum.GROUP_CREATE.getType()).setContent(notification); 63 + .setType(ImContentTypeEnum.GROUP_CREATE.getType()).setContent(notification);
64 } 64 }
65 65
66 - public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId, String oldAvatar, String newAvatar) { 66 + public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId,
  67 + String oldAvatar, String newAvatar,
  68 + Boolean oldJoinApproval, Boolean newJoinApproval) {
67 GroupInfoUpdateNotification notification = new GroupInfoUpdateNotification(); 69 GroupInfoUpdateNotification notification = new GroupInfoUpdateNotification();
68 notification.setOperatorUserId(operatorUserId); 70 notification.setOperatorUserId(operatorUserId);
69 notification.setOldAvatar(oldAvatar); 71 notification.setOldAvatar(oldAvatar);
70 notification.setNewAvatar(newAvatar); 72 notification.setNewAvatar(newAvatar);
  73 + notification.setOldJoinApproval(oldJoinApproval);
  74 + notification.setNewJoinApproval(newJoinApproval);
71 return new ImGroupMessageSendDTO().setGroupId(groupId) 75 return new ImGroupMessageSendDTO().setGroupId(groupId)
72 - .setType(ImMessageTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification); 76 + .setType(ImContentTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification);
73 } 77 }
74 78
75 public static ImGroupMessageSendDTO ofGroupMemberQuit(Long groupId, Long operatorUserId) { 79 public static ImGroupMessageSendDTO ofGroupMemberQuit(Long groupId, Long operatorUserId) {
76 GroupMemberQuitNotification notification = new GroupMemberQuitNotification(); 80 GroupMemberQuitNotification notification = new GroupMemberQuitNotification();
77 notification.setOperatorUserId(operatorUserId); 81 notification.setOperatorUserId(operatorUserId);
78 return new ImGroupMessageSendDTO().setGroupId(groupId) 82 return new ImGroupMessageSendDTO().setGroupId(groupId)
79 - .setType(ImMessageTypeEnum.GROUP_MEMBER_QUIT.getType()).setContent(notification); 83 + .setType(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType()).setContent(notification);
80 } 84 }
81 85
82 public static ImGroupMessageSendDTO ofGroupOwnerTransfer(Long groupId, Long operatorUserId, Long newOwnerUserId) { 86 public static ImGroupMessageSendDTO ofGroupOwnerTransfer(Long groupId, Long operatorUserId, Long newOwnerUserId) {
@@ -84,7 +88,7 @@ public class ImGroupMessageSendDTO { @@ -84,7 +88,7 @@ public class ImGroupMessageSendDTO {
84 notification.setOperatorUserId(operatorUserId); 88 notification.setOperatorUserId(operatorUserId);
85 notification.setNewOwnerUserId(newOwnerUserId); 89 notification.setNewOwnerUserId(newOwnerUserId);
86 return new ImGroupMessageSendDTO().setGroupId(groupId) 90 return new ImGroupMessageSendDTO().setGroupId(groupId)
87 - .setType(ImMessageTypeEnum.GROUP_OWNER_TRANSFER.getType()).setContent(notification); 91 + .setType(ImContentTypeEnum.GROUP_OWNER_TRANSFER.getType()).setContent(notification);
88 } 92 }
89 93
90 public static ImGroupMessageSendDTO ofGroupMemberKick(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) { 94 public static ImGroupMessageSendDTO ofGroupMemberKick(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) {
@@ -92,7 +96,7 @@ public class ImGroupMessageSendDTO { @@ -92,7 +96,7 @@ public class ImGroupMessageSendDTO {
92 notification.setOperatorUserId(operatorUserId); 96 notification.setOperatorUserId(operatorUserId);
93 notification.setMemberUserIds(new ArrayList<>(memberUserIds)); 97 notification.setMemberUserIds(new ArrayList<>(memberUserIds));
94 return new ImGroupMessageSendDTO().setGroupId(groupId) 98 return new ImGroupMessageSendDTO().setGroupId(groupId)
95 - .setType(ImMessageTypeEnum.GROUP_MEMBER_KICK.getType()).setContent(notification); 99 + .setType(ImContentTypeEnum.GROUP_MEMBER_KICK.getType()).setContent(notification);
96 } 100 }
97 101
98 public static ImGroupMessageSendDTO ofGroupMemberInvite(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) { 102 public static ImGroupMessageSendDTO ofGroupMemberInvite(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) {
@@ -100,7 +104,7 @@ public class ImGroupMessageSendDTO { @@ -100,7 +104,7 @@ public class ImGroupMessageSendDTO {
100 notification.setOperatorUserId(operatorUserId); 104 notification.setOperatorUserId(operatorUserId);
101 notification.setMemberUserIds(new ArrayList<>(memberUserIds)); 105 notification.setMemberUserIds(new ArrayList<>(memberUserIds));
102 return new ImGroupMessageSendDTO().setGroupId(groupId) 106 return new ImGroupMessageSendDTO().setGroupId(groupId)
103 - .setType(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType()).setContent(notification); 107 + .setType(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType()).setContent(notification);
104 } 108 }
105 109
106 public static ImGroupMessageSendDTO ofGroupMemberEnter(Long groupId, Long entrantUserId, Integer addSource) { 110 public static ImGroupMessageSendDTO ofGroupMemberEnter(Long groupId, Long entrantUserId, Integer addSource) {
@@ -109,14 +113,14 @@ public class ImGroupMessageSendDTO { @@ -109,14 +113,14 @@ public class ImGroupMessageSendDTO {
109 notification.setEntrantUserId(entrantUserId); 113 notification.setEntrantUserId(entrantUserId);
110 notification.setAddSource(addSource); 114 notification.setAddSource(addSource);
111 return new ImGroupMessageSendDTO().setGroupId(groupId) 115 return new ImGroupMessageSendDTO().setGroupId(groupId)
112 - .setType(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType()).setContent(notification); 116 + .setType(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType()).setContent(notification);
113 } 117 }
114 118
115 public static ImGroupMessageSendDTO ofGroupDissolve(Long groupId, Long operatorUserId) { 119 public static ImGroupMessageSendDTO ofGroupDissolve(Long groupId, Long operatorUserId) {
116 GroupDissolveNotification notification = new GroupDissolveNotification(); 120 GroupDissolveNotification notification = new GroupDissolveNotification();
117 notification.setOperatorUserId(operatorUserId); 121 notification.setOperatorUserId(operatorUserId);
118 return new ImGroupMessageSendDTO().setGroupId(groupId) 122 return new ImGroupMessageSendDTO().setGroupId(groupId)
119 - .setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()).setContent(notification); 123 + .setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent(notification);
120 } 124 }
121 125
122 public static ImGroupMessageSendDTO ofGroupMemberNicknameUpdate(Long groupId, Long operatorUserId, String displayUserName) { 126 public static ImGroupMessageSendDTO ofGroupMemberNicknameUpdate(Long groupId, Long operatorUserId, String displayUserName) {
@@ -124,7 +128,7 @@ public class ImGroupMessageSendDTO { @@ -124,7 +128,7 @@ public class ImGroupMessageSendDTO {
124 notification.setOperatorUserId(operatorUserId); 128 notification.setOperatorUserId(operatorUserId);
125 notification.setDisplayUserName(displayUserName); 129 notification.setDisplayUserName(displayUserName);
126 return new ImGroupMessageSendDTO().setGroupId(groupId) 130 return new ImGroupMessageSendDTO().setGroupId(groupId)
127 - .setType(ImMessageTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()).setContent(notification); 131 + .setType(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()).setContent(notification);
128 } 132 }
129 133
130 public static ImGroupMessageSendDTO ofGroupAdminAdd(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) { 134 public static ImGroupMessageSendDTO ofGroupAdminAdd(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) {
@@ -132,7 +136,7 @@ public class ImGroupMessageSendDTO { @@ -132,7 +136,7 @@ public class ImGroupMessageSendDTO {
132 notification.setOperatorUserId(operatorUserId); 136 notification.setOperatorUserId(operatorUserId);
133 notification.setMemberUserIds(new ArrayList<>(memberUserIds)); 137 notification.setMemberUserIds(new ArrayList<>(memberUserIds));
134 return new ImGroupMessageSendDTO().setGroupId(groupId) 138 return new ImGroupMessageSendDTO().setGroupId(groupId)
135 - .setType(ImMessageTypeEnum.GROUP_ADMIN_ADD.getType()).setContent(notification); 139 + .setType(ImContentTypeEnum.GROUP_ADMIN_ADD.getType()).setContent(notification);
136 } 140 }
137 141
138 public static ImGroupMessageSendDTO ofGroupAdminRemove(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) { 142 public static ImGroupMessageSendDTO ofGroupAdminRemove(Long groupId, Long operatorUserId, Collection<Long> memberUserIds) {
@@ -140,7 +144,7 @@ public class ImGroupMessageSendDTO { @@ -140,7 +144,7 @@ public class ImGroupMessageSendDTO {
140 notification.setOperatorUserId(operatorUserId); 144 notification.setOperatorUserId(operatorUserId);
141 notification.setMemberUserIds(new ArrayList<>(memberUserIds)); 145 notification.setMemberUserIds(new ArrayList<>(memberUserIds));
142 return new ImGroupMessageSendDTO().setGroupId(groupId) 146 return new ImGroupMessageSendDTO().setGroupId(groupId)
143 - .setType(ImMessageTypeEnum.GROUP_ADMIN_REMOVE.getType()).setContent(notification); 147 + .setType(ImContentTypeEnum.GROUP_ADMIN_REMOVE.getType()).setContent(notification);
144 } 148 }
145 149
146 public static ImGroupMessageSendDTO ofGroupNoticeUpdate(Long groupId, Long operatorUserId, String oldNotice, String newNotice) { 150 public static ImGroupMessageSendDTO ofGroupNoticeUpdate(Long groupId, Long operatorUserId, String oldNotice, String newNotice) {
@@ -148,7 +152,7 @@ public class ImGroupMessageSendDTO { @@ -148,7 +152,7 @@ public class ImGroupMessageSendDTO {
148 notification.setOperatorUserId(operatorUserId); 152 notification.setOperatorUserId(operatorUserId);
149 notification.setOldNotice(oldNotice).setNewNotice(newNotice); 153 notification.setOldNotice(oldNotice).setNewNotice(newNotice);
150 return new ImGroupMessageSendDTO().setGroupId(groupId) 154 return new ImGroupMessageSendDTO().setGroupId(groupId)
151 - .setType(ImMessageTypeEnum.GROUP_NOTICE_UPDATE.getType()).setContent(notification); 155 + .setType(ImContentTypeEnum.GROUP_NOTICE_UPDATE.getType()).setContent(notification);
152 } 156 }
153 157
154 public static ImGroupMessageSendDTO ofGroupNameUpdate(Long groupId, Long operatorUserId, String oldName, String newName) { 158 public static ImGroupMessageSendDTO ofGroupNameUpdate(Long groupId, Long operatorUserId, String oldName, String newName) {
@@ -156,7 +160,7 @@ public class ImGroupMessageSendDTO { @@ -156,7 +160,7 @@ public class ImGroupMessageSendDTO {
156 notification.setOperatorUserId(operatorUserId); 160 notification.setOperatorUserId(operatorUserId);
157 notification.setOldName(oldName).setNewName(newName); 161 notification.setOldName(oldName).setNewName(newName);
158 return new ImGroupMessageSendDTO().setGroupId(groupId) 162 return new ImGroupMessageSendDTO().setGroupId(groupId)
159 - .setType(ImMessageTypeEnum.GROUP_NAME_UPDATE.getType()).setContent(notification); 163 + .setType(ImContentTypeEnum.GROUP_NAME_UPDATE.getType()).setContent(notification);
160 } 164 }
161 165
162 public static ImGroupMessageSendDTO ofGroupMemberSettingUpdate(Long groupId, Long operatorUserId, Boolean silent, String groupRemark) { 166 public static ImGroupMessageSendDTO ofGroupMemberSettingUpdate(Long groupId, Long operatorUserId, Boolean silent, String groupRemark) {
@@ -164,7 +168,7 @@ public class ImGroupMessageSendDTO { @@ -164,7 +168,7 @@ public class ImGroupMessageSendDTO {
164 notification.setOperatorUserId(operatorUserId); 168 notification.setOperatorUserId(operatorUserId);
165 notification.setSilent(silent).setGroupRemark(groupRemark); 169 notification.setSilent(silent).setGroupRemark(groupRemark);
166 return new ImGroupMessageSendDTO().setGroupId(groupId) 170 return new ImGroupMessageSendDTO().setGroupId(groupId)
167 - .setType(ImMessageTypeEnum.GROUP_MEMBER_SETTING_UPDATE.getType()).setContent(notification); 171 + .setType(ImContentTypeEnum.GROUP_MEMBER_SETTING_UPDATE.getType()).setContent(notification);
168 } 172 }
169 173
170 public static ImGroupMessageSendDTO ofGroupMessagePin(Long groupId, Long operatorUserId, ImGroupMessageDO message) { 174 public static ImGroupMessageSendDTO ofGroupMessagePin(Long groupId, Long operatorUserId, ImGroupMessageDO message) {
@@ -176,7 +180,7 @@ public class ImGroupMessageSendDTO { @@ -176,7 +180,7 @@ public class ImGroupMessageSendDTO {
176 notification.setOperatorUserId(operatorUserId); 180 notification.setOperatorUserId(operatorUserId);
177 notification.setMessageId(message.getId()).setMessage(pinnedMessage); 181 notification.setMessageId(message.getId()).setMessage(pinnedMessage);
178 return new ImGroupMessageSendDTO().setGroupId(groupId) 182 return new ImGroupMessageSendDTO().setGroupId(groupId)
179 - .setType(ImMessageTypeEnum.GROUP_MESSAGE_PIN.getType()).setContent(notification); 183 + .setType(ImContentTypeEnum.GROUP_MESSAGE_PIN.getType()).setContent(notification);
180 } 184 }
181 185
182 public static ImGroupMessageSendDTO ofGroupMessageUnpin(Long groupId, Long operatorUserId, Long messageId) { 186 public static ImGroupMessageSendDTO ofGroupMessageUnpin(Long groupId, Long operatorUserId, Long messageId) {
@@ -184,7 +188,7 @@ public class ImGroupMessageSendDTO { @@ -184,7 +188,7 @@ public class ImGroupMessageSendDTO {
184 notification.setOperatorUserId(operatorUserId); 188 notification.setOperatorUserId(operatorUserId);
185 notification.setMessageId(messageId); 189 notification.setMessageId(messageId);
186 return new ImGroupMessageSendDTO().setGroupId(groupId) 190 return new ImGroupMessageSendDTO().setGroupId(groupId)
187 - .setType(ImMessageTypeEnum.GROUP_MESSAGE_UNPIN.getType()).setContent(notification); 191 + .setType(ImContentTypeEnum.GROUP_MESSAGE_UNPIN.getType()).setContent(notification);
188 } 192 }
189 193
190 public static ImGroupMessageSendDTO ofGroupMemberMuted(Long groupId, Long operatorUserId, 194 public static ImGroupMessageSendDTO ofGroupMemberMuted(Long groupId, Long operatorUserId,
@@ -195,7 +199,7 @@ public class ImGroupMessageSendDTO { @@ -195,7 +199,7 @@ public class ImGroupMessageSendDTO {
195 notification.setMutedUserId(mutedUserId); 199 notification.setMutedUserId(mutedUserId);
196 notification.setMuteEndTime(muteEndTime); 200 notification.setMuteEndTime(muteEndTime);
197 return new ImGroupMessageSendDTO().setGroupId(groupId) 201 return new ImGroupMessageSendDTO().setGroupId(groupId)
198 - .setType(ImMessageTypeEnum.GROUP_MEMBER_MUTED.getType()).setContent(notification); 202 + .setType(ImContentTypeEnum.GROUP_MEMBER_MUTED.getType()).setContent(notification);
199 } 203 }
200 204
201 public static ImGroupMessageSendDTO ofGroupMemberCancelMuted(Long groupId, Long operatorUserId, Long mutedUserId) { 205 public static ImGroupMessageSendDTO ofGroupMemberCancelMuted(Long groupId, Long operatorUserId, Long mutedUserId) {
@@ -203,21 +207,21 @@ public class ImGroupMessageSendDTO { @@ -203,21 +207,21 @@ public class ImGroupMessageSendDTO {
203 notification.setOperatorUserId(operatorUserId); 207 notification.setOperatorUserId(operatorUserId);
204 notification.setMutedUserId(mutedUserId); 208 notification.setMutedUserId(mutedUserId);
205 return new ImGroupMessageSendDTO().setGroupId(groupId) 209 return new ImGroupMessageSendDTO().setGroupId(groupId)
206 - .setType(ImMessageTypeEnum.GROUP_MEMBER_CANCEL_MUTED.getType()).setContent(notification); 210 + .setType(ImContentTypeEnum.GROUP_MEMBER_CANCEL_MUTED.getType()).setContent(notification);
207 } 211 }
208 212
209 public static ImGroupMessageSendDTO ofGroupMuted(Long groupId, Long operatorUserId) { 213 public static ImGroupMessageSendDTO ofGroupMuted(Long groupId, Long operatorUserId) {
210 GroupMutedNotification notification = new GroupMutedNotification(); 214 GroupMutedNotification notification = new GroupMutedNotification();
211 notification.setOperatorUserId(operatorUserId); 215 notification.setOperatorUserId(operatorUserId);
212 return new ImGroupMessageSendDTO().setGroupId(groupId) 216 return new ImGroupMessageSendDTO().setGroupId(groupId)
213 - .setType(ImMessageTypeEnum.GROUP_MUTED.getType()).setContent(notification); 217 + .setType(ImContentTypeEnum.GROUP_MUTED.getType()).setContent(notification);
214 } 218 }
215 219
216 public static ImGroupMessageSendDTO ofGroupCancelMuted(Long groupId, Long operatorUserId) { 220 public static ImGroupMessageSendDTO ofGroupCancelMuted(Long groupId, Long operatorUserId) {
217 GroupCancelMutedNotification notification = new GroupCancelMutedNotification(); 221 GroupCancelMutedNotification notification = new GroupCancelMutedNotification();
218 notification.setOperatorUserId(operatorUserId); 222 notification.setOperatorUserId(operatorUserId);
219 return new ImGroupMessageSendDTO().setGroupId(groupId) 223 return new ImGroupMessageSendDTO().setGroupId(groupId)
220 - .setType(ImMessageTypeEnum.GROUP_CANCEL_MUTED.getType()).setContent(notification); 224 + .setType(ImContentTypeEnum.GROUP_CANCEL_MUTED.getType()).setContent(notification);
221 } 225 }
222 226
223 public static ImGroupMessageSendDTO ofGroupBanned(Long groupId, Long operatorUserId, boolean banned) { 227 public static ImGroupMessageSendDTO ofGroupBanned(Long groupId, Long operatorUserId, boolean banned) {
@@ -225,7 +229,7 @@ public class ImGroupMessageSendDTO { @@ -225,7 +229,7 @@ public class ImGroupMessageSendDTO {
225 notification.setOperatorUserId(operatorUserId); 229 notification.setOperatorUserId(operatorUserId);
226 notification.setBanned(banned); 230 notification.setBanned(banned);
227 return new ImGroupMessageSendDTO().setGroupId(groupId) 231 return new ImGroupMessageSendDTO().setGroupId(groupId)
228 - .setType(ImMessageTypeEnum.GROUP_BANNED.getType()).setContent(notification); 232 + .setType(ImContentTypeEnum.GROUP_BANNED.getType()).setContent(notification);
229 } 233 }
230 234
231 } 235 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java
1 package cn.iocoder.yudao.module.im.service.message.dto; 1 package cn.iocoder.yudao.module.im.service.message.dto;
2 2
3 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 3 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 /** 6 /**
@@ -18,7 +18,7 @@ public class ImPrivateMessageSendDTO { @@ -18,7 +18,7 @@ public class ImPrivateMessageSendDTO {
18 /** 18 /**
19 * 消息类型 19 * 消息类型
20 * <p> 20 * <p>
21 - * 枚举 {@link ImMessageTypeEnum} 21 + * 枚举 {@link ImContentTypeEnum}
22 */ 22 */
23 private Integer type; 23 private Integer type;
24 /** 24 /**
@@ -30,10 +30,14 @@ public class ImPrivateMessageSendDTO { @@ -30,10 +30,14 @@ public class ImPrivateMessageSendDTO {
30 /** 30 /**
31 * 是否持久化 + 推送给接收方(单边语义开关) 31 * 是否持久化 + 推送给接收方(单边语义开关)
32 * <p> 32 * <p>
33 - * null:默认按 {@link ImMessageTypeEnum#isPersistent()} 决定是否入库 + 双向 WS 推送(保持原行为)<br> 33 + * null:默认按 {@link ImContentTypeEnum#isPersistent()} 决定是否入库 + 双向 WS 推送(保持原行为)<br>
34 * false:覆盖为单边——不入库、仅推 sender 多端,对方不感知(如「你已删除 XXX」这类仅自己可见的 TIP)<br> 34 * false:覆盖为单边——不入库、仅推 sender 多端,对方不感知(如「你已删除 XXX」这类仅自己可见的 TIP)<br>
35 * true:覆盖为双向 + 入库 35 * true:覆盖为双向 + 入库
36 */ 36 */
37 private Boolean persistent; 37 private Boolean persistent;
  38 + /**
  39 + * 是否需要回执;null 等价 false(系统消息默认不需要回执)
  40 + */
  41 + private Boolean receipt;
38 42
39 } 43 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java
@@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcCallMapper; @@ -15,7 +15,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcCallMapper;
15 import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcParticipantMapper; 15 import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcParticipantMapper;
16 import cn.iocoder.yudao.module.im.dal.redis.rtc.ImRtcCallLockRedisDAO; 16 import cn.iocoder.yudao.module.im.dal.redis.rtc.ImRtcCallLockRedisDAO;
17 import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; 17 import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
18 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 18 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
19 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; 19 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum;
20 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; 20 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum;
21 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum; 21 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum;
@@ -30,8 +30,7 @@ import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; @@ -30,8 +30,7 @@ import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
30 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 30 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
31 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 31 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
32 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 32 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
33 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
34 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc.*; 33 +import cn.iocoder.yudao.module.im.service.websocket.notification.rtc.*;
35 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 34 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
36 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 35 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
37 import jakarta.annotation.Resource; 36 import jakarta.annotation.Resource;
@@ -57,7 +56,7 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @@ -57,7 +56,7 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
57 * 并发幂等:同好友对 / 同群活跃唯一性走 {@link ImRtcCallLockRedisDAO} 分布式锁 + 锁内 SELECT 兜底;webhook 兜底走条件 UPDATE; 56 * 并发幂等:同好友对 / 同群活跃唯一性走 {@link ImRtcCallLockRedisDAO} 分布式锁 + 锁内 SELECT 兜底;webhook 兜底走条件 UPDATE;
58 * <p> 57 * <p>
59 * 推送通道分流: 58 * 推送通道分流:
60 - * 1601 RTC_CALL(INVITING / JOINED / REJECTED / NO_ANSWER / LEFT 子类型)→ {@link ImWebSocketService#sendPrivateMessageAsync} 仅推参与方; 59 + * 1601 RTC_CALL(INVITING / JOINED / REJECTED / NO_ANSWER / LEFT 子类型)→ {@link ImWebSocketService#sendNotificationAsync} 仅推参与方;
61 * 1602 / 1603 PARTICIPANT_CONNECTED / DISCONNECTED → {@link ImWebSocketService} 推参与方 + 群通话场景广播全群; 60 * 1602 / 1603 PARTICIPANT_CONNECTED / DISCONNECTED → {@link ImWebSocketService} 推参与方 + 群通话场景广播全群;
62 * 1610 RTC_CALL_START + 1611 RTC_CALL_END → {@link ImPrivateMessageService} / {@link ImGroupMessageService} 入消息流当聊天 tip 61 * 1610 RTC_CALL_START + 1611 RTC_CALL_END → {@link ImPrivateMessageService} / {@link ImGroupMessageService} 入消息流当聊天 tip
63 * (START 仅群通话;两者分别在 invite / cancel(leave) 事务里 INSERT,自增 id 自然保证顺序) 62 * (START 仅群通话;两者分别在 invite / cancel(leave) 事务里 INSERT,自增 id 自然保证顺序)
@@ -707,7 +706,6 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -707,7 +706,6 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
707 } 706 }
708 707
709 // 3.1 群通话:推 RTC_CALL(NO_ANSWER) 让前端 banner 移除该人 + 级联关房判定 708 // 3.1 群通话:推 RTC_CALL(NO_ANSWER) 让前端 banner 移除该人 + 级联关房判定
710 - // TODO DONE @AI:拆分独立 NO_ANSWER 信令,不再复用 REJECT  
711 if (ImConversationTypeEnum.isGroup(call.getConversationType())) { 709 if (ImConversationTypeEnum.isGroup(call.getConversationType())) {
712 pushCallNoAnswerNotification(call, userId, userMap.get(userId)); 710 pushCallNoAnswerNotification(call, userId, userMap.get(userId));
713 endSessionIfTerminal(call, userId); 711 endSessionIfTerminal(call, userId);
@@ -1003,8 +1001,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1003,8 +1001,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1003 String token = signToken(inviteeId, resolveDisplayName(invitee, inviteeId), call.getRoom()); 1001 String token = signToken(inviteeId, resolveDisplayName(invitee, inviteeId), call.getRoom());
1004 ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( 1002 ImRtcCallNotification payload = ImRtcCallNotification.ofInvite(
1005 call, inviter, imProperties.getRtc().getLivekitUrl(), token, inviteeIds); 1003 call, inviter, imProperties.getRtc().getLivekitUrl(), token, inviteeIds);
1006 - webSocketService.sendPrivateMessageAsync(inviteeId, ImPrivateMessageDTO.ofRtcNotification(  
1007 - ImMessageTypeEnum.RTC_CALL.getType(), call.getInviterUserId(), inviteeId, payload)); 1004 + webSocketService.sendNotificationAsync(inviteeId, ImConversationTypeEnum.NONE.getType(),
  1005 + ImContentTypeEnum.RTC_CALL.getType(), payload);
1008 } 1006 }
1009 1007
1010 /** 1008 /**
@@ -1019,8 +1017,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1019,8 +1017,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1019 AdminUserRespDTO operator = operatorUserId != null ? adminUserApi.getUser(operatorUserId) : null; 1017 AdminUserRespDTO operator = operatorUserId != null ? adminUserApi.getUser(operatorUserId) : null;
1020 ImRtcCallNotification payload = ImRtcCallNotification.ofReject(call, operatorUserId, operator); 1018 ImRtcCallNotification payload = ImRtcCallNotification.ofReject(call, operatorUserId, operator);
1021 for (Long receiverUserId : getCallAudienceUserIdList(call)) { 1019 for (Long receiverUserId : getCallAudienceUserIdList(call)) {
1022 - webSocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofRtcNotification(  
1023 - ImMessageTypeEnum.RTC_CALL.getType(), operatorUserId, receiverUserId, payload)); 1020 + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(),
  1021 + ImContentTypeEnum.RTC_CALL.getType(), payload);
1024 } 1022 }
1025 } 1023 }
1026 1024
@@ -1036,8 +1034,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1036,8 +1034,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1036 private void pushCallNoAnswerNotification(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) { 1034 private void pushCallNoAnswerNotification(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) {
1037 ImRtcCallNotification payload = ImRtcCallNotification.ofNoAnswer(call, operatorUserId, operator); 1035 ImRtcCallNotification payload = ImRtcCallNotification.ofNoAnswer(call, operatorUserId, operator);
1038 for (Long receiverUserId : getCallAudienceUserIdList(call)) { 1036 for (Long receiverUserId : getCallAudienceUserIdList(call)) {
1039 - webSocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofRtcNotification(  
1040 - ImMessageTypeEnum.RTC_CALL.getType(), operatorUserId, receiverUserId, payload)); 1037 + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(),
  1038 + ImContentTypeEnum.RTC_CALL.getType(), payload);
1041 } 1039 }
1042 } 1040 }
1043 1041
@@ -1048,7 +1046,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1048,7 +1046,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1048 * @param userId 加入的参与者用户编号 1046 * @param userId 加入的参与者用户编号
1049 */ 1047 */
1050 private void pushParticipantConnectedNotification(ImRtcCallDO call, Long userId) { 1048 private void pushParticipantConnectedNotification(ImRtcCallDO call, Long userId) {
1051 - pushParticipantNotification(call, ImMessageTypeEnum.RTC_PARTICIPANT_CONNECTED.getType(), userId, 1049 + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_CONNECTED.getType(), userId,
1052 ImRtcParticipantConnectedNotification.of(call, userId)); 1050 ImRtcParticipantConnectedNotification.of(call, userId));
1053 } 1051 }
1054 1052
@@ -1059,7 +1057,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1059,7 +1057,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1059 * @param userId 离开的参与者用户编号 1057 * @param userId 离开的参与者用户编号
1060 */ 1058 */
1061 private void pushParticipantDisconnectedNotification(ImRtcCallDO call, Long userId) { 1059 private void pushParticipantDisconnectedNotification(ImRtcCallDO call, Long userId) {
1062 - pushParticipantNotification(call, ImMessageTypeEnum.RTC_PARTICIPANT_DISCONNECTED.getType(), userId, 1060 + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_DISCONNECTED.getType(), userId,
1063 ImRtcParticipantDisconnectedNotification.of(call, userId)); 1061 ImRtcParticipantDisconnectedNotification.of(call, userId));
1064 } 1062 }
1065 1063
@@ -1076,8 +1074,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1076,8 +1074,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1076 if (CollUtil.isEmpty(receivers)) { 1074 if (CollUtil.isEmpty(receivers)) {
1077 return; 1075 return;
1078 } 1076 }
1079 - ImPrivateMessageDTO dto = ImPrivateMessageDTO.ofRtcNotification(type, actorUserId, null, payload);  
1080 - webSocketService.sendPrivateMessageAsync(receivers, dto); 1077 + webSocketService.sendNotificationAsync(receivers, ImConversationTypeEnum.NONE.getType(), type, payload);
1081 } 1078 }
1082 1079
1083 /** 1080 /**
@@ -1092,7 +1089,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1092,7 +1089,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1092 private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Set<Long> invitees) { 1089 private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Set<Long> invitees) {
1093 ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter); 1090 ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter);
1094 Long peerUserId = ImConversationTypeEnum.isGroup(call.getConversationType()) ? null : CollUtil.getFirst(invitees); 1091 Long peerUserId = ImConversationTypeEnum.isGroup(call.getConversationType()) ? null : CollUtil.getFirst(invitees);
1095 - pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_START, payload, peerUserId); 1092 + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_START, payload, peerUserId);
1096 } 1093 }
1097 1094
1098 /** 1095 /**
@@ -1116,7 +1113,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1116,7 +1113,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1116 p -> ObjUtil.notEqual(p.getUserId(), call.getInviterUserId())); 1113 p -> ObjUtil.notEqual(p.getUserId(), call.getInviterUserId()));
1117 peerUserId = peer != null ? peer.getUserId() : null; 1114 peerUserId = peer != null ? peer.getUserId() : null;
1118 } 1115 }
1119 - pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_END, payload, peerUserId); 1116 + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_END, payload, peerUserId);
1120 } 1117 }
1121 1118
1122 /** 1119 /**
@@ -1129,7 +1126,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { @@ -1129,7 +1126,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService {
1129 * @param payload 推送 payload 1126 * @param payload 推送 payload
1130 * @param peerUserId 私聊对端用户编号;群聊忽略,私聊缺失时回退为 senderId 1127 * @param peerUserId 私聊对端用户编号;群聊忽略,私聊缺失时回退为 senderId
1131 */ 1128 */
1132 - private void pushCallChatMessage(ImRtcCallDO call, ImMessageTypeEnum type, Object payload, Long peerUserId) { 1129 + private void pushCallChatMessage(ImRtcCallDO call, ImContentTypeEnum type, Object payload, Long peerUserId) {
1133 Long senderId = call.getInviterUserId(); 1130 Long senderId = call.getInviterUserId();
1134 if (ImConversationTypeEnum.isGroup(call.getConversationType())) { 1131 if (ImConversationTypeEnum.isGroup(call.getConversationType())) {
1135 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId()) 1132 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId())
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java
@@ -80,7 +80,7 @@ public interface ImStatisticsManagerService { @@ -80,7 +80,7 @@ public interface ImStatisticsManagerService {
80 Map<LocalDateTime, Long> getGroupMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); 80 Map<LocalDateTime, Long> getGroupMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime);
81 81
82 /** 82 /**
83 - * 获取区间内消息类型分布 Map(key 为消息类型) 83 + * 获取区间内内容类型分布 Map(key 为消息类型)
84 */ 84 */
85 Map<Integer, Long> getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime); 85 Map<Integer, Long> getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime);
86 86
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java
1 package cn.iocoder.yudao.module.im.service.websocket; 1 package cn.iocoder.yudao.module.im.service.websocket;
2 2
3 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO;  
4 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;  
5 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
6 -  
7 import java.util.Collection; 3 import java.util.Collection;
8 import java.util.Collections; 4 import java.util.Collections;
9 5
10 /** 6 /**
11 * IM WebSocket 推送 Service 接口 7 * IM WebSocket 推送 Service 接口
12 * <p> 8 * <p>
13 - * 统一封装 WebSocket 消息推送,事务内调用时提交后异步执行。 9 + * 统一封装 WebSocket 通知推送,事务内调用时提交后异步执行。
14 * 10 *
15 * @author 芋道源码 11 * @author 芋道源码
16 */ 12 */
17 public interface ImWebSocketService { 13 public interface ImWebSocketService {
18 14
19 /** 15 /**
20 - * 异步推送私聊消息给指定用户  
21 - *  
22 - * @param userId 目标用户编号  
23 - * @param dto 私聊消息 DTO  
24 - */  
25 - default void sendPrivateMessageAsync(Long userId, ImPrivateMessageDTO dto) {  
26 - sendPrivateMessageAsync(Collections.singleton(userId), dto);  
27 - }  
28 -  
29 - /**  
30 - * 异步批量推送私聊消息给多个用户;用于同一份 DTO 扇出到多个收件人  
31 - * <p>  
32 - * 相比逐个发送,仅注册一次 afterCommit 回调和异步任务。  
33 - *  
34 - * @param userIds 目标用户编号列表  
35 - * @param dto 私聊消息 DTO  
36 - */  
37 - void sendPrivateMessageAsync(Collection<Long> userIds, ImPrivateMessageDTO dto);  
38 -  
39 - /**  
40 - * 异步推送群聊消息给指定用户  
41 - *  
42 - * @param userId 目标用户编号  
43 - * @param dto 群聊消息 DTO  
44 - */  
45 - default void sendGroupMessageAsync(Long userId, ImGroupMessageDTO dto) {  
46 - sendGroupMessageAsync(Collections.singleton(userId), dto);  
47 - }  
48 -  
49 - /**  
50 - * 异步批量推送群聊消息给多个用户  
51 - *  
52 - * @param userIds 目标用户编号列表  
53 - * @param dto 群聊消息 DTO  
54 - */  
55 - void sendGroupMessageAsync(Collection<Long> userIds, ImGroupMessageDTO dto);  
56 -  
57 - /**  
58 - * 异步推送频道消息给指定用户 16 + * 异步推送 WebSocket 通知给指定用户
59 * 17 *
60 - * @param userId 目标用户编号  
61 - * @param dto 频道消息 DTO 18 + * @param userId 目标用户编号
  19 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类
  20 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类
  21 + * @param payload 通知 payload
62 */ 22 */
63 - default void sendChannelMessageAsync(Long userId, ImChannelMessageDTO dto) {  
64 - sendChannelMessageAsync(Collections.singleton(userId), dto); 23 + default void sendNotificationAsync(Long userId, Integer conversationType, Integer contentType, Object payload) {
  24 + sendNotificationAsync(Collections.singleton(userId), conversationType, contentType, payload);
65 } 25 }
66 26
67 /** 27 /**
68 - * 异步批量推送频道消息给多个用户 28 + * 异步批量推送 WebSocket 通知给多个用户
69 * 29 *
70 - * @param userIds 目标用户编号列表  
71 - * @param dto 频道消息 DTO 30 + * @param userIds 目标用户编号列表
  31 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类
  32 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类
  33 + * @param payload 通知 payload
72 */ 34 */
73 - void sendChannelMessageAsync(Collection<Long> userIds, ImChannelMessageDTO dto); 35 + void sendNotificationAsync(Collection<Long> userIds, Integer conversationType, Integer contentType, Object payload);
74 36
75 /** 37 /**
76 - * 异步广播频道消息给当前所有在线管理端用户;用于全员推送 38 + * 异步广播 WebSocket 通知给当前所有在线管理端用户
77 * 39 *
78 - * @param dto 频道消息 DTO 40 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类
  41 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类
  42 + * @param payload 通知 payload
79 */ 43 */
80 - void broadcastChannelMessageAsync(ImChannelMessageDTO dto); 44 + void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload);
81 45
82 } 46 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java
@@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.im.service.websocket; @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.im.service.websocket;
2 2
3 import cn.hutool.extra.spring.SpringUtil; 3 import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
5 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO;  
6 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;  
7 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; 5 +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO;
8 import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; 6 import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
9 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
10 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
@@ -36,83 +34,51 @@ public class ImWebSocketServiceImpl implements ImWebSocketService { @@ -36,83 +34,51 @@ public class ImWebSocketServiceImpl implements ImWebSocketService {
36 private WebSocketSenderApi webSocketSenderApi; 34 private WebSocketSenderApi webSocketSenderApi;
37 35
38 @Override 36 @Override
39 - public void sendPrivateMessageAsync(Collection<Long> userIds, ImPrivateMessageDTO dto) {  
40 - // 说明:通过 executeAfterTransaction 保证事务提交后再推送,避免客户端收到消息后查询数据库时事务尚未提交  
41 - // 通过 getSelf() 获取 Spring 代理对象调用 @Async 方法,确保异步 AOP 生效(直接 this 调用会绕过代理)  
42 - executeAfterTransaction(() -> getSelf().doSendPrivateMessage(userIds, dto)); 37 + public void sendNotificationAsync(Collection<Long> userIds, Integer conversationType, Integer contentType,
  38 + Object payload) {
  39 + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload);
  40 + executeAfterTransaction(() -> getSelf().doSendNotification(userIds, notification));
43 } 41 }
44 42
45 @Override 43 @Override
46 - public void sendGroupMessageAsync(Collection<Long> userIds, ImGroupMessageDTO dto) {  
47 - executeAfterTransaction(() -> getSelf().doSendGroupMessage(userIds, dto)); 44 + public void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload) {
  45 + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload);
  46 + executeAfterTransaction(() -> getSelf().doBroadcastNotification(notification));
48 } 47 }
49 48
50 - @Override  
51 - public void sendChannelMessageAsync(Collection<Long> userIds, ImChannelMessageDTO dto) {  
52 - executeAfterTransaction(() -> getSelf().doSendChannelMessage(userIds, dto));  
53 - }  
54 -  
55 - @Override  
56 - public void broadcastChannelMessageAsync(ImChannelMessageDTO dto) {  
57 - executeAfterTransaction(() -> getSelf().doBroadcastChannelMessage(dto));  
58 - }  
59 -  
60 - /**  
61 - * 异步发送私聊 WebSocket 消息;多收件人共享同一 dto,避免按收件人重复注册 afterCommit 回调  
62 - */  
63 - @Async  
64 - public void doSendPrivateMessage(Collection<Long> userIds, ImPrivateMessageDTO dto) {  
65 - for (Long userId : getDistinctUserIds(userIds)) {  
66 - try {  
67 - webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId,  
68 - ImPrivateMessageDTO.TYPE, dto);  
69 - } catch (Exception e) {  
70 - log.error("[doSendPrivateMessage][userId({}) dto({}) 发送失败]", userId, dto, e);  
71 - }  
72 - }  
73 - }  
74 -  
75 - /**  
76 - * 异步发送群聊 WebSocket 消息  
77 - */  
78 - @Async  
79 - public void doSendGroupMessage(Collection<Long> userIds, ImGroupMessageDTO dto) {  
80 - for (Long userId : getDistinctUserIds(userIds)) {  
81 - try {  
82 - webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId,  
83 - ImGroupMessageDTO.TYPE, dto);  
84 - } catch (Exception e) {  
85 - log.error("[doSendGroupMessage][userId({}) dto({}) 发送失败]", userId, dto, e);  
86 - }  
87 - } 49 + private static ImNotificationWebSocketDTO buildNotification(Integer conversationType, Integer contentType,
  50 + Object payload) {
  51 + return new ImNotificationWebSocketDTO()
  52 + .setConversationType(conversationType)
  53 + .setContentType(contentType)
  54 + .setPayload(payload);
88 } 55 }
89 56
90 /** 57 /**
91 - * 异步发送频道 WebSocket 消息 58 + * 异步发送 WebSocket 通知
92 */ 59 */
93 @Async 60 @Async
94 - public void doSendChannelMessage(Collection<Long> userIds, ImChannelMessageDTO dto) { 61 + public void doSendNotification(Collection<Long> userIds, ImNotificationWebSocketDTO notification) {
95 for (Long userId : getDistinctUserIds(userIds)) { 62 for (Long userId : getDistinctUserIds(userIds)) {
96 try { 63 try {
97 webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, 64 webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId,
98 - ImChannelMessageDTO.TYPE, dto); 65 + ImNotificationWebSocketDTO.TYPE, notification);
99 } catch (Exception e) { 66 } catch (Exception e) {
100 - log.error("[doSendChannelMessage][userId({}) dto({}) 发送失败]", userId, dto, e); 67 + log.error("[doSendNotification][userId({}) notification({}) 发送失败]", userId, notification, e);
101 } 68 }
102 } 69 }
103 } 70 }
104 71
105 /** 72 /**
106 - * 异步广播频道 WebSocket 消息给当前所有在线管理端用户;  
107 - * 依赖 infra WebSocketSenderApi 按 UserType 广播能力,离线用户由客户端上线 pull 兜底 73 + * 异步广播 WebSocket 通知
108 */ 74 */
109 @Async 75 @Async
110 - public void doBroadcastChannelMessage(ImChannelMessageDTO dto) { 76 + public void doBroadcastNotification(ImNotificationWebSocketDTO notification) {
111 try { 77 try {
112 webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), 78 webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(),
113 - ImChannelMessageDTO.TYPE, dto); 79 + ImNotificationWebSocketDTO.TYPE, notification);
114 } catch (Exception e) { 80 } catch (Exception e) {
115 - log.error("[doBroadcastChannelMessage][dto({}) 广播失败]", dto, e); 81 + log.error("[doBroadcastNotification][notification({}) 广播失败]", notification, e);
116 } 82 }
117 } 83 }
118 84
@@ -121,19 +87,15 @@ public class ImWebSocketServiceImpl implements ImWebSocketService { @@ -121,19 +87,15 @@ public class ImWebSocketServiceImpl implements ImWebSocketService {
121 } 87 }
122 88
123 /** 89 /**
124 - * 事务感知的任务调度:  
125 - * - 有事务:注册 afterCommit 回调,事务提交后再执行,防止客户端拿到消息去查库时数据还没落盘  
126 - * - 无事务:直接执行(如非 @Transactional 方法中的调用) 90 + * 事务感知的任务调度
127 * 91 *
128 - * @param task 待执行的推送任务(内部通过 getSelf() 走 @Async 异步执行) 92 + * @param task 待执行的推送任务
129 */ 93 */
130 private void executeAfterTransaction(Runnable task) { 94 private void executeAfterTransaction(Runnable task) {
131 - // 情况一:没有事务,直接执行  
132 if (!TransactionSynchronizationManager.isSynchronizationActive()) { 95 if (!TransactionSynchronizationManager.isSynchronizationActive()) {
133 task.run(); 96 task.run();
134 return; 97 return;
135 } 98 }
136 - // 情况二:有事务,注册 afterCommit 事件,在事务提交后执行  
137 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { 99 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
138 100
139 @Override 101 @Override
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java deleted
1 -package cn.iocoder.yudao.module.im.service.websocket.dto;  
2 -  
3 -import cn.hutool.core.lang.Assert;  
4 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
5 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
6 -import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;  
7 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;  
8 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.BaseFriendNotification;  
9 -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.BaseGroupNotification;  
10 -import lombok.Data;  
11 -import lombok.experimental.Accessors;  
12 -  
13 -import java.time.LocalDateTime;  
14 -  
15 -/**  
16 - * IM 私聊消息 WebSocket 统一推送 DTO  
17 - * <p>  
18 - * 发送、已读、回执、撤回都通过 {@link #type} 字段区分,  
19 - * 并统一复用 {@link #id} 字段表达目标消息或已读位置。  
20 - *  
21 - * @author 芋道源码  
22 - */  
23 -@Data  
24 -@Accessors(chain = true)  
25 -public class ImPrivateMessageDTO {  
26 -  
27 - public static final String TYPE = "im-private-message";  
28 -  
29 - /**  
30 - * 消息编号  
31 - * <p>  
32 - * 普通消息:当前消息 id  
33 - * READ:已读位置(我已读到这条消息)  
34 - * RECEIPT:已读位置(对方已读到这条消息)  
35 - * RECALL:被撤回的原消息 id  
36 - */  
37 - private Long id;  
38 - /**  
39 - * 客户端消息编号  
40 - */  
41 - private String clientMessageId;  
42 - /**  
43 - * 发送人编号  
44 - */  
45 - private Long senderId;  
46 - /**  
47 - * 接收人编号  
48 - */  
49 - private Long receiverId;  
50 - /**  
51 - * 消息类型  
52 - */  
53 - private Integer type;  
54 - /**  
55 - * 消息内容  
56 - */  
57 - private String content;  
58 - /**  
59 - * 消息状态  
60 - */  
61 - private Integer status;  
62 - /**  
63 - * 发送时间  
64 - */  
65 - private LocalDateTime sendTime;  
66 -  
67 - // ========== 静态工厂方法 ==========  
68 -  
69 - /**  
70 - * 构建发送消息 DTO  
71 - *  
72 - * @param message 私聊消息 DO  
73 - * @return 私聊 DTO  
74 - */  
75 - public static ImPrivateMessageDTO ofSend(ImPrivateMessageDO message) {  
76 - return BeanUtils.toBean(message, ImPrivateMessageDTO.class);  
77 - }  
78 -  
79 - /**  
80 - * 构建已读同步 DTO(多端同步:通知自己的其他终端"我已经读了某个会话")  
81 - *  
82 - * @param senderId 当前用户编号  
83 - * @param receiverId 对方用户编号  
84 - * @param readId 已读位置(最大已读消息编号)  
85 - * @return 私聊 DTO  
86 - */  
87 - public static ImPrivateMessageDTO ofRead(Long senderId, Long receiverId, Long readId) {  
88 - return new ImPrivateMessageDTO()  
89 - .setId(readId).setType(ImMessageTypeEnum.READ.getType())  
90 - .setSenderId(senderId).setReceiverId(receiverId);  
91 - }  
92 -  
93 - /**  
94 - * 构建已读回执 DTO(通知对方"我已读了你的消息")  
95 - *  
96 - * @param senderId 已读方的用户编号  
97 - * @param receiverId 对方用户编号  
98 - * @param readId 已读位置(最大已读消息编号)  
99 - * @return 私聊 DTO  
100 - */  
101 - public static ImPrivateMessageDTO ofReceipt(Long senderId, Long receiverId, Long readId) {  
102 - return new ImPrivateMessageDTO()  
103 - .setId(readId).setType(ImMessageTypeEnum.RECEIPT.getType())  
104 - .setSenderId(senderId).setReceiverId(receiverId);  
105 - }  
106 -  
107 - // ==================== 好友变更相关 ====================  
108 -  
109 - /**  
110 - * 构建好友通知推送 DTO(统一入口)  
111 - *  
112 - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 FRIEND_* 段  
113 - * @param operatorUserId 操作人用户编号;同时作为帧的 senderId 用于定位接收端 friendUserId  
114 - * @param receiverUserId 推送目标用户编号  
115 - * @param payload 好友通知 payload(继承 {@link BaseFriendNotification})  
116 - * @return 私聊 DTO  
117 - */  
118 - public static ImPrivateMessageDTO ofFriendNotification(Integer type, Long operatorUserId,  
119 - Long receiverUserId, BaseFriendNotification payload) {  
120 - validateNotification(type, payload, ImMessageTypeEnum.isFriendNotification(type));  
121 - return new ImPrivateMessageDTO().setType(type)  
122 - .setSenderId(operatorUserId).setReceiverId(receiverUserId)  
123 - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now());  
124 - }  
125 -  
126 - // ==================== 群定向私聊通知 ====================  
127 -  
128 - // TODO DONE @AI:群申请定向通知继续走私聊通道  
129 - /**  
130 - * 构建群通知推送 DTO(走私聊通道定向推送,不入群消息流)  
131 - * <p>  
132 - * 用于 GROUP_REQUEST_RECEIVED / GROUP_REQUEST_APPROVED / GROUP_REQUEST_REJECTED 段位;  
133 - * 与 {@link cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO} 走 sendGroupMessage 群广播路径不同  
134 - *  
135 - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 GROUP_REQUEST_* 段  
136 - * @param operatorUserId 操作人用户编号;同时作为帧的 senderId  
137 - * @param receiverUserId 推送目标用户编号  
138 - * @param payload 群事件 payload(继承 {@link BaseGroupNotification})  
139 - * @return 私聊 DTO  
140 - */  
141 - public static ImPrivateMessageDTO ofGroupNotification(Integer type, Long operatorUserId,  
142 - Long receiverUserId, BaseGroupNotification payload) {  
143 - validateNotification(type, payload, ImMessageTypeEnum.isGroupRequestNotification(type));  
144 - return new ImPrivateMessageDTO().setType(type)  
145 - .setSenderId(operatorUserId).setReceiverId(receiverUserId)  
146 - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now());  
147 - }  
148 -  
149 - // ==================== 实时通话信令 ====================  
150 -  
151 - /**  
152 - * 构建通话信令推送 DTO(走私聊通道仅推参与方,不入消息流)  
153 - * <p>  
154 - * 用于 RTC_CALL(INVITE / REJECT 等)/ RTC_PARTICIPANT_CONNECTED / RTC_PARTICIPANT_DISCONNECTED  
155 - *  
156 - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 RTC_* 段  
157 - * @param senderId 发送人编号;INVITE 时为发起人,REJECT 时为拒绝者,参与者事件时为加入 / 离开者  
158 - * @param receiverUserId 推送目标用户编号  
159 - * @param payload 通话事件 payload(任意 RTC 通知 DTO)  
160 - * @return 私聊 DTO  
161 - */  
162 - public static ImPrivateMessageDTO ofRtcNotification(Integer type, Long senderId,  
163 - Long receiverUserId, Object payload) {  
164 - validateNotification(type, payload, ImMessageTypeEnum.isRtcNotification(type));  
165 - return new ImPrivateMessageDTO().setType(type)  
166 - .setSenderId(senderId).setReceiverId(receiverUserId)  
167 - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now());  
168 - }  
169 -  
170 - private static void validateNotification(Integer type, Object payload, boolean supported) {  
171 - Assert.notNull(type, "消息类型不能为空");  
172 - Assert.notNull(payload, "消息内容不能为空");  
173 - Assert.isTrue(supported, "消息类型不匹配 type={}", type);  
174 - }  
175 -  
176 -}  
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.websocket.notification;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +/**
  7 + * IM WebSocket 在线通知外壳
  8 + * <p>
  9 + * conversationType 定位会话维度;contentType 定位业务内容;payload 承载对应通知对象。
  10 + * 会进入聊天流的私聊、群聊、频道事件走 message 子包;不进入聊天流的好友、加群申请、通话信令走 NONE 会话。
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +@Accessors(chain = true)
  16 +public class ImNotificationWebSocketDTO {
  17 +
  18 + public static final String TYPE = "im-notification";
  19 +
  20 + /**
  21 + * 会话类型,参见 ImConversationTypeEnum 枚举类
  22 + */
  23 + private Integer conversationType;
  24 + /**
  25 + * 内容类型,参见 ImContentTypeEnum 枚举类
  26 + */
  27 + private Integer contentType;
  28 + /**
  29 + * 负载数据
  30 + */
  31 + private Object payload;
  32 +
  33 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/BaseFriendNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendAddNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendBlockNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendDeleteNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestApprovedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestRejectedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUnblockNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.friend;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/BaseGroupNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminAddNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 添加管理员事件通知(memberUserIds 为被设管理员的成员) 4 * 添加管理员事件通知(memberUserIds 为被设管理员的成员)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminRemoveNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 撤销管理员事件通知(memberUserIds 为被撤销管理员的成员) 4 * 撤销管理员事件通知(memberUserIds 为被撤销管理员的成员)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupBannedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCancelMutedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCreateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 群创建事件通知(memberUserIds 含创建者 + 初始邀请成员) 4 * 群创建事件通知(memberUserIds 含创建者 + 初始邀请成员)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupDissolveNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 群解散事件通知 4 * 群解散事件通知
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupInfoUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
5 /** 5 /**
6 - * 群信息变更事件通知(当前承载头像变更,NAME / NOTICE 走独立事件) 6 + * 群信息变更事件通知(NAME / NOTICE 走独立事件)
7 */ 7 */
8 @Data 8 @Data
9 public class GroupInfoUpdateNotification extends BaseGroupNotification { 9 public class GroupInfoUpdateNotification extends BaseGroupNotification {
@@ -16,5 +16,13 @@ public class GroupInfoUpdateNotification extends BaseGroupNotification { @@ -16,5 +16,13 @@ public class GroupInfoUpdateNotification extends BaseGroupNotification {
16 * 新群头像 16 * 新群头像
17 */ 17 */
18 private String newAvatar; 18 private String newAvatar;
  19 + /**
  20 + * 旧进群审批开关
  21 + */
  22 + private Boolean oldJoinApproval;
  23 + /**
  24 + * 新进群审批开关
  25 + */
  26 + private Boolean newJoinApproval;
19 27
20 } 28 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberCancelMutedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberEnterNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberInviteNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 成员加入事件通知(memberUserIds 为被邀请人) 4 * 成员加入事件通知(memberUserIds 为被邀请人)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberKickNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 成员被移出事件通知(memberUserIds 为被移出成员) 4 * 成员被移出事件通知(memberUserIds 为被移出成员)
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberListNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberMutedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberNicknameUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberQuitNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 /** 3 /**
4 * 成员退群事件通知 4 * 成员退群事件通知
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberSettingUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessagePinNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessageUnpinNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMutedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNameUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNoticeUpdateNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupOwnerTransferNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
@@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode;
6 /** 6 /**
7 * 入群申请被同意通知 7 * 入群申请被同意通知
8 * <p> 8 * <p>
9 - * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 9 + * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除
10 */ 10 */
11 @EqualsAndHashCode(callSuper = true) 11 @EqualsAndHashCode(callSuper = true)
12 @Data 12 @Data
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestReceivedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
@@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode;
6 /** 6 /**
7 * 收到新的入群申请通知 7 * 收到新的入群申请通知
8 * <p> 8 * <p>
9 - * 走私聊通道定向推送给群主 + 全部管理员(多端同步);payload 已携带申请方昵称 / 头像,前端按 requestId 直接 push 进列表 9 + * 定向推送给群主 + 全部管理员(多端同步);payload 已携带申请方昵称 / 头像,前端按 requestId 直接 push 进列表
10 */ 10 */
11 @EqualsAndHashCode(callSuper = true) 11 @EqualsAndHashCode(callSuper = true)
12 @Data 12 @Data
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.group;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 import lombok.EqualsAndHashCode; 4 import lombok.EqualsAndHashCode;
@@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode;
6 /** 6 /**
7 * 入群申请被拒绝通知 7 * 入群申请被拒绝通知
8 * <p> 8 * <p>
9 - * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 9 + * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除
10 */ 10 */
11 @EqualsAndHashCode(callSuper = true) 11 @EqualsAndHashCode(callSuper = true)
12 @Data 12 @Data
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImChannelMessageDTO.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.message;
2 2
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
5 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;  
6 import lombok.Data; 5 import lombok.Data;
7 import lombok.experimental.Accessors; 6 import lombok.experimental.Accessors;
8 7
9 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
10 9
11 /** 10 /**
12 - * IM 频道消息 WebSocket 推送 DTO 11 + * IM 频道消息 WebSocket 推送通知
13 * <p> 12 * <p>
14 * 单向:服务端运营推送 → C 端用户;C 端不能向频道发消息。 13 * 单向:服务端运营推送 → C 端用户;C 端不能向频道发消息。
15 * 字段分层:顶层是消息元数据 + 检索维度,content 是 MaterialMessage payload 的 JSON 串。 14 * 字段分层:顶层是消息元数据 + 检索维度,content 是 MaterialMessage payload 的 JSON 串。
@@ -18,9 +17,7 @@ import java.time.LocalDateTime; @@ -18,9 +17,7 @@ import java.time.LocalDateTime;
18 */ 17 */
19 @Data 18 @Data
20 @Accessors(chain = true) 19 @Accessors(chain = true)
21 -public class ImChannelMessageDTO {  
22 -  
23 - public static final String TYPE = "im-channel-message"; 20 +public class ImChannelMessageNotification {
24 21
25 /** 22 /**
26 * 消息编号 23 * 消息编号
@@ -48,23 +45,10 @@ public class ImChannelMessageDTO { @@ -48,23 +45,10 @@ public class ImChannelMessageDTO {
48 private LocalDateTime sendTime; 45 private LocalDateTime sendTime;
49 46
50 /** 47 /**
51 - * 由频道消息 DO 构建推送 DTO  
52 - */  
53 - public static ImChannelMessageDTO ofSend(ImChannelMessageDO message) {  
54 - return BeanUtils.toBean(message, ImChannelMessageDTO.class);  
55 - }  
56 -  
57 - /**  
58 - * 构建已读同步 DTO;多端同步:通知自己的其他终端「我已经读了某频道」  
59 - *  
60 - * @param channelId 频道编号  
61 - * @param readId 已读位置(最大已读消息编号)  
62 - * @return 频道 DTO 48 + * 由频道消息 DO 构建推送通知
63 */ 49 */
64 - public static ImChannelMessageDTO ofRead(Long channelId, Long readId) {  
65 - return new ImChannelMessageDTO()  
66 - .setId(readId).setType(ImMessageTypeEnum.READ.getType())  
67 - .setChannelId(channelId); 50 + public static ImChannelMessageNotification ofSend(ImChannelMessageDO message) {
  51 + return BeanUtils.toBean(message, ImChannelMessageNotification.class);
68 } 52 }
69 53
70 } 54 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.message;
2 2
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
5 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;  
6 import lombok.Data; 5 import lombok.Data;
7 6
8 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
9 import java.util.List; 8 import java.util.List;
10 9
11 /** 10 /**
12 - * IM 群聊消息 WebSocket 统一推送 DTO  
13 - * <p>  
14 - * 发送、已读、回执都通过 {@link #type} 字段区分,  
15 - * 并统一复用 {@link #id} 字段表达目标消息或已读位置。 11 + * IM 群聊消息 WebSocket 统一推送通知
16 * 12 *
17 * @author 芋道源码 13 * @author 芋道源码
18 */ 14 */
19 @Data 15 @Data
20 -public class ImGroupMessageDTO {  
21 -  
22 - public static final String TYPE = "im-group-message"; 16 +public class ImGroupMessageNotification {
23 17
24 /** 18 /**
25 * 消息编号 19 * 消息编号
26 - * <p>  
27 - * 普通消息:当前消息 id  
28 - * READ:无(群已读不需要)  
29 - * RECEIPT:需要回执的目标消息 id  
30 */ 20 */
31 private Long id; 21 private Long id;
32 /** 22 /**
@@ -54,6 +44,14 @@ public class ImGroupMessageDTO { @@ -54,6 +44,14 @@ public class ImGroupMessageDTO {
54 */ 44 */
55 private Integer status; 45 private Integer status;
56 /** 46 /**
  47 + * 回执状态
  48 + */
  49 + private Integer receiptStatus;
  50 + /**
  51 + * 已读人数
  52 + */
  53 + private Integer readCount;
  54 + /**
57 * 发送时间 55 * 发送时间
58 */ 56 */
59 private LocalDateTime sendTime; 57 private LocalDateTime sendTime;
@@ -66,58 +64,14 @@ public class ImGroupMessageDTO { @@ -66,58 +64,14 @@ public class ImGroupMessageDTO {
66 */ 64 */
67 private List<Long> receiverUserIds; 65 private List<Long> receiverUserIds;
68 /** 66 /**
69 - * 群回执状态  
70 - */  
71 - private Integer receiptStatus;  
72 - /**  
73 - * 群回执已读人数  
74 - */  
75 - private Integer readCount;  
76 - /**  
77 - * 已读位置  
78 - */  
79 - private Long readId;  
80 -  
81 - // ========== 静态工厂方法 ==========  
82 -  
83 - /**  
84 - * 构建发送消息 DTO 67 + * 构建发送消息通知
85 * 68 *
86 * @param message 群聊消息 DO 69 * @param message 群聊消息 DO
87 - * @return 群聊 DTO  
88 - */  
89 - public static ImGroupMessageDTO ofSend(ImGroupMessageDO message) {  
90 - return BeanUtils.toBean(message, ImGroupMessageDTO.class).setReceiverUserIds(null);  
91 - }  
92 -  
93 - /**  
94 - * 构建已读同步 DTO(多端同步:通知自己的其他终端"我已经读了某个群")  
95 - *  
96 - * @param senderId 当前用户编号  
97 - * @param groupId 群编号  
98 - * @param readId 已读位置(最大已读消息编号)  
99 - * @return 群聊 DTO  
100 - */  
101 - public static ImGroupMessageDTO ofRead(Long senderId, Long groupId, Long readId) {  
102 - return new ImGroupMessageDTO()  
103 - .setId(readId).setReadId(readId).setType(ImMessageTypeEnum.READ.getType())  
104 - .setSenderId(senderId).setGroupId(groupId);  
105 - }  
106 -  
107 - /**  
108 - * 构建群回执 DTO(广播回执状态和已读人数)  
109 - *  
110 - * @param messageId 消息编号  
111 - * @param groupId 群编号  
112 - * @param readCount 已读人数  
113 - * @param receiptStatus 回执状态  
114 - * @return 群聊 DTO 70 + * @return 群聊通知
115 */ 71 */
116 - public static ImGroupMessageDTO ofReceipt(Long messageId, Long groupId,  
117 - Integer readCount, Integer receiptStatus) {  
118 - return new ImGroupMessageDTO()  
119 - .setId(messageId).setType(ImMessageTypeEnum.RECEIPT.getType())  
120 - .setGroupId(groupId).setReadCount(readCount).setReceiptStatus(receiptStatus); 72 + public static ImGroupMessageNotification ofSend(ImGroupMessageDO message) {
  73 + return BeanUtils.toBean(message, ImGroupMessageNotification.class)
  74 + .setReadCount(message.getReadCount() != null ? message.getReadCount() : 0);
121 } 75 }
122 76
123 } 77 }
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.websocket.notification.message;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +/**
  7 + * IM 消息已读同步通知
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Data
  12 +@Accessors(chain = true)
  13 +public class ImMessageReadNotification {
  14 +
  15 + /**
  16 + * 已读位置(最大已读消息编号)
  17 + */
  18 + private Long id;
  19 + /**
  20 + * 发送人编号
  21 + */
  22 + private Long senderId;
  23 + /**
  24 + * 私聊接收人编号
  25 + */
  26 + private Long receiverId;
  27 + /**
  28 + * 群编号
  29 + */
  30 + private Long groupId;
  31 + /**
  32 + * 频道编号
  33 + */
  34 + private Long channelId;
  35 + /**
  36 + * 已读位置(最大已读消息编号)
  37 + */
  38 + private Long readId;
  39 +
  40 + public static ImMessageReadNotification ofPrivate(Long senderId, Long receiverId, Long readId) {
  41 + return new ImMessageReadNotification()
  42 + .setId(readId).setReadId(readId)
  43 + .setSenderId(senderId).setReceiverId(receiverId);
  44 + }
  45 +
  46 + public static ImMessageReadNotification ofGroup(Long senderId, Long groupId, Long readId) {
  47 + return new ImMessageReadNotification()
  48 + .setId(readId).setReadId(readId)
  49 + .setSenderId(senderId).setGroupId(groupId);
  50 + }
  51 +
  52 + public static ImMessageReadNotification ofChannel(Long channelId, Long readId) {
  53 + return new ImMessageReadNotification()
  54 + .setId(readId).setReadId(readId)
  55 + .setChannelId(channelId);
  56 + }
  57 +
  58 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.websocket.notification.message;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +/**
  7 + * IM 消息回执通知
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Data
  12 +@Accessors(chain = true)
  13 +public class ImMessageReceiptNotification {
  14 +
  15 + /**
  16 + * 消息编号
  17 + */
  18 + private Long id;
  19 + /**
  20 + * 已读方的用户编号
  21 + */
  22 + private Long senderId;
  23 + /**
  24 + * 私聊接收人编号
  25 + */
  26 + private Long receiverId;
  27 + /**
  28 + * 群编号
  29 + */
  30 + private Long groupId;
  31 + /**
  32 + * 已读人数
  33 + */
  34 + private Integer readCount;
  35 + /**
  36 + * 回执状态
  37 + */
  38 + private Integer receiptStatus;
  39 +
  40 + public static ImMessageReceiptNotification ofPrivate(Long senderId, Long receiverId, Long readId) {
  41 + return new ImMessageReceiptNotification()
  42 + .setId(readId).setSenderId(senderId).setReceiverId(receiverId);
  43 + }
  44 +
  45 + public static ImMessageReceiptNotification ofGroup(Long messageId, Long groupId,
  46 + Integer readCount, Integer receiptStatus) {
  47 + return new ImMessageReceiptNotification()
  48 + .setId(messageId).setGroupId(groupId)
  49 + .setReadCount(readCount).setReceiptStatus(receiptStatus);
  50 + }
  51 +
  52 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.websocket.notification.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
  5 +import lombok.Data;
  6 +import lombok.experimental.Accessors;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * IM 私聊消息 WebSocket 统一推送通知
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +@Accessors(chain = true)
  17 +public class ImPrivateMessageNotification {
  18 +
  19 + /**
  20 + * 消息编号
  21 + */
  22 + private Long id;
  23 + /**
  24 + * 客户端消息编号
  25 + */
  26 + private String clientMessageId;
  27 + /**
  28 + * 发送人编号
  29 + */
  30 + private Long senderId;
  31 + /**
  32 + * 接收人编号
  33 + */
  34 + private Long receiverId;
  35 + /**
  36 + * 消息类型
  37 + */
  38 + private Integer type;
  39 + /**
  40 + * 消息内容
  41 + */
  42 + private String content;
  43 + /**
  44 + * 消息状态
  45 + */
  46 + private Integer status;
  47 + /**
  48 + * 回执状态
  49 + */
  50 + private Integer receiptStatus;
  51 + /**
  52 + * 发送时间
  53 + */
  54 + private LocalDateTime sendTime;
  55 +
  56 + // ========== 静态工厂方法 ==========
  57 +
  58 + /**
  59 + * 构建发送消息通知
  60 + *
  61 + * @param message 私聊消息 DO
  62 + * @return 私聊通知
  63 + */
  64 + public static ImPrivateMessageNotification ofSend(ImPrivateMessageDO message) {
  65 + return BeanUtils.toBean(message, ImPrivateMessageNotification.class);
  66 + }
  67 +
  68 +}
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java 0 → 100644
  1 +/**
  2 + * IM WebSocket 在线通知对象。
  3 + *
  4 + * <p>
  5 + * 本包只放 WebSocket 外层通知对象,固定使用 im-notification 作为推送 type。
  6 + * 具体业务 payload 放在子包中:
  7 + * <ul>
  8 + * <li>{@code message}:私聊、群聊、频道消息通知,以及消息内容结构;</li>
  9 + * <li>{@code friend}:好友关系和好友申请通知;</li>
  10 + * <li>{@code group}:群资料、群成员、加群申请通知;</li>
  11 + * <li>{@code rtc}:实时通话通知。</li>
  12 + * </ul>
  13 + */
  14 +package cn.iocoder.yudao.module.im.service.websocket.notification;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallEndNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
4 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; 4 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
4 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; 4 import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallStartNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
4 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 4 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantConnectedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
4 import lombok.Data; 4 import lombok.Data;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantDisconnectedNotification.java renamed to yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java
1 -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; 1 +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc;
2 2
3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; 3 import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO;
4 import lombok.Data; 4 import lombok.Data;
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java
@@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollUtil; @@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.convert.Convert; 4 import cn.hutool.core.convert.Convert;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum;  
8 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; 7 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  8 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage;
9 9
10 import java.util.Collection; 10 import java.util.Collection;
11 import java.util.LinkedHashMap; 11 import java.util.LinkedHashMap;
@@ -39,9 +39,9 @@ public class ImMessageUtils { @@ -39,9 +39,9 @@ public class ImMessageUtils {
39 * @param content 消息内容 39 * @param content 消息内容
40 */ 40 */
41 public static void validateUserMessageContent(Integer type, String content) { 41 public static void validateUserMessageContent(Integer type, String content) {
42 - ImMessageTypeEnum messageType; 42 + ImContentTypeEnum messageType;
43 try { 43 try {
44 - messageType = ImMessageTypeEnum.validate(type); 44 + messageType = ImContentTypeEnum.validate(type);
45 } catch (IllegalArgumentException e) { 45 } catch (IllegalArgumentException e) {
46 throw exception(MESSAGE_CONTENT_INVALID); 46 throw exception(MESSAGE_CONTENT_INVALID);
47 } 47 }
@@ -52,23 +52,23 @@ public class ImMessageUtils { @@ -52,23 +52,23 @@ public class ImMessageUtils {
52 if (map == null) { 52 if (map == null) {
53 throw exception(MESSAGE_CONTENT_INVALID); 53 throw exception(MESSAGE_CONTENT_INVALID);
54 } 54 }
55 - if (ImMessageTypeEnum.TEXT == messageType) { 55 + if (ImContentTypeEnum.TEXT == messageType) {
56 validateTextContent(map); 56 validateTextContent(map);
57 - } else if (ImMessageTypeEnum.IMAGE == messageType || ImMessageTypeEnum.FACE == messageType) { 57 + } else if (ImContentTypeEnum.IMAGE == messageType || ImContentTypeEnum.FACE == messageType) {
58 validateUrl(getString(map, "url")); 58 validateUrl(getString(map, "url"));
59 - } else if (ImMessageTypeEnum.VOICE == messageType) { 59 + } else if (ImContentTypeEnum.VOICE == messageType) {
60 validateUrl(getString(map, "url")); 60 validateUrl(getString(map, "url"));
61 validatePositiveNumber(map.get("duration")); 61 validatePositiveNumber(map.get("duration"));
62 - } else if (ImMessageTypeEnum.VIDEO == messageType) { 62 + } else if (ImContentTypeEnum.VIDEO == messageType) {
63 validateUrl(getString(map, "url")); 63 validateUrl(getString(map, "url"));
64 - } else if (ImMessageTypeEnum.FILE == messageType) { 64 + } else if (ImContentTypeEnum.FILE == messageType) {
65 validateUrl(getString(map, "url")); 65 validateUrl(getString(map, "url"));
66 validateNotBlank(getString(map, "name")); 66 validateNotBlank(getString(map, "name"));
67 - } else if (ImMessageTypeEnum.CARD == messageType) { 67 + } else if (ImContentTypeEnum.CARD == messageType) {
68 validateCardContent(map); 68 validateCardContent(map);
69 - } else if (ImMessageTypeEnum.MERGE == messageType) { 69 + } else if (ImContentTypeEnum.MERGE == messageType) {
70 validateMergeContent(map); 70 validateMergeContent(map);
71 - } else if (ImMessageTypeEnum.MATERIAL == messageType) { 71 + } else if (ImContentTypeEnum.MATERIAL == messageType) {
72 validateMaterialContent(map); 72 validateMaterialContent(map);
73 } 73 }
74 } 74 }
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.dal.mysql.conversation;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO;
  5 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  6 +import jakarta.annotation.Resource;
  7 +import org.junit.jupiter.api.Test;
  8 +
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  14 +import static org.junit.jupiter.api.Assertions.*;
  15 +
  16 +/**
  17 + * {@link ImConversationReadMapper} 的单元测试
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +public class ImConversationReadMapperTest extends BaseDbUnitTest {
  22 +
  23 + @Resource
  24 + private ImConversationReadMapper mapper;
  25 +
  26 + @Test
  27 + public void testUpdateReadMessageIdToLarger_onlyAdvancesWhenLarger() {
  28 + // 准备:用户 1 在群 10 的读位置 = 50
  29 + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L);
  30 + mapper.insert(read);
  31 +
  32 + // 1. 新位置更大 → 前进
  33 + int advanced = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now());
  34 + assertEquals(1, advanced);
  35 + assertEquals(100L, mapper.selectById(read.getId()).getMessageId());
  36 +
  37 + // 2. 新位置相等 → 不更新
  38 + int equalUpdate = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now());
  39 + assertEquals(0, equalUpdate);
  40 +
  41 + // 3. 新位置更小(乱序上报)→ 不回退
  42 + int smaller = mapper.updateReadMessageIdToLarger(read.getId(), 80L, LocalDateTime.now());
  43 + assertEquals(0, smaller);
  44 + assertEquals(100L, mapper.selectById(read.getId()).getMessageId());
  45 + }
  46 +
  47 + @Test
  48 + public void testSelectByUserIdAndConversation() {
  49 + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L);
  50 + mapper.insert(read);
  51 + // 同用户不同会话类型,不应命中
  52 + mapper.insert(buildRead(1L, ImConversationTypeEnum.PRIVATE.getType(), 10L, 99L));
  53 +
  54 + ImConversationReadDO result = mapper.selectByUserIdAndConversation(
  55 + 1L, ImConversationTypeEnum.GROUP.getType(), 10L);
  56 +
  57 + assertNotNull(result);
  58 + assertEquals(50L, result.getMessageId());
  59 + }
  60 +
  61 + @Test
  62 + public void testSelectListByConversation_aggregatesAllUsers() {
  63 + // 群 10 内三个用户的读位置,用于群回执人数聚合
  64 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L));
  65 + mapper.insert(buildRead(2L, ImConversationTypeEnum.GROUP.getType(), 10L, 80L));
  66 + mapper.insert(buildRead(3L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L));
  67 + // 别的群,不应混入
  68 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 20L, 999L));
  69 +
  70 + List<ImConversationReadDO> list = mapper.selectListByConversation(
  71 + ImConversationTypeEnum.GROUP.getType(), 10L);
  72 + Map<Long, Long> positions = convertMap(list,
  73 + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId);
  74 +
  75 + assertEquals(3, positions.size());
  76 + assertEquals(100L, positions.get(1L));
  77 + assertEquals(80L, positions.get(2L));
  78 + assertEquals(50L, positions.get(3L));
  79 + }
  80 +
  81 + @Test
  82 + public void testSelectListByUserIdAndConversations_batch() {
  83 + // 用户 1 在频道 10 / 20 / 30 的读位置
  84 + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 10L, 5L));
  85 + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 20L, 8L));
  86 + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 30L, 3L));
  87 +
  88 + List<ImConversationReadDO> list = mapper.selectListByUserIdAndConversations(
  89 + 1L, ImConversationTypeEnum.CHANNEL.getType(), List.of(10L, 20L));
  90 +
  91 + assertEquals(2, list.size());
  92 + }
  93 +
  94 + private ImConversationReadDO buildRead(Long userId, Integer conversationType, Long conversationId,
  95 + Long readMessageId) {
  96 + return ImConversationReadDO.builder()
  97 + .userId(userId)
  98 + .conversationType(conversationType)
  99 + .targetId(conversationId)
  100 + .messageId(readMessageId)
  101 + .readTime(LocalDateTime.now())
  102 + .build();
  103 + }
  104 +
  105 +}
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java
@@ -11,7 +11,7 @@ import java.util.List; @@ -11,7 +11,7 @@ import java.util.List;
11 import static org.junit.jupiter.api.Assertions.*; 11 import static org.junit.jupiter.api.Assertions.*;
12 12
13 /** 13 /**
14 - * IM 好友申请记录 Mapper 单元测试 14 + * {@link ImFriendRequestMapper} 的单元测试
15 * 15 *
16 * @author 芋道源码 16 * @author 芋道源码
17 */ 17 */
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java
1 package cn.iocoder.yudao.module.im.dal.mysql.message; 1 package cn.iocoder.yudao.module.im.dal.mysql.message;
2 2
3 -import cn.hutool.core.collection.ListUtil;  
4 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 4 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
6 -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 5 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
7 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 6 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
8 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
9 import org.junit.jupiter.api.Test; 8 import org.junit.jupiter.api.Test;
@@ -14,7 +13,7 @@ import java.util.List; @@ -14,7 +13,7 @@ import java.util.List;
14 import static org.junit.jupiter.api.Assertions.*; 13 import static org.junit.jupiter.api.Assertions.*;
15 14
16 /** 15 /**
17 - * IM 群聊消息 Mapper 单元测试 16 + * {@link ImGroupMessageMapper} 的单元测试
18 * 17 *
19 * @author 芋道源码 18 * @author 芋道源码
20 */ 19 */
@@ -28,46 +27,76 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { @@ -28,46 +27,76 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest {
28 // ========== selectListByMinId ========== 27 // ========== selectListByMinId ==========
29 28
30 @Test 29 @Test
  30 + public void testSelectListByMinId_filterByReceiverSnapshot() {
  31 + // 准备:群 10 三条消息,快照分别命中 / 不命中用户 1
  32 + ImGroupMessageDO visible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
  33 + mapper.insert(visible);
  34 + ImGroupMessageDO invisible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L));
  35 + mapper.insert(invisible);
  36 + ImGroupMessageDO selfSend = buildMessage(10L, 1L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
  37 + mapper.insert(selfSend);
  38 +
  39 + // 调用:用户 1 拉取群 10
  40 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100);
  41 +
  42 + // 断言:仅返回快照包含用户 1 的消息
  43 + assertEquals(2, result.size());
  44 + assertEquals(visible.getId(), result.get(0).getId());
  45 + assertEquals(selfSend.getId(), result.get(1).getId());
  46 + }
  47 +
  48 + @Test
  49 + public void testSelectListByMinId_excludeNullAndEmptySnapshot() {
  50 + // 准备:快照为 null / 空字符串的老数据,不应被任何用户拉到
  51 + ImGroupMessageDO nullSnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, null);
  52 + mapper.insert(nullSnapshot);
  53 + ImGroupMessageDO emptySnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of());
  54 + mapper.insert(emptySnapshot);
  55 +
  56 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100);
  57 +
  58 + assertTrue(result.isEmpty());
  59 + }
  60 +
  61 + @Test
31 public void testSelectListByMinId_onlyReturnsGroupsInIdList() { 62 public void testSelectListByMinId_onlyReturnsGroupsInIdList() {
32 - // 准备:群 10 的消息 + 群 20 的消息  
33 - ImGroupMessageDO msg10 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 63 + // 准备:群 10 + 群 20 各一条,用户 1 都在快照内
  64 + ImGroupMessageDO msg10 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
34 mapper.insert(msg10); 65 mapper.insert(msg10);
35 - ImGroupMessageDO msg20 = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD); 66 + ImGroupMessageDO msg20 = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
36 mapper.insert(msg20); 67 mapper.insert(msg20);
37 68
38 - // 调用:只查群 10  
39 - List<ImGroupMessageDO> result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 100); 69 + // 调用:候选群只含群 10
  70 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100);
40 71
41 - // 断言:只有 msg10  
42 assertEquals(1, result.size()); 72 assertEquals(1, result.size());
43 assertEquals(msg10.getId(), result.get(0).getId()); 73 assertEquals(msg10.getId(), result.get(0).getId());
44 } 74 }
45 75
46 @Test 76 @Test
47 public void testSelectListByMinId_includesRecall() { 77 public void testSelectListByMinId_includesRecall() {
48 - // 准备:一条正常 + 一条撤回;客户端拉离线消息需要拿到撤回状态以渲染「此消息已撤回」占位  
49 - ImGroupMessageDO normal = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 78 + // 准备:撤回消息也要返回,由客户端按 status 渲染「此消息已撤回」占位
  79 + ImGroupMessageDO normal = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
50 mapper.insert(normal); 80 mapper.insert(normal);
51 - ImGroupMessageDO recalled = buildMessage(10L, 1L, ImMessageStatusEnum.RECALL); 81 + ImGroupMessageDO recalled = buildMessage(10L, 2L, ImMessageStatusEnum.RECALL, List.of(1L));
52 mapper.insert(recalled); 82 mapper.insert(recalled);
53 83
54 - List<ImGroupMessageDO> result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 100); 84 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100);
55 85
56 - // 断言:撤回消息一并返回,由客户端按 status 切换渲染  
57 assertEquals(2, result.size()); 86 assertEquals(2, result.size());
58 } 87 }
59 88
60 @Test 89 @Test
61 public void testSelectListByMinId_sendTimeWindow() { 90 public void testSelectListByMinId_sendTimeWindow() {
62 // 准备:一条在窗口内,一条在窗口外 91 // 准备:一条在窗口内,一条在窗口外
63 - ImGroupMessageDO inWindow = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 92 + ImGroupMessageDO inWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
64 inWindow.setSendTime(LocalDateTime.now().minusDays(1)); 93 inWindow.setSendTime(LocalDateTime.now().minusDays(1));
65 mapper.insert(inWindow); 94 mapper.insert(inWindow);
66 - ImGroupMessageDO outWindow = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 95 + ImGroupMessageDO outWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
67 outWindow.setSendTime(LocalDateTime.now().minusDays(40)); 96 outWindow.setSendTime(LocalDateTime.now().minusDays(40));
68 mapper.insert(outWindow); 97 mapper.insert(outWindow);
69 98
70 - List<ImGroupMessageDO> result = mapper.selectListByMinId(ListUtil.of(10L), 0L, 99 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L,
71 LocalDateTime.now().minusDays(30), 100); 100 LocalDateTime.now().minusDays(30), 100);
72 101
73 assertEquals(1, result.size()); 102 assertEquals(1, result.size());
@@ -76,16 +105,16 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { @@ -76,16 +105,16 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest {
76 105
77 @Test 106 @Test
78 public void testSelectListByMinId_sortAscLimit() { 107 public void testSelectListByMinId_sortAscLimit() {
79 - // 准备:插入 3 条  
80 - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 108 + // 准备:插入 3 条可见消息
  109 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
81 mapper.insert(m1); 110 mapper.insert(m1);
82 - ImGroupMessageDO m2 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 111 + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
83 mapper.insert(m2); 112 mapper.insert(m2);
84 - ImGroupMessageDO m3 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 113 + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
85 mapper.insert(m3); 114 mapper.insert(m3);
86 115
87 // 调用:size=2,返回 id 最小的 2 条 116 // 调用:size=2,返回 id 最小的 2 条
88 - List<ImGroupMessageDO> result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 2); 117 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 2);
89 118
90 assertEquals(2, result.size()); 119 assertEquals(2, result.size());
91 assertTrue(result.get(0).getId() < result.get(1).getId()); 120 assertTrue(result.get(0).getId() < result.get(1).getId());
@@ -93,129 +122,63 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { @@ -93,129 +122,63 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest {
93 assertEquals(m2.getId(), result.get(1).getId()); 122 assertEquals(m2.getId(), result.get(1).getId());
94 } 123 }
95 124
96 - // ========== selectListByGroupIdAndMinIdAndQuitTimeBefore ==========  
97 -  
98 - @Test  
99 - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_onlyReturnsBeforeQuit() {  
100 - // 准备:退群时间 = now-1 day,插入一条"退群前",一条"退群后"  
101 - LocalDateTime quitTime = LocalDateTime.now().minusDays(1);  
102 - ImGroupMessageDO before = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
103 - before.setSendTime(LocalDateTime.now().minusDays(2));  
104 - mapper.insert(before);  
105 - ImGroupMessageDO after = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
106 - after.setSendTime(LocalDateTime.now().minusHours(1));  
107 - mapper.insert(after);  
108 -  
109 - List<ImGroupMessageDO> result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(  
110 - 10L, 0L, FAR_PAST, quitTime, 100);  
111 -  
112 - // 断言:只返回退群前消息  
113 - assertEquals(1, result.size());  
114 - assertEquals(before.getId(), result.get(0).getId());  
115 - }  
116 -  
117 @Test 125 @Test
118 - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_boundaryEqualsQuitTime() {  
119 - // 准备:消息发送时间恰好等于退群时间(le 语义应包含该条)  
120 - LocalDateTime quitTime = LocalDateTime.now().minusDays(1).withNano(0);  
121 - ImGroupMessageDO boundary = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
122 - boundary.setSendTime(quitTime);  
123 - mapper.insert(boundary);  
124 -  
125 - List<ImGroupMessageDO> result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(  
126 - 10L, 0L, FAR_PAST, quitTime, 100);  
127 -  
128 - assertEquals(1, result.size());  
129 - } 126 + public void testSelectListByMinId_minIdExclusive() {
  127 + // 准备:游标之前的消息不返回
  128 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
  129 + mapper.insert(m1);
  130 + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
  131 + mapper.insert(m2);
130 132
131 - @Test  
132 - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_otherGroupExcluded() {  
133 - // 准备:群 10 和群 20 各一条消息  
134 - LocalDateTime quitTime = LocalDateTime.now();  
135 - ImGroupMessageDO in10 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
136 - in10.setSendTime(LocalDateTime.now().minusHours(2));  
137 - mapper.insert(in10);  
138 - ImGroupMessageDO in20 = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD);  
139 - in20.setSendTime(LocalDateTime.now().minusHours(2));  
140 - mapper.insert(in20);  
141 -  
142 - List<ImGroupMessageDO> result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(  
143 - 10L, 0L, FAR_PAST, quitTime, 100); 133 + List<ImGroupMessageDO> result = mapper.selectListByMinId(1L, List.of(10L), m1.getId(), FAR_PAST, 100);
144 134
145 assertEquals(1, result.size()); 135 assertEquals(1, result.size());
146 - assertEquals(in10.getId(), result.get(0).getId()); 136 + assertEquals(m2.getId(), result.get(0).getId());
147 } 137 }
148 138
149 - // ========== selectHistoryList ========== 139 + // ========== selectHistoryListByUser ==========
150 140
151 @Test 141 @Test
152 - public void testSelectHistoryList_basic() {  
153 - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 142 + public void testSelectHistoryListByUser_filterByReceiverSnapshot() {
  143 + // 准备:群 10 三条,用户 1 在前两条快照内、第三条定向给别人
  144 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
154 mapper.insert(m1); 145 mapper.insert(m1);
155 - ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.UNREAD); 146 + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L));
156 mapper.insert(m2); 147 mapper.insert(m2);
  148 + ImGroupMessageDO directedOther = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L));
  149 + mapper.insert(directedOther);
157 // 别的群 150 // 别的群
158 - ImGroupMessageDO other = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD); 151 + ImGroupMessageDO other = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
159 mapper.insert(other); 152 mapper.insert(other);
160 153
161 - List<ImGroupMessageDO> result = mapper.selectHistoryList(10L, null, 100, null);  
162 -  
163 - // 断言:只返回 10 群的,按 id 倒序  
164 - assertEquals(2, result.size());  
165 - assertTrue(result.get(0).getId() > result.get(1).getId());  
166 - }  
167 -  
168 - @Test  
169 - public void testSelectHistoryList_includeRecall() {  
170 - ImGroupMessageDO normal = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
171 - mapper.insert(normal);  
172 - ImGroupMessageDO recalled = buildMessage(10L, 1L, ImMessageStatusEnum.RECALL);  
173 - mapper.insert(recalled);  
174 -  
175 - List<ImGroupMessageDO> result = mapper.selectHistoryList(10L, null, 100, null); 154 + List<ImGroupMessageDO> result = mapper.selectHistoryListByUser(1L, 10L, null, 100);
176 155
  156 + // 断言:只返回群 10 中用户 1 可见的,按 id 倒序
177 assertEquals(2, result.size()); 157 assertEquals(2, result.size());
178 - assertEquals(recalled.getId(), result.get(0).getId());  
179 - assertEquals(normal.getId(), result.get(1).getId()); 158 + assertEquals(m2.getId(), result.get(0).getId());
  159 + assertEquals(m1.getId(), result.get(1).getId());
180 } 160 }
181 161
182 @Test 162 @Test
183 - public void testSelectHistoryList_maxIdCursor() {  
184 - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 163 + public void testSelectHistoryListByUser_maxIdCursor() {
  164 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
185 mapper.insert(m1); 165 mapper.insert(m1);
186 - ImGroupMessageDO m2 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 166 + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
187 mapper.insert(m2); 167 mapper.insert(m2);
188 - ImGroupMessageDO m3 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); 168 + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L));
189 mapper.insert(m3); 169 mapper.insert(m3);
190 170
191 - List<ImGroupMessageDO> result = mapper.selectHistoryList(10L, m3.getId(), 100, null); 171 + List<ImGroupMessageDO> result = mapper.selectHistoryListByUser(1L, 10L, m3.getId(), 100);
192 172
193 - // 断言:只返回 id < m3.id 的,即 m1、m2 173 + // 断言:只返回 id < m3.id 的 m1、m2
194 assertEquals(2, result.size()); 174 assertEquals(2, result.size());
195 assertTrue(result.stream().allMatch(m -> m.getId() < m3.getId())); 175 assertTrue(result.stream().allMatch(m -> m.getId() < m3.getId()));
196 } 176 }
197 177
198 - @Test  
199 - public void testSelectHistoryList_filterByJoinTime() {  
200 - // 准备:一条在入群前,一条在入群后  
201 - LocalDateTime joinTime = LocalDateTime.now().minusHours(1);  
202 - ImGroupMessageDO before = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
203 - before.setSendTime(LocalDateTime.now().minusHours(2));  
204 - mapper.insert(before);  
205 - ImGroupMessageDO after = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD);  
206 - after.setSendTime(LocalDateTime.now().minusMinutes(30));  
207 - mapper.insert(after);  
208 -  
209 - List<ImGroupMessageDO> result = mapper.selectHistoryList(10L, null, 100, joinTime);  
210 -  
211 - // 断言:入群前消息不可见  
212 - assertEquals(1, result.size());  
213 - assertEquals(after.getId(), result.get(0).getId());  
214 - }  
215 -  
216 // ========== 工具方法 ========== 178 // ========== 工具方法 ==========
217 179
218 - private ImGroupMessageDO buildMessage(Long groupId, Long senderId, ImMessageStatusEnum status) { 180 + private ImGroupMessageDO buildMessage(Long groupId, Long senderId, ImMessageStatusEnum status,
  181 + List<Long> receiverUserIds) {
219 return ImGroupMessageDO.builder() 182 return ImGroupMessageDO.builder()
220 .clientMessageId("uuid-" + System.nanoTime()) 183 .clientMessageId("uuid-" + System.nanoTime())
221 .senderId(senderId) 184 .senderId(senderId)
@@ -224,7 +187,8 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { @@ -224,7 +187,8 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest {
224 .content("{\"content\":\"test\"}") 187 .content("{\"content\":\"test\"}")
225 .status(status.getStatus()) 188 .status(status.getStatus())
226 .sendTime(LocalDateTime.now()) 189 .sendTime(LocalDateTime.now())
227 - .receiptStatus(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) 190 + .receiverUserIds(receiverUserIds)
  191 + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus())
228 .build(); 192 .build();
229 } 193 }
230 194
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java
@@ -12,7 +12,7 @@ import java.util.List; @@ -12,7 +12,7 @@ import java.util.List;
12 import static org.junit.jupiter.api.Assertions.*; 12 import static org.junit.jupiter.api.Assertions.*;
13 13
14 /** 14 /**
15 - * IM 私聊消息 Mapper 单元测试 15 + * {@link ImPrivateMessageMapper} 的单元测试
16 * 16 *
17 * @author 芋道源码 17 * @author 芋道源码
18 */ 18 */
@@ -21,9 +21,6 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -21,9 +21,6 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
21 @Resource 21 @Resource
22 private ImPrivateMessageMapper mapper; 22 private ImPrivateMessageMapper mapper;
23 23
24 - /**  
25 - * 一个足够早的时间作为 minSendTime,避免把测试数据过滤掉  
26 - */  
27 private static final LocalDateTime FAR_PAST = LocalDateTime.of(2000, 1, 1, 0, 0, 0); 24 private static final LocalDateTime FAR_PAST = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
28 25
29 // ========== selectListByMinId ========== 26 // ========== selectListByMinId ==========
@@ -31,13 +28,13 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -31,13 +28,13 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
31 @Test 28 @Test
32 public void testSelectListByMinId() { 29 public void testSelectListByMinId() {
33 // 准备:用户 1 发给用户 2 30 // 准备:用户 1 发给用户 2
34 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 31 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
35 mapper.insert(msg1); 32 mapper.insert(msg1);
36 // 用户 2 发给用户 1 33 // 用户 2 发给用户 1
37 - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); 34 + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL);
38 mapper.insert(msg2); 35 mapper.insert(msg2);
39 // 用户 3 发给用户 4(不相关) 36 // 用户 3 发给用户 4(不相关)
40 - ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.UNREAD); 37 + ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.NORMAL);
41 mapper.insert(msg3); 38 mapper.insert(msg3);
42 39
43 // 调用:用户 1 从 id=0 拉取 40 // 调用:用户 1 从 id=0 拉取
@@ -52,9 +49,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -52,9 +49,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
52 @Test 49 @Test
53 public void testSelectListByMinId_withMinId() { 50 public void testSelectListByMinId_withMinId() {
54 // 准备 51 // 准备
55 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 52 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
56 mapper.insert(msg1); 53 mapper.insert(msg1);
57 - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); 54 + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL);
58 mapper.insert(msg2); 55 mapper.insert(msg2);
59 56
60 // 调用:从 msg1.id 之后拉取 57 // 调用:从 msg1.id 之后拉取
@@ -68,9 +65,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -68,9 +65,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
68 @Test 65 @Test
69 public void testSelectListByMinId_limitSize() { 66 public void testSelectListByMinId_limitSize() {
70 // 准备:插入 3 条 67 // 准备:插入 3 条
71 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD));  
72 - mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD));  
73 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); 68 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL));
  69 + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL));
  70 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL));
74 71
75 // 调用:limit 2 72 // 调用:limit 2
76 List<ImPrivateMessageDO> result = mapper.selectListByMinId(1L, 0L, FAR_PAST, 2); 73 List<ImPrivateMessageDO> result = mapper.selectListByMinId(1L, 0L, FAR_PAST, 2);
@@ -82,10 +79,10 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -82,10 +79,10 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
82 @Test 79 @Test
83 public void testSelectListByMinId_sendTimeFilter() { 80 public void testSelectListByMinId_sendTimeFilter() {
84 // 准备:一条落在窗口内,一条落在窗口外 81 // 准备:一条落在窗口内,一条落在窗口外
85 - ImPrivateMessageDO newMsg = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 82 + ImPrivateMessageDO newMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
86 newMsg.setSendTime(LocalDateTime.now().minusDays(1)); 83 newMsg.setSendTime(LocalDateTime.now().minusDays(1));
87 mapper.insert(newMsg); 84 mapper.insert(newMsg);
88 - ImPrivateMessageDO oldMsg = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 85 + ImPrivateMessageDO oldMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
89 oldMsg.setSendTime(LocalDateTime.now().minusDays(40)); 86 oldMsg.setSendTime(LocalDateTime.now().minusDays(40));
90 mapper.insert(oldMsg); 87 mapper.insert(oldMsg);
91 88
@@ -103,12 +100,12 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -103,12 +100,12 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
103 @Test 100 @Test
104 public void testSelectHistoryList_basic() { 101 public void testSelectHistoryList_basic() {
105 // 准备:用户 1 <-> 用户 2 的消息 102 // 准备:用户 1 <-> 用户 2 的消息
106 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 103 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
107 mapper.insert(msg1); 104 mapper.insert(msg1);
108 - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); 105 + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL);
109 mapper.insert(msg2); 106 mapper.insert(msg2);
110 // 用户 1 <-> 用户 3 的消息(不相关) 107 // 用户 1 <-> 用户 3 的消息(不相关)
111 - ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.UNREAD); 108 + ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.NORMAL);
112 mapper.insert(msg3); 109 mapper.insert(msg3);
113 110
114 // 调用 111 // 调用
@@ -122,7 +119,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -122,7 +119,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
122 @Test 119 @Test
123 public void testSelectHistoryList_includeRecall() { 120 public void testSelectHistoryList_includeRecall() {
124 // 准备 121 // 准备
125 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 122 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
126 mapper.insert(msg1); 123 mapper.insert(msg1);
127 ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.RECALL); 124 ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.RECALL);
128 mapper.insert(msg2); 125 mapper.insert(msg2);
@@ -139,11 +136,11 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -139,11 +136,11 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
139 @Test 136 @Test
140 public void testSelectHistoryList_withMaxId() { 137 public void testSelectHistoryList_withMaxId() {
141 // 准备 138 // 准备
142 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 139 + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
143 mapper.insert(msg1); 140 mapper.insert(msg1);
144 - ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 141 + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
145 mapper.insert(msg2); 142 mapper.insert(msg2);
146 - ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); 143 + ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL);
147 mapper.insert(msg3); 144 mapper.insert(msg3);
148 145
149 // 调用:从 msg3 往前拉 146 // 调用:从 msg3 往前拉
@@ -157,9 +154,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -157,9 +154,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
157 @Test 154 @Test
158 public void testSelectHistoryList_limit() { 155 public void testSelectHistoryList_limit() {
159 // 准备 156 // 准备
160 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD));  
161 - mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD));  
162 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); 157 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL));
  158 + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL));
  159 + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL));
163 160
164 // 调用:limit 2 161 // 调用:limit 2
165 List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 2); 162 List<ImPrivateMessageDO> result = mapper.selectHistoryList(1L, 2L, null, 2);
@@ -171,7 +168,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @@ -171,7 +168,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest {
171 @Test 168 @Test
172 public void testSelectHistoryList_bidirectional() { 169 public void testSelectHistoryList_bidirectional() {
173 // 准备:验证双向查询 170 // 准备:验证双向查询
174 - ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); // 对方发的 171 + ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); // 对方发的
175 mapper.insert(msg1); 172 mapper.insert(msg1);
176 173
177 // 调用:以用户 1 的视角查 174 // 调用:以用户 1 的视角查
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java
@@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
11 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; 11 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
12 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 12 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
13 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 13 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
14 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 14 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
15 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
16 import org.junit.jupiter.api.BeforeEach; 16 import org.junit.jupiter.api.BeforeEach;
17 import org.junit.jupiter.api.Test; 17 import org.junit.jupiter.api.Test;
@@ -25,7 +25,7 @@ import java.util.Map; @@ -25,7 +25,7 @@ import java.util.Map;
25 import static org.junit.jupiter.api.Assertions.*; 25 import static org.junit.jupiter.api.Assertions.*;
26 26
27 /** 27 /**
28 - * IM 数据看板 Mapper 单元测试 28 + * {@link ImStatisticsManagerMapper} 的单元测试
29 * 29 *
30 * @author 芋道源码 30 * @author 芋道源码
31 */ 31 */
@@ -194,10 +194,10 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -194,10 +194,10 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
194 @Test 194 @Test
195 public void testSelectMessageTypeDistribution_mergePrivateAndGroup() { 195 public void testSelectMessageTypeDistribution_mergePrivateAndGroup() {
196 // 准备:私聊 type=0 ×1,群聊 type=0 ×2、type=1 ×1 196 // 准备:私聊 type=0 ×1,群聊 type=0 ×2、type=1 ×1
197 - privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType()));  
198 - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType()));  
199 - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType()));  
200 - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.IMAGE.getType())); 197 + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType()));
  198 + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType()));
  199 + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType()));
  200 + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.IMAGE.getType()));
201 201
202 // 调用 202 // 调用
203 List<Map<String, Object>> dist = mapper.selectMessageTypeDistribution(WINDOW_BEGIN, WINDOW_END); 203 List<Map<String, Object>> dist = mapper.selectMessageTypeDistribution(WINDOW_BEGIN, WINDOW_END);
@@ -205,8 +205,8 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -205,8 +205,8 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
205 // 断言:type=TEXT(0) → 3;type=IMAGE(1) → 1 205 // 断言:type=TEXT(0) → 3;type=IMAGE(1) → 1
206 Map<Object, Long> byType = dist.stream().collect(java.util.stream.Collectors.toMap( 206 Map<Object, Long> byType = dist.stream().collect(java.util.stream.Collectors.toMap(
207 m -> ((Number) m.get("type")).intValue(), m -> ((Number) m.get("count")).longValue())); 207 m -> ((Number) m.get("type")).intValue(), m -> ((Number) m.get("count")).longValue()));
208 - assertEquals(3L, byType.get(ImMessageTypeEnum.TEXT.getType()));  
209 - assertEquals(1L, byType.get(ImMessageTypeEnum.IMAGE.getType())); 208 + assertEquals(3L, byType.get(ImContentTypeEnum.TEXT.getType()));
  209 + assertEquals(1L, byType.get(ImContentTypeEnum.IMAGE.getType()));
210 } 210 }
211 211
212 @Test 212 @Test
@@ -243,9 +243,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -243,9 +243,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
243 return ImPrivateMessageDO.builder() 243 return ImPrivateMessageDO.builder()
244 .clientMessageId("uuid-" + System.nanoTime()) 244 .clientMessageId("uuid-" + System.nanoTime())
245 .senderId(senderId).receiverId(receiverId) 245 .senderId(senderId).receiverId(receiverId)
246 - .type(ImMessageTypeEnum.TEXT.getType()) 246 + .type(ImContentTypeEnum.TEXT.getType())
247 .content("{}") 247 .content("{}")
248 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 248 + .status(ImMessageStatusEnum.NORMAL.getStatus())
249 .sendTime(sendTime).build(); 249 .sendTime(sendTime).build();
250 } 250 }
251 251
@@ -253,9 +253,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -253,9 +253,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
253 return new ImGroupMessageDO() 253 return new ImGroupMessageDO()
254 .setClientMessageId("uuid-" + System.nanoTime()) 254 .setClientMessageId("uuid-" + System.nanoTime())
255 .setSenderId(senderId).setGroupId(groupId) 255 .setSenderId(senderId).setGroupId(groupId)
256 - .setType(ImMessageTypeEnum.TEXT.getType()) 256 + .setType(ImContentTypeEnum.TEXT.getType())
257 .setContent("{}") 257 .setContent("{}")
258 - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()) 258 + .setStatus(ImMessageStatusEnum.NORMAL.getStatus())
259 .setSendTime(sendTime); 259 .setSendTime(sendTime);
260 } 260 }
261 261
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.verify; @@ -26,7 +26,7 @@ import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when; 26 import static org.mockito.Mockito.when;
27 27
28 /** 28 /**
29 - * IM 表情包项 Service 单元测试 29 + * {@link ImFacePackItemServiceImpl} 的单元测试
30 * 30 *
31 * @author 芋道源码 31 * @author 芋道源码
32 */ 32 */
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java
@@ -25,7 +25,7 @@ import static org.mockito.Mockito.verify; @@ -25,7 +25,7 @@ import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when; 25 import static org.mockito.Mockito.when;
26 26
27 /** 27 /**
28 - * IM 表情包 Service 单元测试 28 + * {@link ImFacePackServiceImpl} 的单元测试
29 * 29 *
30 * @author 芋道源码 30 * @author 芋道源码
31 */ 31 */
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.verify; @@ -26,7 +26,7 @@ import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when; 26 import static org.mockito.Mockito.when;
27 27
28 /** 28 /**
29 - * IM 用户私有表情 Service 单元测试 29 + * {@link ImFaceUserItemServiceImpl} 的单元测试
30 * 30 *
31 * @author 芋道源码 31 * @author 芋道源码
32 */ 32 */
@@ -141,7 +141,7 @@ public class ImFaceUserItemServiceImplTest extends BaseMockitoUnitTest { @@ -141,7 +141,7 @@ public class ImFaceUserItemServiceImplTest extends BaseMockitoUnitTest {
141 verify(faceUserItemMapper, never()).deleteById(anyLong()); 141 verify(faceUserItemMapper, never()).deleteById(anyLong());
142 } 142 }
143 143
144 - // ========== deleteFaceUserItem(管理后台 by id) ========== 144 + // ========== deleteFaceUserItem(管理后台) ==========
145 145
146 @Test 146 @Test
147 public void testDeleteFaceUserItemAdmin_success() { 147 public void testDeleteFaceUserItemAdmin_success() {
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java
1 package cn.iocoder.yudao.module.im.service.friend; 1 package cn.iocoder.yudao.module.im.service.friend;
2 2
3 -import cn.hutool.core.collection.ListUtil;  
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.exception.ServiceException; 4 import cn.iocoder.yudao.framework.common.exception.ServiceException;
6 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
@@ -12,7 +11,6 @@ import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; @@ -12,7 +11,6 @@ import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum;
12 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; 11 import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum;
13 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 12 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
14 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 13 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
15 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
16 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
17 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 15 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
18 import org.junit.jupiter.api.Test; 16 import org.junit.jupiter.api.Test;
@@ -36,7 +34,7 @@ import static org.mockito.Mockito.verify; @@ -36,7 +34,7 @@ import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when; 34 import static org.mockito.Mockito.when;
37 35
38 /** 36 /**
39 - * IM 好友申请 Service 单元测试 37 + * {@link ImFriendRequestServiceImpl} 的单元测试
40 * 38 *
41 * @author 芋道源码 39 * @author 芋道源码
42 */ 40 */
@@ -113,7 +111,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -113,7 +111,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
113 assertNull(result); 111 assertNull(result);
114 verify(friendService).silentReAddFriend(eq(1L), eq(2L), eq("老张"), eq(1)); 112 verify(friendService).silentReAddFriend(eq(1L), eq(2L), eq("老张"), eq(1));
115 verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class)); 113 verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class));
116 - verify(websocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 114 + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
117 } 115 }
118 116
119 @Test 117 @Test
@@ -158,7 +156,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -158,7 +156,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
158 assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), saved.getHandleResult()); 156 assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), saved.getHandleResult());
159 assertSame(saved, result); 157 assertSame(saved, result);
160 // 断言:推送给接收方 158 // 断言:推送给接收方
161 - verify(websocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); 159 + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any());
162 } 160 }
163 161
164 @Test 162 @Test
@@ -211,7 +209,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -211,7 +209,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
211 any(LocalDateTime.class)); 209 any(LocalDateTime.class));
212 assertEquals(100L, result.getId()); 210 assertEquals(100L, result.getId());
213 assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); 211 assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult());
214 - verify(websocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); 212 + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any());
215 } 213 }
216 214
217 // ========== agreeFriendRequest ========== 215 // ========== agreeFriendRequest ==========
@@ -229,9 +227,9 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -229,9 +227,9 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
229 friendRequestService.agreeFriendRequest(2L, 100L); 227 friendRequestService.agreeFriendRequest(2L, 100L);
230 228
231 // 断言:双向建立好友 + 推 APPROVED 给发起方 229 // 断言:双向建立好友 + 推 APPROVED 给发起方
232 - verify(adminUserApi).validateUserList(ListUtil.of(1L, 2L)); 230 + verify(adminUserApi, never()).validateUserList(any());
233 verify(friendService).becomeFriends(request); 231 verify(friendService).becomeFriends(request);
234 - verify(websocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); 232 + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
235 } 233 }
236 234
237 @Test 235 @Test
@@ -306,7 +304,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -306,7 +304,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
306 eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), captor.capture()); 304 eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), captor.capture());
307 assertEquals(ImFriendRequestHandleResultEnum.REFUSED.getResult(), captor.getValue().getHandleResult()); 305 assertEquals(ImFriendRequestHandleResultEnum.REFUSED.getResult(), captor.getValue().getHandleResult());
308 assertEquals("不认识", captor.getValue().getHandleContent()); 306 assertEquals("不认识", captor.getValue().getHandleContent());
309 - verify(websocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); 307 + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
310 } 308 }
311 309
312 @Test 310 @Test
@@ -321,7 +319,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { @@ -321,7 +319,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest {
321 ServiceException exception = assertThrows(ServiceException.class, 319 ServiceException exception = assertThrows(ServiceException.class,
322 () -> friendRequestService.refuseFriendRequest(2L, 100L, "x")); 320 () -> friendRequestService.refuseFriendRequest(2L, 100L, "x"));
323 assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode()); 321 assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode());
324 - verify(websocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 322 + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
325 } 323 }
326 324
327 // ========== getMyFriendRequestList ========== 325 // ========== getMyFriendRequestList ==========
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; @@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
10 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; 10 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
11 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 11 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
12 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 12 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
13 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 13 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
15 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.Test;
16 import org.mockito.ArgumentCaptor; 15 import org.mockito.ArgumentCaptor;
@@ -30,7 +29,7 @@ import static org.mockito.Mockito.verify; @@ -30,7 +29,7 @@ import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when; 29 import static org.mockito.Mockito.when;
31 30
32 /** 31 /**
33 - * IM 好友关系 Service 单元测试 32 + * {@link ImFriendServiceImpl} 的单元测试
34 * 33 *
35 * @author 芋道源码 34 * @author 芋道源码
36 */ 35 */
@@ -69,7 +68,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -69,7 +68,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
69 assertEquals(100L, captor.getValue().getId()); 68 assertEquals(100L, captor.getValue().getId());
70 assertTrue(captor.getValue().getSilent()); 69 assertTrue(captor.getValue().getSilent());
71 // 断言:推送了好友更新通知 70 // 断言:推送了好友更新通知
72 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); 71 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
73 } 72 }
74 73
75 @Test 74 @Test
@@ -101,7 +100,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -101,7 +100,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
101 () -> friendService.updateFriend(1L, reqVO)); 100 () -> friendService.updateFriend(1L, reqVO));
102 assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode()); 101 assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode());
103 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); 102 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class));
104 - verify(imWebSocketService, never()).sendPrivateMessageAsync(any(Long.class), any(ImPrivateMessageDTO.class)); 103 + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any());
105 } 104 }
106 105
107 @Test 106 @Test
@@ -124,7 +123,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -124,7 +123,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
124 assertEquals("老张", captor.getValue().getDisplayName()); 123 assertEquals("老张", captor.getValue().getDisplayName());
125 assertNull(captor.getValue().getSilent()); 124 assertNull(captor.getValue().getSilent());
126 // 断言:推送好友更新通知 125 // 断言:推送好友更新通知
127 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); 126 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
128 } 127 }
129 128
130 @Test 129 @Test
@@ -139,10 +138,10 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -139,10 +138,10 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
139 // 断言:没查记录、没触发 SQL 更新 / 没发 WebSocket 推送 138 // 断言:没查记录、没触发 SQL 更新 / 没发 WebSocket 推送
140 verify(imFriendMapper, never()).selectByUserIdAndFriendUserId(anyLong(), anyLong()); 139 verify(imFriendMapper, never()).selectByUserIdAndFriendUserId(anyLong(), anyLong());
141 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); 140 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class));
142 - verify(imWebSocketService, never()).sendPrivateMessageAsync(any(Long.class), any(ImPrivateMessageDTO.class)); 141 + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any());
143 } 142 }
144 143
145 - // ========== addFriend0(内部方法,被 becomeFriends / silentReAddFriend 调用) ========== 144 + // ========== 建立好友 ==========
146 145
147 @Test 146 @Test
148 public void testAddFriend0_existingEnabledSkip() { 147 public void testAddFriend0_existingEnabledSkip() {
@@ -158,7 +157,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -158,7 +157,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
158 verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); 157 verify(imFriendMapper, never()).insert(any(ImFriendDO.class));
159 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); 158 verify(imFriendMapper, never()).updateById(any(ImFriendDO.class));
160 verify(imFriendMapper, never()).updateReAddFields(anyLong(), anyInt(), any(LocalDateTime.class), 159 verify(imFriendMapper, never()).updateReAddFields(anyLong(), anyInt(), any(LocalDateTime.class),
161 - anyBoolean(), anyBoolean(), anyBoolean(), any(), any()); 160 + any(LocalDateTime.class), anyBoolean(), anyBoolean(), anyBoolean(), any(), any());
162 } 161 }
163 162
164 @Test 163 @Test
@@ -173,7 +172,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { @@ -173,7 +172,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest {
173 172
174 // 断言:恢复 ENABLE,并清空 deleteTime 173 // 断言:恢复 ENABLE,并清空 deleteTime
175 verify(imFriendMapper).updateReAddFields(eq(10L), eq(CommonStatusEnum.ENABLE.getStatus()), 174 verify(imFriendMapper).updateReAddFields(eq(10L), eq(CommonStatusEnum.ENABLE.getStatus()),
176 - any(LocalDateTime.class), eq(false), eq(false), eq(false), isNull(), isNull()); 175 + any(LocalDateTime.class), any(LocalDateTime.class), eq(false), eq(false), eq(false), isNull(), isNull());
177 verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); 176 verify(imFriendMapper, never()).insert(any(ImFriendDO.class));
178 } 177 }
179 178
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java
@@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
9 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; 9 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
10 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 10 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
11 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 11 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
12 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; 12 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification;
13 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
14 import org.mockito.ArgumentCaptor; 14 import org.mockito.ArgumentCaptor;
15 import org.mockito.InjectMocks; 15 import org.mockito.InjectMocks;
@@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.*; @@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.*;
24 import static org.mockito.Mockito.*; 24 import static org.mockito.Mockito.*;
25 25
26 /** 26 /**
27 - * IM 群成员 Service 单元测试 27 + * {@link ImGroupMemberServiceImpl} 的单元测试
28 * 28 *
29 * @author 芋道源码 29 * @author 芋道源码
30 */ 30 */
@@ -242,7 +242,7 @@ public class ImGroupMemberServiceImplTest extends BaseMockitoUnitTest { @@ -242,7 +242,7 @@ public class ImGroupMemberServiceImplTest extends BaseMockitoUnitTest {
242 verify(groupMemberMapper).updateById(captor.capture()); 242 verify(groupMemberMapper).updateById(captor.capture());
243 assertEquals(50L, captor.getValue().getId()); 243 assertEquals(50L, captor.getValue().getId());
244 assertTrue(captor.getValue().getSilent()); 244 assertTrue(captor.getValue().getSilent());
245 - // 公开字段昵称变化 → 全员广播 GROUP_MEMBER_NICKNAME_UPDATE 245 + // 公开字段昵称变化 → 全员在线同步 GROUP_MEMBER_NICKNAME_UPDATE
246 verify(groupMessageService).sendGroupMessage(eq(1L), any(cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO.class)); 246 verify(groupMessageService).sendGroupMessage(eq(1L), any(cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO.class));
247 // 个人字段 silent 变化 → 仅自己多端同步 GROUP_MEMBER_SETTING_UPDATE 247 // 个人字段 silent 变化 → 仅自己多端同步 GROUP_MEMBER_SETTING_UPDATE
248 verify(groupMessageService).sendGroupMessage(eq(1L), eq(ListUtil.of(1L)), 248 verify(groupMessageService).sendGroupMessage(eq(1L), eq(ListUtil.of(1L)),
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java
@@ -13,11 +13,10 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper; @@ -13,11 +13,10 @@ import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupRequestMapper;
13 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; 13 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
14 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 14 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
15 import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; 15 import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum;
16 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 16 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
17 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; 17 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
18 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 18 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
20 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
21 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
23 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.api.Test;
@@ -35,7 +34,7 @@ import static org.mockito.ArgumentMatchers.*; @@ -35,7 +34,7 @@ import static org.mockito.ArgumentMatchers.*;
35 import static org.mockito.Mockito.*; 34 import static org.mockito.Mockito.*;
36 35
37 /** 36 /**
38 - * IM 加群申请 Service 单元测试 37 + * {@link ImGroupRequestServiceImpl} 的单元测试
39 * 38 *
40 * @author 芋道源码 39 * @author 芋道源码
41 */ 40 */
@@ -84,7 +83,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -84,7 +83,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
84 // 推 1510 自由进群 83 // 推 1510 自由进群
85 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 84 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
86 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); 85 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture());
87 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); 86 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType());
88 } 87 }
89 88
90 @Test 89 @Test
@@ -117,7 +116,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -117,7 +116,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
117 verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), anyInt(), anyLong()); 116 verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), anyInt(), anyLong());
118 verify(groupRequestMapper).insert(any(ImGroupRequestDO.class)); 117 verify(groupRequestMapper).insert(any(ImGroupRequestDO.class));
119 // 1503 推送给 owner(99) + admin(98),去重后两条 118 // 1503 推送给 owner(99) + admin(98),去重后两条
120 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 119 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
121 } 120 }
122 121
123 @Test 122 @Test
@@ -150,7 +149,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -150,7 +149,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
150 eq(ImGroupAddSourceEnum.SEARCH.getSource()), any()); 149 eq(ImGroupAddSourceEnum.SEARCH.getSource()), any());
151 assertEquals(50L, result.getId()); 150 assertEquals(50L, result.getId());
152 assertEquals(ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); 151 assertEquals(ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult());
153 - verify(websocketService).sendPrivateMessageAsync(eq(99L), any(ImPrivateMessageDTO.class)); 152 + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any());
154 } 153 }
155 154
156 @Test 155 @Test
@@ -207,9 +206,9 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -207,9 +206,9 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
207 eq(ImGroupAddSourceEnum.SEARCH.getSource()), isNull()); 206 eq(ImGroupAddSourceEnum.SEARCH.getSource()), isNull());
208 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 207 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
209 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); 208 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture());
210 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); 209 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType());
211 // 1505 推送给申请人 + owner,去重后两条 210 // 1505 推送给申请人 + owner,去重后两条
212 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 211 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
213 } 212 }
214 213
215 @Test 214 @Test
@@ -279,7 +278,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -279,7 +278,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
279 278
280 // 不写群成员;推 1506 给申请人 + 群主(同一人 1L 时去重为 1 + 申请人 2L = 2 条) 279 // 不写群成员;推 1506 给申请人 + 群主(同一人 1L 时去重为 1 + 申请人 2L = 2 条)
281 verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), any(), any()); 280 verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), any(), any());
282 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 281 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
283 } 282 }
284 283
285 // ==================== createInviteRequestList ==================== 284 // ==================== createInviteRequestList ====================
@@ -306,7 +305,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -306,7 +305,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
306 // 断言:插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧 305 // 断言:插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧
307 ArgumentCaptor<ImGroupRequestDO> captor = ArgumentCaptor.forClass(ImGroupRequestDO.class); 306 ArgumentCaptor<ImGroupRequestDO> captor = ArgumentCaptor.forClass(ImGroupRequestDO.class);
308 verify(groupRequestMapper, times(2)).insert(captor.capture()); 307 verify(groupRequestMapper, times(2)).insert(captor.capture());
309 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 308 + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
310 // 断言:每条记录 inviterUserId=1 + addSource=INVITE,避免审批通过后回写群成员留痕的来源为空 / 脏带旧值 309 // 断言:每条记录 inviterUserId=1 + addSource=INVITE,避免审批通过后回写群成员留痕的来源为空 / 脏带旧值
311 Collection<ImGroupRequestDO> inserted = captor.getAllValues(); 310 Collection<ImGroupRequestDO> inserted = captor.getAllValues();
312 assertEquals(2, inserted.size()); 311 assertEquals(2, inserted.size());
@@ -338,7 +337,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { @@ -338,7 +337,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest {
338 // 断言:复用并重置旧邀请申请 337 // 断言:复用并重置旧邀请申请
339 verify(groupRequestMapper).updateInviteByIdReset(eq(50L), eq(1L), 338 verify(groupRequestMapper).updateInviteByIdReset(eq(50L), eq(1L),
340 eq(ImGroupAddSourceEnum.INVITE.getSource()), any()); 339 eq(ImGroupAddSourceEnum.INVITE.getSource()), any());
341 - verify(websocketService).sendPrivateMessageAsync(eq(99L), any(ImPrivateMessageDTO.class)); 340 + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any());
342 } 341 }
343 342
344 private AdminUserRespDTO buildUser(Long id, String nickname) { 343 private AdminUserRespDTO buildUser(Long id, String nickname) {
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java
1 package cn.iocoder.yudao.module.im.service.group; 1 package cn.iocoder.yudao.module.im.service.group;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.collection.ListUtil;  
5 -import cn.hutool.core.map.MapUtil;  
6 import cn.hutool.extra.spring.SpringUtil; 3 import cn.hutool.extra.spring.SpringUtil;
7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 import cn.iocoder.yudao.framework.common.exception.ServiceException; 5 import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -22,13 +19,13 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -22,13 +19,13 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
22 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; 19 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
23 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; 20 import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum;
24 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 21 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
25 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 22 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
26 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 23 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
27 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 24 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
28 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; 25 import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
29 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 26 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
30 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 27 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
31 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; 28 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification;
32 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 29 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
33 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 30 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
34 import org.junit.jupiter.api.Test; 31 import org.junit.jupiter.api.Test;
@@ -51,7 +48,7 @@ import static org.mockito.ArgumentMatchers.*; @@ -51,7 +48,7 @@ import static org.mockito.ArgumentMatchers.*;
51 import static org.mockito.Mockito.*; 48 import static org.mockito.Mockito.*;
52 49
53 /** 50 /**
54 - * IM 群 Service 单元测试 51 + * {@link ImGroupServiceImpl} 的单元测试
55 * 52 *
56 * @author 芋道源码 53 * @author 芋道源码
57 */ 54 */
@@ -103,7 +100,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -103,7 +100,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
103 // 验证:推送 GROUP_CREATE 通知(payload memberUserIds 含创建者自己) 100 // 验证:推送 GROUP_CREATE 通知(payload memberUserIds 含创建者自己)
104 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 101 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
105 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); 102 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
106 - assertEquals(ImMessageTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); 103 + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType());
107 } 104 }
108 105
109 @Test 106 @Test
@@ -111,13 +108,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -111,13 +108,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
111 // 准备:创建者 + 2 个初始成员,都是好友 108 // 准备:创建者 + 2 个初始成员,都是好友
112 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); 109 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO();
113 reqVO.setName("测试群"); 110 reqVO.setName("测试群");
114 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); 111 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
115 when(groupMapper.insert(any(ImGroupDO.class))).thenAnswer(invocation -> { 112 when(groupMapper.insert(any(ImGroupDO.class))).thenAnswer(invocation -> {
116 ImGroupDO group = invocation.getArgument(0); 113 ImGroupDO group = invocation.getArgument(0);
117 group.setId(100L); 114 group.setId(100L);
118 return 1; 115 return 1;
119 }); 116 });
120 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 117 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
121 ImFriendDO.builder().userId(1L).friendUserId(2L) 118 ImFriendDO.builder().userId(1L).friendUserId(2L)
122 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 119 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
123 ImFriendDO.builder().userId(1L).friendUserId(3L) 120 ImFriendDO.builder().userId(1L).friendUserId(3L)
@@ -135,7 +132,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -135,7 +132,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
135 // 验证:推送 GROUP_CREATE 通知,payload memberUserIds 含全员(创建者 + 邀请) 132 // 验证:推送 GROUP_CREATE 通知,payload memberUserIds 含全员(创建者 + 邀请)
136 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 133 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
137 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); 134 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
138 - assertEquals(ImMessageTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); 135 + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType());
139 } 136 }
140 137
141 @Test 138 @Test
@@ -143,15 +140,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -143,15 +140,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
143 // 准备:初始成员里有非好友 140 // 准备:初始成员里有非好友
144 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); 141 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO();
145 reqVO.setName("测试群"); 142 reqVO.setName("测试群");
146 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); 143 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
147 // 只有 2 是好友,3 不是 144 // 只有 2 是好友,3 不是
148 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 145 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
149 ImFriendDO.builder().userId(1L).friendUserId(2L) 146 ImFriendDO.builder().userId(1L).friendUserId(2L)
150 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 147 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
151 AdminUserRespDTO u3 = new AdminUserRespDTO(); 148 AdminUserRespDTO u3 = new AdminUserRespDTO();
152 u3.setId(3L); 149 u3.setId(3L);
153 u3.setNickname("李四"); 150 u3.setNickname("李四");
154 - when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(3L, u3)); 151 + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3));
155 152
156 // 调用并断言 153 // 调用并断言
157 ServiceException exception = assertThrows(ServiceException.class, 154 ServiceException exception = assertThrows(ServiceException.class,
@@ -208,7 +205,59 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -208,7 +205,59 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
208 // 推送 GROUP_NAME_UPDATE 通知给全员 205 // 推送 GROUP_NAME_UPDATE 通知给全员
209 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 206 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
210 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); 207 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
211 - assertEquals(ImMessageTypeEnum.GROUP_NAME_UPDATE.getType(), dtoCaptor.getValue().getType()); 208 + assertEquals(ImContentTypeEnum.GROUP_NAME_UPDATE.getType(), dtoCaptor.getValue().getType());
  209 + }
  210 + }
  211 +
  212 + @Test
  213 + public void testUpdateGroup_joinApprovalChanged() {
  214 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  215 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class)))
  216 + .thenReturn(groupService);
  217 +
  218 + // 准备
  219 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L)
  220 + .joinApproval(false).status(CommonStatusEnum.ENABLE.getStatus()).build();
  221 + when(groupMapper.selectById(10L)).thenReturn(group);
  222 +
  223 + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO();
  224 + reqVO.setId(10L);
  225 + reqVO.setJoinApproval(true);
  226 +
  227 + // 调用
  228 + ImGroupDO result = groupService.updateGroup(reqVO, 1L);
  229 +
  230 + // 断言
  231 + verify(groupMapper).updateById(any(ImGroupDO.class));
  232 + assertTrue(result.getJoinApproval());
  233 + ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
  234 + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
  235 + assertEquals(ImContentTypeEnum.GROUP_INFO_UPDATE.getType(), dtoCaptor.getValue().getType());
  236 + }
  237 + }
  238 +
  239 + @Test
  240 + public void testUpdateGroup_joinApprovalSame() {
  241 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  242 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class)))
  243 + .thenReturn(groupService);
  244 +
  245 + // 准备
  246 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L)
  247 + .joinApproval(true).status(CommonStatusEnum.ENABLE.getStatus()).build();
  248 + when(groupMapper.selectById(10L)).thenReturn(group);
  249 +
  250 + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO();
  251 + reqVO.setId(10L);
  252 + reqVO.setJoinApproval(true);
  253 +
  254 + // 调用
  255 + ImGroupDO result = groupService.updateGroup(reqVO, 1L);
  256 +
  257 + // 断言
  258 + verify(groupMapper).updateById(any(ImGroupDO.class));
  259 + assertTrue(result.getJoinApproval());
  260 + verify(groupMessageService, never()).sendGroupMessage(anyLong(), anyCollection(), any());
212 } 261 }
213 } 262 }
214 263
@@ -234,11 +283,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -234,11 +283,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
234 assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); 283 assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus());
235 assertNotNull(captor.getValue().getDissolvedTime()); 284 assertNotNull(captor.getValue().getDissolvedTime());
236 verify(groupMemberService).removeGroupMembersByGroupId(10L); 285 verify(groupMemberService).removeGroupMembersByGroupId(10L);
237 - verify(groupMessageService).deleteReadMaxMessageIdMap(10L);  
238 // 推送 GROUP_DISSOLVE 通知(send-before-remove,sendGroupMessage 内部查 active 自动覆盖全员,含群主多端同步) 286 // 推送 GROUP_DISSOLVE 通知(send-before-remove,sendGroupMessage 内部查 active 自动覆盖全员,含群主多端同步)
239 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 287 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
240 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); 288 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture());
241 - assertEquals(ImMessageTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); 289 + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType());
242 } 290 }
243 } 291 }
244 292
@@ -280,9 +328,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -280,9 +328,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
280 // 断言:使用管理员编号发通知并完成清理 328 // 断言:使用管理员编号发通知并完成清理
281 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 329 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
282 verify(groupMessageService).sendGroupMessage(eq(99L), dtoCaptor.capture()); 330 verify(groupMessageService).sendGroupMessage(eq(99L), dtoCaptor.capture());
283 - assertEquals(ImMessageTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); 331 + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType());
284 verify(groupMemberService).removeGroupMembersByGroupId(10L); 332 verify(groupMemberService).removeGroupMembersByGroupId(10L);
285 - verify(groupMessageService).deleteReadMaxMessageIdMap(10L);  
286 } 333 }
287 } 334 }
288 335
@@ -330,8 +377,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -330,8 +377,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
330 // 被邀请人 2 和 3 都是好友 377 // 被邀请人 2 和 3 都是好友
331 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 378 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
332 reqVO.setGroupId(10L); 379 reqVO.setGroupId(10L);
333 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L)));  
334 - List<ImFriendDO> friends = ListUtil.of( 380 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
  381 + List<ImFriendDO> friends = List.of(
335 ImFriendDO.builder().userId(1L).friendUserId(2L) 382 ImFriendDO.builder().userId(1L).friendUserId(2L)
336 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 383 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
337 ImFriendDO.builder().userId(1L).friendUserId(3L) 384 ImFriendDO.builder().userId(1L).friendUserId(3L)
@@ -347,10 +394,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -347,10 +394,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
347 verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), 394 verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(),
348 eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); 395 eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L));
349 verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); 396 verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection());
350 - verify(webSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); 397 + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any());
351 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 398 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
352 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); 399 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
353 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); 400 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType());
354 } 401 }
355 } 402 }
356 403
@@ -369,7 +416,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -369,7 +416,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
369 ImGroupMemberDO.builder().groupId(10L).userId(1L) 416 ImGroupMemberDO.builder().groupId(10L).userId(1L)
370 .role(ImGroupMemberRoleEnum.NORMAL.getRole()) 417 .role(ImGroupMemberRoleEnum.NORMAL.getRole())
371 .status(CommonStatusEnum.ENABLE.getStatus()).build()); 418 .status(CommonStatusEnum.ENABLE.getStatus()).build());
372 - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( 419 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of(
373 ImGroupMemberDO.builder().groupId(10L).userId(99L) 420 ImGroupMemberDO.builder().groupId(10L).userId(99L)
374 .role(ImGroupMemberRoleEnum.OWNER.getRole()) 421 .role(ImGroupMemberRoleEnum.OWNER.getRole())
375 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 422 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
@@ -379,8 +426,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -379,8 +426,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
379 426
380 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 427 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
381 reqVO.setGroupId(10L); 428 reqVO.setGroupId(10L);
382 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L)));  
383 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 429 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
  430 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
384 ImFriendDO.builder().userId(1L).friendUserId(2L) 431 ImFriendDO.builder().userId(1L).friendUserId(2L)
385 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 432 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
386 ImFriendDO.builder().userId(1L).friendUserId(3L) 433 ImFriendDO.builder().userId(1L).friendUserId(3L)
@@ -411,15 +458,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -411,15 +458,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
411 ImGroupMemberDO.builder().groupId(10L).userId(1L) 458 ImGroupMemberDO.builder().groupId(10L).userId(1L)
412 .role(ImGroupMemberRoleEnum.OWNER.getRole()) 459 .role(ImGroupMemberRoleEnum.OWNER.getRole())
413 .status(CommonStatusEnum.ENABLE.getStatus()).build()); 460 .status(CommonStatusEnum.ENABLE.getStatus()).build());
414 - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( 461 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of(
415 ImGroupMemberDO.builder().groupId(10L).userId(1L) 462 ImGroupMemberDO.builder().groupId(10L).userId(1L)
416 .role(ImGroupMemberRoleEnum.OWNER.getRole()) 463 .role(ImGroupMemberRoleEnum.OWNER.getRole())
417 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 464 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
418 465
419 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 466 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
420 reqVO.setGroupId(10L); 467 reqVO.setGroupId(10L);
421 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L)));  
422 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 468 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
  469 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
423 ImFriendDO.builder().userId(1L).friendUserId(2L) 470 ImFriendDO.builder().userId(1L).friendUserId(2L)
424 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 471 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
425 ImFriendDO.builder().userId(1L).friendUserId(3L) 472 ImFriendDO.builder().userId(1L).friendUserId(3L)
@@ -434,7 +481,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -434,7 +481,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
434 eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); 481 eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L));
435 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 482 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
436 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); 483 verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture());
437 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); 484 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType());
438 } 485 }
439 } 486 }
440 487
@@ -448,18 +495,18 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -448,18 +495,18 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
448 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 495 .status(CommonStatusEnum.ENABLE.getStatus()).build();
449 when(groupMapper.selectById(10L)).thenReturn(group); 496 when(groupMapper.selectById(10L)).thenReturn(group);
450 when(groupMemberService.getActiveGroupMemberListByGroupId(10L)) 497 when(groupMemberService.getActiveGroupMemberListByGroupId(10L))
451 - .thenReturn(ListUtil.of(ImGroupMemberDO.builder().groupId(10L).userId(1L) 498 + .thenReturn(List.of(ImGroupMemberDO.builder().groupId(10L).userId(1L)
452 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 499 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
453 500
454 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 501 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
455 reqVO.setGroupId(10L); 502 reqVO.setGroupId(10L);
456 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); 503 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L)));
457 // 只有 2 是好友,3 不是 504 // 只有 2 是好友,3 不是
458 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 505 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
459 ImFriendDO.builder().userId(1L).friendUserId(2L) 506 ImFriendDO.builder().userId(1L).friendUserId(2L)
460 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 507 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
461 AdminUserRespDTO u3 = new AdminUserRespDTO(); u3.setId(3L); u3.setNickname("李四"); 508 AdminUserRespDTO u3 = new AdminUserRespDTO(); u3.setId(3L); u3.setNickname("李四");
462 - when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(3L, u3)); 509 + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3));
463 510
464 ServiceException exception = assertThrows(ServiceException.class, 511 ServiceException exception = assertThrows(ServiceException.class,
465 () -> groupService.inviteGroupMember(1L, reqVO)); 512 () -> groupService.inviteGroupMember(1L, reqVO));
@@ -487,9 +534,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -487,9 +534,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
487 534
488 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 535 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
489 reqVO.setGroupId(10L); 536 reqVO.setGroupId(10L);
490 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(600L, 601L))); 537 + reqVO.setMemberUserIds(new ArrayList<>(List.of(600L, 601L)));
491 // 被邀请人都是好友 538 // 被邀请人都是好友
492 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( 539 + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of(
493 ImFriendDO.builder().userId(1L).friendUserId(600L) 540 ImFriendDO.builder().userId(1L).friendUserId(600L)
494 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 541 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
495 ImFriendDO.builder().userId(1L).friendUserId(601L) 542 ImFriendDO.builder().userId(1L).friendUserId(601L)
@@ -515,7 +562,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -515,7 +562,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
515 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 562 .status(CommonStatusEnum.ENABLE.getStatus()).build();
516 when(groupMapper.selectById(10L)).thenReturn(group); 563 when(groupMapper.selectById(10L)).thenReturn(group);
517 // 用户 2 已在群中 564 // 用户 2 已在群中
518 - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( 565 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of(
519 ImGroupMemberDO.builder().groupId(10L).userId(1L) 566 ImGroupMemberDO.builder().groupId(10L).userId(1L)
520 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 567 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
521 ImGroupMemberDO.builder().groupId(10L).userId(2L) 568 ImGroupMemberDO.builder().groupId(10L).userId(2L)
@@ -524,14 +571,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -524,14 +571,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
524 571
525 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); 572 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO();
526 reqVO.setGroupId(10L); 573 reqVO.setGroupId(10L);
527 - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L))); // 只邀请 2,他已在群中 574 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L))); // 只邀请 2,他已在群中
528 575
529 // 调用 576 // 调用
530 groupService.inviteGroupMember(1L, reqVO); 577 groupService.inviteGroupMember(1L, reqVO);
531 578
532 // 断言:不会触发添加、不会推送 579 // 断言:不会触发添加、不会推送
533 verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection()); 580 verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection());
534 - verify(webSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); 581 + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any());
535 } 582 }
536 } 583 }
537 584
@@ -566,11 +613,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -566,11 +613,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
566 groupService.quitGroup(10L, 1L); 613 groupService.quitGroup(10L, 1L);
567 614
568 verify(groupMemberService).removeGroupMember(10L, 1L); 615 verify(groupMemberService).removeGroupMember(10L, 1L);
569 - verify(groupMessageService).deleteReadMaxMessageId(10L, 1L);  
570 // 推送 GROUP_MEMBER_QUIT 通知给全员(含 quitter,前端自判清群) 616 // 推送 GROUP_MEMBER_QUIT 通知给全员(含 quitter,前端自判清群)
571 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 617 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
572 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); 618 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture());
573 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_QUIT.getType(), dtoCaptor.getValue().getType()); 619 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType(), dtoCaptor.getValue().getType());
574 } 620 }
575 } 621 }
576 622
@@ -587,7 +633,6 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -587,7 +633,6 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
587 groupService.quitGroup(10L, 1L); 633 groupService.quitGroup(10L, 1L);
588 634
589 verify(groupMemberService).removeGroupMember(10L, 1L); 635 verify(groupMemberService).removeGroupMember(10L, 1L);
590 - verify(groupMessageService).deleteReadMaxMessageId(10L, 1L);  
591 verify(groupMessageService).sendGroupMessage(eq(1L), any(ImGroupMessageSendDTO.class)); 636 verify(groupMessageService).sendGroupMessage(eq(1L), any(ImGroupMessageSendDTO.class));
592 } 637 }
593 } 638 }
@@ -609,7 +654,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -609,7 +654,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
609 654
610 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); 655 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO();
611 reqVO.setGroupId(10L); 656 reqVO.setGroupId(10L);
612 - reqVO.setMemberUserIds(ListUtil.of(1L, 2L)); 657 + reqVO.setMemberUserIds(List.of(1L, 2L));
613 658
614 ServiceException exception = assertThrows(ServiceException.class, 659 ServiceException exception = assertThrows(ServiceException.class,
615 () -> groupService.removeGroupMember(1L, reqVO)); 660 () -> groupService.removeGroupMember(1L, reqVO));
@@ -631,7 +676,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -631,7 +676,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
631 ImGroupMemberDO.builder().groupId(10L).userId(1L) 676 ImGroupMemberDO.builder().groupId(10L).userId(1L)
632 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); 677 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build());
633 // 目标:两个普通成员 678 // 目标:两个普通成员
634 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 679 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
635 ImGroupMemberDO.builder().groupId(10L).userId(2L) 680 ImGroupMemberDO.builder().groupId(10L).userId(2L)
636 .role(ImGroupMemberRoleEnum.NORMAL.getRole()) 681 .role(ImGroupMemberRoleEnum.NORMAL.getRole())
637 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 682 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
@@ -641,16 +686,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -641,16 +686,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
641 686
642 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); 687 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO();
643 reqVO.setGroupId(10L); 688 reqVO.setGroupId(10L);
644 - reqVO.setMemberUserIds(ListUtil.of(2L, 3L)); 689 + reqVO.setMemberUserIds(List.of(2L, 3L));
645 690
646 groupService.removeGroupMember(1L, reqVO); 691 groupService.removeGroupMember(1L, reqVO);
647 692
648 verify(groupMemberService).removeGroupMembers(eq(10L), anyCollection()); 693 verify(groupMemberService).removeGroupMembers(eq(10L), anyCollection());
649 - verify(groupMessageService).deleteReadMaxMessageIds(eq(10L), anyCollection());  
650 // 推送 GROUP_MEMBER_KICK 通知给全员(含被踢者,前端自判清群) 694 // 推送 GROUP_MEMBER_KICK 通知给全员(含被踢者,前端自判清群)
651 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); 695 ArgumentCaptor<ImGroupMessageSendDTO> dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class);
652 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); 696 verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture());
653 - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_KICK.getType(), dtoCaptor.getValue().getType()); 697 + assertEquals(ImContentTypeEnum.GROUP_MEMBER_KICK.getType(), dtoCaptor.getValue().getType());
654 } 698 }
655 } 699 }
656 700
@@ -668,14 +712,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -668,14 +712,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
668 ImGroupMemberDO.builder().groupId(10L).userId(1L) 712 ImGroupMemberDO.builder().groupId(10L).userId(1L)
669 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()); 713 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build());
670 // 目标:另一个管理员 714 // 目标:另一个管理员
671 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 715 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
672 ImGroupMemberDO.builder().groupId(10L).userId(2L) 716 ImGroupMemberDO.builder().groupId(10L).userId(2L)
673 .role(ImGroupMemberRoleEnum.ADMIN.getRole()) 717 .role(ImGroupMemberRoleEnum.ADMIN.getRole())
674 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 718 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
675 719
676 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); 720 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO();
677 reqVO.setGroupId(10L); 721 reqVO.setGroupId(10L);
678 - reqVO.setMemberUserIds(ListUtil.of(2L)); 722 + reqVO.setMemberUserIds(List.of(2L));
679 723
680 ServiceException exception = assertThrows(ServiceException.class, 724 ServiceException exception = assertThrows(ServiceException.class,
681 () -> groupService.removeGroupMember(1L, reqVO)); 725 () -> groupService.removeGroupMember(1L, reqVO));
@@ -697,14 +741,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -697,14 +741,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
697 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( 741 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(
698 ImGroupMemberDO.builder().groupId(10L).userId(1L) 742 ImGroupMemberDO.builder().groupId(10L).userId(1L)
699 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); 743 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build());
700 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 744 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
701 ImGroupMemberDO.builder().groupId(10L).userId(99L) 745 ImGroupMemberDO.builder().groupId(10L).userId(99L)
702 .role(ImGroupMemberRoleEnum.OWNER.getRole()) 746 .role(ImGroupMemberRoleEnum.OWNER.getRole())
703 .status(CommonStatusEnum.ENABLE.getStatus()).build())); 747 .status(CommonStatusEnum.ENABLE.getStatus()).build()));
704 748
705 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); 749 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO();
706 reqVO.setGroupId(10L); 750 reqVO.setGroupId(10L);
707 - reqVO.setMemberUserIds(ListUtil.of(99L)); 751 + reqVO.setMemberUserIds(List.of(99L));
708 752
709 ServiceException exception = assertThrows(ServiceException.class, 753 ServiceException exception = assertThrows(ServiceException.class,
710 () -> groupService.removeGroupMember(1L, reqVO)); 754 () -> groupService.removeGroupMember(1L, reqVO));
@@ -726,7 +770,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -726,7 +770,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
726 ImGroupMemberDO.builder().groupId(10L).userId(1L) 770 ImGroupMemberDO.builder().groupId(10L).userId(1L)
727 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); 771 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build());
728 // 目标:2L 有效普通成员;3L 已退群(DISABLE)的历史管理员,应被跳过而非拦截整批 772 // 目标:2L 有效普通成员;3L 已退群(DISABLE)的历史管理员,应被跳过而非拦截整批
729 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 773 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
730 ImGroupMemberDO.builder().groupId(10L).userId(2L) 774 ImGroupMemberDO.builder().groupId(10L).userId(2L)
731 .role(ImGroupMemberRoleEnum.NORMAL.getRole()) 775 .role(ImGroupMemberRoleEnum.NORMAL.getRole())
732 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 776 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
@@ -736,17 +780,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -736,17 +780,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
736 780
737 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); 781 ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO();
738 reqVO.setGroupId(10L); 782 reqVO.setGroupId(10L);
739 - reqVO.setMemberUserIds(ListUtil.of(2L, 3L)); 783 + reqVO.setMemberUserIds(List.of(2L, 3L));
740 784
741 groupService.removeGroupMember(1L, reqVO); 785 groupService.removeGroupMember(1L, reqVO);
742 786
743 - // 仅有效成员 2L 进入移除 / 已读清理,已退群的 3L 被跳过 787 + // 仅有效成员 2L 进入移除,已退群的 3L 被跳过
744 ArgumentCaptor<Collection> removeCaptor = ArgumentCaptor.forClass(Collection.class); 788 ArgumentCaptor<Collection> removeCaptor = ArgumentCaptor.forClass(Collection.class);
745 verify(groupMemberService).removeGroupMembers(eq(10L), removeCaptor.capture()); 789 verify(groupMemberService).removeGroupMembers(eq(10L), removeCaptor.capture());
746 - assertEquals(CollUtil.newHashSet(2L), CollUtil.newHashSet(removeCaptor.getValue()));  
747 - ArgumentCaptor<Collection> readCaptor = ArgumentCaptor.forClass(Collection.class);  
748 - verify(groupMessageService).deleteReadMaxMessageIds(eq(10L), readCaptor.capture());  
749 - assertEquals(CollUtil.newHashSet(2L), CollUtil.newHashSet(readCaptor.getValue())); 790 + assertEquals(Set.of(2L), Set.copyOf(removeCaptor.getValue()));
750 } 791 }
751 } 792 }
752 793
@@ -762,7 +803,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -762,7 +803,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
762 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 803 .status(CommonStatusEnum.ENABLE.getStatus()).build();
763 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 804 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
764 // 目标 3 是普通成员 805 // 目标 3 是普通成员
765 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 806 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
766 ImGroupMemberDO.builder().userId(3L).status(CommonStatusEnum.ENABLE.getStatus()) 807 ImGroupMemberDO.builder().userId(3L).status(CommonStatusEnum.ENABLE.getStatus())
767 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); 808 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()));
768 // 群里已有 1 个 ADMIN,1 + 1 ≤ 3 不超上限 809 // 群里已有 1 个 ADMIN,1 + 1 ≤ 3 不超上限
@@ -773,7 +814,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -773,7 +814,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
773 814
774 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); 815 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO();
775 reqVO.setId(10L); 816 reqVO.setId(10L);
776 - reqVO.setUserIds(ListUtil.of(3L)); 817 + reqVO.setUserIds(List.of(3L));
777 818
778 groupService.addGroupAdmin(1L, reqVO); 819 groupService.addGroupAdmin(1L, reqVO);
779 820
@@ -792,7 +833,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -792,7 +833,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
792 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) 833 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L)
793 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 834 .status(CommonStatusEnum.ENABLE.getStatus()).build();
794 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 835 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
795 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 836 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
796 ImGroupMemberDO.builder().userId(5L).status(CommonStatusEnum.ENABLE.getStatus()) 837 ImGroupMemberDO.builder().userId(5L).status(CommonStatusEnum.ENABLE.getStatus())
797 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); 838 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()));
798 // 群里已有 3 个 ADMIN(达到上限),再加 1 会超 839 // 群里已有 3 个 ADMIN(达到上限),再加 1 会超
@@ -801,7 +842,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -801,7 +842,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
801 842
802 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); 843 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO();
803 reqVO.setId(10L); 844 reqVO.setId(10L);
804 - reqVO.setUserIds(ListUtil.of(5L)); 845 + reqVO.setUserIds(List.of(5L));
805 846
806 ServiceException exception = assertThrows(ServiceException.class, 847 ServiceException exception = assertThrows(ServiceException.class,
807 () -> groupService.addGroupAdmin(1L, reqVO)); 848 () -> groupService.addGroupAdmin(1L, reqVO));
@@ -819,13 +860,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -819,13 +860,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
819 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) 860 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L)
820 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 861 .status(CommonStatusEnum.ENABLE.getStatus()).build();
821 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 862 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
822 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 863 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
823 ImGroupMemberDO.builder().userId(1L).status(CommonStatusEnum.ENABLE.getStatus()) 864 ImGroupMemberDO.builder().userId(1L).status(CommonStatusEnum.ENABLE.getStatus())
824 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build())); 865 .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()));
825 866
826 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); 867 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO();
827 reqVO.setId(10L); 868 reqVO.setId(10L);
828 - reqVO.setUserIds(ListUtil.of(1L)); 869 + reqVO.setUserIds(List.of(1L));
829 870
830 ServiceException exception = assertThrows(ServiceException.class, 871 ServiceException exception = assertThrows(ServiceException.class,
831 () -> groupService.addGroupAdmin(1L, reqVO)); 872 () -> groupService.addGroupAdmin(1L, reqVO));
@@ -843,13 +884,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -843,13 +884,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
843 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 884 .status(CommonStatusEnum.ENABLE.getStatus()).build();
844 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 885 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
845 // 目标已是 ADMIN:再加无需操作 886 // 目标已是 ADMIN:再加无需操作
846 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 887 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
847 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) 888 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus())
848 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); 889 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()));
849 890
850 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); 891 ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO();
851 reqVO.setId(10L); 892 reqVO.setId(10L);
852 - reqVO.setUserIds(ListUtil.of(2L)); 893 + reqVO.setUserIds(List.of(2L));
853 894
854 groupService.addGroupAdmin(1L, reqVO); 895 groupService.addGroupAdmin(1L, reqVO);
855 896
@@ -869,7 +910,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -869,7 +910,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
869 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) 910 ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L)
870 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 911 .status(CommonStatusEnum.ENABLE.getStatus()).build();
871 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 912 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
872 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 913 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
873 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) 914 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus())
874 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); 915 .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()));
875 when(groupMemberService.updateGroupMemberRole(eq(10L), anyCollection(), 916 when(groupMemberService.updateGroupMemberRole(eq(10L), anyCollection(),
@@ -877,7 +918,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -877,7 +918,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
877 918
878 ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); 919 ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO();
879 reqVO.setId(10L); 920 reqVO.setId(10L);
880 - reqVO.setUserIds(ListUtil.of(2L)); 921 + reqVO.setUserIds(List.of(2L));
881 922
882 groupService.removeGroupAdmin(1L, reqVO); 923 groupService.removeGroupAdmin(1L, reqVO);
883 924
@@ -897,13 +938,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -897,13 +938,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
897 .status(CommonStatusEnum.ENABLE.getStatus()).build(); 938 .status(CommonStatusEnum.ENABLE.getStatus()).build();
898 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); 939 when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group);
899 // 目标已是 MEMBER:撤销无需操作 940 // 目标已是 MEMBER:撤销无需操作
900 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( 941 + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of(
901 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) 942 ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus())
902 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); 943 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()));
903 944
904 ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); 945 ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO();
905 reqVO.setId(10L); 946 reqVO.setId(10L);
906 - reqVO.setUserIds(ListUtil.of(2L)); 947 + reqVO.setUserIds(List.of(2L));
907 948
908 groupService.removeGroupAdmin(1L, reqVO); 949 groupService.removeGroupAdmin(1L, reqVO);
909 950
@@ -925,9 +966,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -925,9 +966,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
925 when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn( 966 when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn(
926 ImGroupMemberDO.builder().groupId(10L).userId(2L) 967 ImGroupMemberDO.builder().groupId(10L).userId(2L)
927 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); 968 .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build());
928 - when(groupMemberService.updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(2L)), 969 + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(2L)),
929 eq(ImGroupMemberRoleEnum.OWNER.getRole()))).thenReturn(1); 970 eq(ImGroupMemberRoleEnum.OWNER.getRole()))).thenReturn(1);
930 - when(groupMemberService.updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(1L)), 971 + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(1L)),
931 eq(ImGroupMemberRoleEnum.NORMAL.getRole()))).thenReturn(1); 972 eq(ImGroupMemberRoleEnum.NORMAL.getRole()))).thenReturn(1);
932 973
933 ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO(); 974 ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO();
@@ -941,9 +982,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -941,9 +982,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
941 verify(groupMapper).updateById(groupCaptor.capture()); 982 verify(groupMapper).updateById(groupCaptor.capture());
942 assertEquals(2L, groupCaptor.getValue().getOwnerUserId()); 983 assertEquals(2L, groupCaptor.getValue().getOwnerUserId());
943 // 旧群主 → MEMBER;新群主 → OWNER 984 // 旧群主 → MEMBER;新群主 → OWNER
944 - verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(1L)), 985 + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(1L)),
945 eq(ImGroupMemberRoleEnum.NORMAL.getRole())); 986 eq(ImGroupMemberRoleEnum.NORMAL.getRole()));
946 - verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(2L)), 987 + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(2L)),
947 eq(ImGroupMemberRoleEnum.OWNER.getRole())); 988 eq(ImGroupMemberRoleEnum.OWNER.getRole()));
948 } 989 }
949 } 990 }
@@ -1111,9 +1152,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -1111,9 +1152,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
1111 1152
1112 @Test 1153 @Test
1113 public void testGetMyGroupList_noMembers() { 1154 public void testGetMyGroupList_noMembers() {
1114 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>());  
1115 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
1116 - .thenReturn(new ArrayList<>()); 1155 + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>());
1117 1156
1118 List<ImGroupDO> result = groupService.getMyGroupList(1L); 1157 List<ImGroupDO> result = groupService.getMyGroupList(1L);
1119 assertTrue(result.isEmpty()); 1158 assertTrue(result.isEmpty());
@@ -1122,20 +1161,16 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @@ -1122,20 +1161,16 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest {
1122 1161
1123 @Test 1162 @Test
1124 public void testGetMyGroupList_success() { 1163 public void testGetMyGroupList_success() {
1125 - // 活跃群成员  
1126 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>(ListUtil.of( 1164 + // 曾经加入的所有群(含退群)
  1165 + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>(List.of(
1127 ImGroupMemberDO.builder().groupId(10L).userId(1L) 1166 ImGroupMemberDO.builder().groupId(10L).userId(1L)
1128 .status(CommonStatusEnum.ENABLE.getStatus()).build(), 1167 .status(CommonStatusEnum.ENABLE.getStatus()).build(),
1129 ImGroupMemberDO.builder().groupId(20L).userId(1L) 1168 ImGroupMemberDO.builder().groupId(20L).userId(1L)
1130 - .status(CommonStatusEnum.ENABLE.getStatus()).build() 1169 + .status(CommonStatusEnum.ENABLE.getStatus()).build(),
  1170 + ImGroupMemberDO.builder().groupId(30L).userId(1L)
  1171 + .status(CommonStatusEnum.DISABLE.getStatus()).build()
1131 ))); 1172 )));
1132 - // 最近退群成员(最近 30 天内)  
1133 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
1134 - .thenReturn(new ArrayList<>(ListUtil.of(  
1135 - ImGroupMemberDO.builder().groupId(30L).userId(1L)  
1136 - .status(CommonStatusEnum.DISABLE.getStatus()).build()  
1137 - )));  
1138 - List<ImGroupDO> groups = ListUtil.of( 1173 + List<ImGroupDO> groups = List.of(
1139 ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(), 1174 ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(),
1140 ImGroupDO.builder().id(20L).status(CommonStatusEnum.ENABLE.getStatus()).build(), 1175 ImGroupDO.builder().id(20L).status(CommonStatusEnum.ENABLE.getStatus()).build(),
1141 ImGroupDO.builder().id(30L).status(CommonStatusEnum.ENABLE.getStatus()).build() 1176 ImGroupDO.builder().id(30L).status(CommonStatusEnum.ENABLE.getStatus()).build()
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.service.message;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO;
  5 +import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper;
  6 +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService;
  7 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
  8 +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.mockito.InjectMocks;
  11 +import org.mockito.Mock;
  12 +
  13 +import java.time.LocalDateTime;
  14 +import java.util.List;
  15 +
  16 +import static org.mockito.ArgumentMatchers.*;
  17 +import static org.mockito.Mockito.*;
  18 +
  19 +/**
  20 + * {@link ImChannelMessageServiceImpl} 的单元测试
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +public class ImChannelMessageServiceImplTest extends BaseMockitoUnitTest {
  25 +
  26 + @InjectMocks
  27 + private ImChannelMessageServiceImpl channelMessageService;
  28 +
  29 + @Mock
  30 + private ImChannelMessageMapper channelMessageMapper;
  31 + @Mock
  32 + private ImChannelMaterialService channelMaterialService;
  33 + @Mock
  34 + private ImWebSocketService webSocketService;
  35 + @Mock
  36 + private ImConversationReadService conversationReadService;
  37 +
  38 + @Test
  39 + public void testReadChannelMessages_messageNotExists() {
  40 + // 准备:messageId 不存在(伪造 / 未来 id)
  41 + when(channelMessageMapper.selectById(999L)).thenReturn(null);
  42 +
  43 + // 调用
  44 + channelMessageService.readChannelMessages(1L, 10L, 999L);
  45 +
  46 + // 断言:不推进读位置
  47 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
  48 + }
  49 +
  50 + @Test
  51 + public void testReadChannelMessages_wrongChannel() {
  52 + // 准备:消息属于别的频道
  53 + ImChannelMessageDO message = ImChannelMessageDO.builder()
  54 + .id(100L).channelId(20L).sendTime(LocalDateTime.now()).build();
  55 + when(channelMessageMapper.selectById(100L)).thenReturn(message);
  56 +
  57 + // 调用:声称读的是频道 10
  58 + channelMessageService.readChannelMessages(1L, 10L, 100L);
  59 +
  60 + // 断言:不推进读位置
  61 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
  62 + }
  63 +
  64 + @Test
  65 + public void testReadChannelMessages_notVisible() {
  66 + // 准备:定向消息,接收人不含当前用户
  67 + ImChannelMessageDO message = ImChannelMessageDO.builder()
  68 + .id(100L).channelId(10L).receiverUserIds(List.of(2L, 3L)).sendTime(LocalDateTime.now()).build();
  69 + when(channelMessageMapper.selectById(100L)).thenReturn(message);
  70 +
  71 + // 调用
  72 + channelMessageService.readChannelMessages(1L, 10L, 100L);
  73 +
  74 + // 断言:不推进读位置
  75 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
  76 + }
  77 +
  78 + @Test
  79 + public void testReadChannelMessages_notAdvanced() {
  80 + // 准备:消息真实可见,但读位置未前进(updateConversationReadPosition 返回 false)
  81 + ImChannelMessageDO message = ImChannelMessageDO.builder()
  82 + .id(100L).channelId(10L).sendTime(LocalDateTime.now()).build();
  83 + when(channelMessageMapper.selectById(100L)).thenReturn(message);
  84 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()))
  85 + .thenReturn(false);
  86 +
  87 + // 调用
  88 + channelMessageService.readChannelMessages(1L, 10L, 100L);
  89 +
  90 + // 断言:读位置未前进 → 不推 READ 事件
  91 + verify(webSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
  92 + }
  93 +
  94 +}
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java
1 package cn.iocoder.yudao.module.im.service.message; 1 package cn.iocoder.yudao.module.im.service.message;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.collection.ListUtil;  
5 import cn.hutool.extra.spring.SpringUtil; 3 import cn.hutool.extra.spring.SpringUtil;
6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 import cn.iocoder.yudao.framework.common.exception.ServiceException; 5 import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -12,19 +10,20 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; @@ -12,19 +10,20 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
12 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
13 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; 11 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
14 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; 12 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
15 -import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO; 13 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
16 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; 14 import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum;
17 -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; 15 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
18 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 16 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
19 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 17 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
20 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 18 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
  19 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
21 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; 20 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
22 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 21 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
23 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; 22 import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO;
24 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 23 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
25 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 24 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
26 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;  
27 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; 25 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification;
  26 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage;
28 import org.junit.jupiter.api.Test; 27 import org.junit.jupiter.api.Test;
29 import org.mockito.ArgumentCaptor; 28 import org.mockito.ArgumentCaptor;
30 import org.mockito.InjectMocks; 29 import org.mockito.InjectMocks;
@@ -41,7 +40,7 @@ import static org.mockito.ArgumentMatchers.*; @@ -41,7 +40,7 @@ import static org.mockito.ArgumentMatchers.*;
41 import static org.mockito.Mockito.*; 40 import static org.mockito.Mockito.*;
42 41
43 /** 42 /**
44 - * IM 群聊消息 Service 单元测试 43 + * {@link ImGroupMessageServiceImpl} 的单元测试
45 * 44 *
46 * @author 芋道源码 45 * @author 芋道源码
47 */ 46 */
@@ -59,11 +58,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -59,11 +58,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
59 @Mock 58 @Mock
60 private ImSensitiveWordService sensitiveWordService; 59 private ImSensitiveWordService sensitiveWordService;
61 @Mock 60 @Mock
62 - private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO; 61 + private ImConversationReadService conversationReadService;
63 @Mock 62 @Mock
64 private ImWebSocketService imWebSocketService; 63 private ImWebSocketService imWebSocketService;
65 64
66 - /** 用真实实例避免 NPE;默认值与生产保持一致(recallTimeoutMinutes=5、private/group read enabled=true、maxPullSize=1000);个别用例可改字段测分支 */  
67 @Spy 65 @Spy
68 private ImProperties imProperties = new ImProperties(); 66 private ImProperties imProperties = new ImProperties();
69 67
@@ -71,7 +69,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -71,7 +69,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
71 ImGroupMessageSendReqVO reqVO = new ImGroupMessageSendReqVO(); 69 ImGroupMessageSendReqVO reqVO = new ImGroupMessageSendReqVO();
72 reqVO.setClientMessageId("test-uuid-group-001"); 70 reqVO.setClientMessageId("test-uuid-group-001");
73 reqVO.setGroupId(10L); 71 reqVO.setGroupId(10L);
74 - reqVO.setType(ImMessageTypeEnum.TEXT.getType()); 72 + reqVO.setType(ImContentTypeEnum.TEXT.getType());
75 reqVO.setContent("{\"content\":\"群聊你好\"}"); 73 reqVO.setContent("{\"content\":\"群聊你好\"}");
76 return reqVO; 74 return reqVO;
77 } 75 }
@@ -86,6 +84,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -86,6 +84,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
86 84
87 // 准备 85 // 准备
88 ImGroupMessageSendReqVO reqVO = buildSendReqVO(); 86 ImGroupMessageSendReqVO reqVO = buildSendReqVO();
  87 + reqVO.setReceipt(true);
89 when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) 88 when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001"))
90 .thenReturn(null); 89 .thenReturn(null);
91 ImGroupDO group = new ImGroupDO(); 90 ImGroupDO group = new ImGroupDO();
@@ -98,7 +97,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -98,7 +97,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
98 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); 97 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
99 98
100 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) 99 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
101 - .thenReturn(ListUtil.of(1L, 2L, 3L)); 100 + .thenReturn(List.of(1L, 2L, 3L));
102 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { 101 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> {
103 ImGroupMessageDO msg = invocation.getArgument(0); 102 ImGroupMessageDO msg = invocation.getArgument(0);
104 msg.setId(99L); 103 msg.setId(99L);
@@ -112,13 +111,56 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -112,13 +111,56 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
112 assertNotNull(result); 111 assertNotNull(result);
113 assertEquals(1L, result.getSenderId()); 112 assertEquals(1L, result.getSenderId());
114 assertEquals(10L, result.getGroupId()); 113 assertEquals(10L, result.getGroupId());
115 - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.getStatus());  
116 - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus()); 114 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus());
  115 + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus());
117 116
118 // 验证推送给 3 个群成员(含发送者自己,用于多端同步) 117 // 验证推送给 3 个群成员(含发送者自己,用于多端同步)
119 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection<Long> ids) -> 118 + ArgumentCaptor<ImGroupMessageNotification> payloadCaptor =
  119 + ArgumentCaptor.forClass(ImGroupMessageNotification.class);
  120 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
120 ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)), 121 ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)),
121 - any(ImGroupMessageDTO.class)); 122 + eq(ImConversationTypeEnum.GROUP.getType()), eq(ImContentTypeEnum.TEXT.getType()),
  123 + payloadCaptor.capture());
  124 + ImGroupMessageNotification payload = payloadCaptor.getValue();
  125 + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), payload.getReceiptStatus());
  126 + assertEquals(0, payload.getReadCount());
  127 + assertEquals(List.of(1L, 2L, 3L), payload.getReceiverUserIds());
  128 + }
  129 + }
  130 +
  131 + @Test
  132 + public void testSendMessage_senderAlwaysVisible() {
  133 + // 边界:成员缓存漏掉发送者(理论上 validateMemberInGroup 已挡,这里纯防御);发送者仍必须固化进 receiver_user_ids
  134 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  135 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class)))
  136 + .thenReturn(groupMessageService);
  137 +
  138 + ImGroupMessageSendReqVO reqVO = buildSendReqVO();
  139 + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001"))
  140 + .thenReturn(null);
  141 + ImGroupDO group = new ImGroupDO();
  142 + group.setId(10L);
  143 + group.setName("测试群");
  144 + when(groupService.validateGroupExists(10L)).thenReturn(group);
  145 + ImGroupMemberDO member = ImGroupMemberDO.builder()
  146 + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build();
  147 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
  148 + // 成员缓存只返回 {2,3},漏掉发送者 1
  149 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
  150 + .thenReturn(List.of(2L, 3L));
  151 + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> {
  152 + ImGroupMessageDO msg = invocation.getArgument(0);
  153 + msg.setId(99L);
  154 + return 1;
  155 + });
  156 +
  157 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO);
  158 +
  159 + // 断言:固化快照必含发送者,推送目标补回发送者 = {1,2,3}
  160 + assertTrue(result.getReceiverUserIds().contains(1L), "发送者必须在 receiver_user_ids 快照内");
  161 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
  162 + ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)),
  163 + anyInt(), anyInt(), any());
122 } 164 }
123 } 165 }
124 166
@@ -159,375 +201,122 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -159,375 +201,122 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
159 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); 201 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode());
160 } 202 }
161 203
162 - // ========== pull 测试 ==========  
163 -  
164 @Test 204 @Test
165 - public void testPullMessages_joinTimeFilter() {  
166 - // 准备:消息时间用"最近 1 天内"避免被 30 天窗口过滤  
167 - LocalDateTime now = LocalDateTime.now();  
168 - LocalDateTime joinTime = now.minusHours(2);  
169 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
170 - .groupId(10L).userId(1L)  
171 - .status(CommonStatusEnum.ENABLE.getStatus())  
172 - .joinTime(joinTime).build();  
173 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member));  
174 -  
175 - // 入群前 1 条 + 入群后 1 条  
176 - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder()  
177 - .id(1L).groupId(10L).senderId(2L)  
178 - .sendTime(now.minusHours(3)).build();  
179 - ImGroupMessageDO afterJoin = ImGroupMessageDO.builder()  
180 - .id(2L).groupId(10L).senderId(2L)  
181 - .sendTime(now.minusHours(1)).build();  
182 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
183 - any(LocalDateTime.class), eq(100)))  
184 - .thenReturn(ListUtil.of(beforeJoin, afterJoin));  
185 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
186 - .thenReturn(ListUtil.of());  
187 -  
188 - // 调用  
189 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);  
190 -  
191 - // 断言:入群前消息不可见  
192 - assertEquals(1, result.size());  
193 - assertEquals(2L, result.get(0).getId());  
194 - }  
195 -  
196 - @Test  
197 - public void testPullMessages_activeQueryRetriesWhenFirstBatchInvisible() {  
198 - // 准备:首批 2 条(入群前 + 定向给别人)全部不可见,第二批命中可见消息  
199 - LocalDateTime now = LocalDateTime.now();  
200 - LocalDateTime joinTime = now.minusHours(1);  
201 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
202 - .groupId(10L).userId(1L)  
203 - .status(CommonStatusEnum.ENABLE.getStatus())  
204 - .joinTime(joinTime).build();  
205 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member));  
206 -  
207 - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder()  
208 - .id(1L).groupId(10L).senderId(2L)  
209 - .sendTime(now.minusHours(3)).build();  
210 - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder()  
211 - .id(2L).groupId(10L).senderId(2L)  
212 - .receiverUserIds(ListUtil.of(2L, 3L))  
213 - .sendTime(now.minusHours(2)).build();  
214 - ImGroupMessageDO visibleMsg = ImGroupMessageDO.builder()  
215 - .id(3L).groupId(10L).senderId(2L)  
216 - .sendTime(now.minusMinutes(30)).build();  
217 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
218 - any(LocalDateTime.class), eq(2)))  
219 - .thenReturn(ListUtil.of(beforeJoin, directedMsg));  
220 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(2L),  
221 - any(LocalDateTime.class), eq(2)))  
222 - .thenReturn(ListUtil.of(visibleMsg));  
223 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
224 - .thenReturn(ListUtil.of());  
225 -  
226 - // 调用  
227 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 2);  
228 -  
229 - // 断言:仅返回第二批的可见消息;两批 selectListByMinId 各被调用一次  
230 - assertEquals(1, result.size());  
231 - assertEquals(3L, result.get(0).getId());  
232 - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
233 - any(LocalDateTime.class), eq(2));  
234 - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(2L),  
235 - any(LocalDateTime.class), eq(2));  
236 - }  
237 -  
238 - @Test  
239 - public void testPullMessages_quitMemberLoadsPreQuitMessages() {  
240 - // 准备:当前群(在群)+ 一个最近退群  
241 - LocalDateTime now = LocalDateTime.now();  
242 - ImGroupMemberDO activeMember = ImGroupMemberDO.builder()  
243 - .groupId(10L).userId(1L)  
244 - .status(CommonStatusEnum.ENABLE.getStatus())  
245 - .joinTime(now.minusDays(10)).build();  
246 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember));  
247 -  
248 - ImGroupMessageDO activeMsg = ImGroupMessageDO.builder()  
249 - .id(5L).groupId(10L).senderId(2L)  
250 - .sendTime(now.minusHours(2)).build();  
251 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
252 - any(LocalDateTime.class), eq(100)))  
253 - .thenReturn(ListUtil.of(activeMsg));  
254 -  
255 - // 已退群:退群时间在窗口内(minId=0,minQuitTime 直接用 minSendTime)  
256 - LocalDateTime quitTime = now.minusDays(3);  
257 - ImGroupMemberDO quitMember = ImGroupMemberDO.builder()  
258 - .groupId(20L).userId(1L)  
259 - .status(CommonStatusEnum.DISABLE.getStatus())  
260 - .joinTime(now.minusDays(20))  
261 - .quitTime(quitTime).build();  
262 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
263 - .thenReturn(ListUtil.of(quitMember));  
264 -  
265 - ImGroupMessageDO quitGroupMsg = ImGroupMessageDO.builder()  
266 - .id(3L).groupId(20L).senderId(99L)  
267 - .sendTime(now.minusDays(5)).build();  
268 - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(0L),  
269 - any(LocalDateTime.class), eq(quitTime), eq(100)))  
270 - .thenReturn(ListUtil.of(quitGroupMsg));  
271 -  
272 - // 调用  
273 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);  
274 -  
275 - // 断言:两条消息均返回,并按 id 升序  
276 - assertEquals(2, result.size());  
277 - assertEquals(3L, result.get(0).getId());  
278 - assertEquals(5L, result.get(1).getId());  
279 - }  
280 -  
281 - @Test  
282 - public void testPullMessages_quitMemberFilteredByBoundarySendTime() {  
283 - // 准备:minId > 0,边界消息 sendTime 晚于窗口起点,应当被用作 minQuitTime  
284 - LocalDateTime now = LocalDateTime.now();  
285 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of());  
286 -  
287 - LocalDateTime boundarySendTime = now.minusDays(2);  
288 - ImGroupMessageDO boundary = ImGroupMessageDO.builder()  
289 - .id(8L).groupId(10L).senderId(2L).sendTime(boundarySendTime).build();  
290 - when(groupMessageMapper.selectById(8L)).thenReturn(boundary);  
291 - when(groupMemberService.getQuitGroupMemberListByUserId(1L, boundarySendTime))  
292 - .thenReturn(ListUtil.of());  
293 -  
294 - // 调用  
295 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 8L, 100);  
296 -  
297 - // 断言:使用边界消息 sendTime 抬升 minQuitTime  
298 - assertEquals(0, result.size());  
299 - verify(groupMemberService).getQuitGroupMemberListByUserId(1L, boundarySendTime);  
300 - }  
301 -  
302 - @Test  
303 - public void testPullMessages_quitQueryKeepsOriginalMinIdWhenActiveQueryRetries() {  
304 - // 准备:主查询首批不可见触发重试(游标从 8→101),退群补齐仍使用原始 minId=8  
305 - LocalDateTime now = LocalDateTime.now();  
306 - LocalDateTime joinTime = now.minusHours(1);  
307 - ImGroupMemberDO activeMember = ImGroupMemberDO.builder()  
308 - .groupId(10L).userId(1L)  
309 - .status(CommonStatusEnum.ENABLE.getStatus())  
310 - .joinTime(joinTime).build();  
311 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember));  
312 -  
313 - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder()  
314 - .id(100L).groupId(10L).senderId(2L)  
315 - .sendTime(now.minusHours(3)).build();  
316 - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder()  
317 - .id(101L).groupId(10L).senderId(2L)  
318 - .receiverUserIds(ListUtil.of(2L, 3L))  
319 - .sendTime(now.minusMinutes(50)).build();  
320 - ImGroupMessageDO visibleActiveMsg = ImGroupMessageDO.builder()  
321 - .id(102L).groupId(10L).senderId(2L)  
322 - .sendTime(now.minusMinutes(30)).build();  
323 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(8L),  
324 - any(LocalDateTime.class), eq(2)))  
325 - .thenReturn(ListUtil.of(beforeJoin, directedMsg));  
326 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(101L),  
327 - any(LocalDateTime.class), eq(2)))  
328 - .thenReturn(ListUtil.of(visibleActiveMsg));  
329 -  
330 - LocalDateTime boundarySendTime = now.minusDays(2);  
331 - ImGroupMessageDO boundary = ImGroupMessageDO.builder()  
332 - .id(8L).groupId(99L).senderId(2L).sendTime(boundarySendTime).build();  
333 - when(groupMessageMapper.selectById(8L)).thenReturn(boundary);  
334 -  
335 - LocalDateTime quitTime = now.minusHours(12);  
336 - ImGroupMemberDO quitMember = ImGroupMemberDO.builder()  
337 - .groupId(20L).userId(1L)  
338 - .status(CommonStatusEnum.DISABLE.getStatus())  
339 - .joinTime(now.minusDays(10))  
340 - .quitTime(quitTime).build();  
341 - when(groupMemberService.getQuitGroupMemberListByUserId(1L, boundarySendTime))  
342 - .thenReturn(ListUtil.of(quitMember));  
343 -  
344 - ImGroupMessageDO quitGroupMsg = ImGroupMessageDO.builder()  
345 - .id(50L).groupId(20L).senderId(99L)  
346 - .sendTime(now.minusDays(1)).build();  
347 - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(8L),  
348 - any(LocalDateTime.class), eq(quitTime), eq(2)))  
349 - .thenReturn(ListUtil.of(quitGroupMsg));  
350 -  
351 - // 调用  
352 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 8L, 2);  
353 -  
354 - // 断言:退群补齐使用原始 minId=8 而非主查询重试后的 101  
355 - assertEquals(2, result.size());  
356 - assertEquals(50L, result.get(0).getId());  
357 - assertEquals(102L, result.get(1).getId());  
358 - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(8L),  
359 - any(LocalDateTime.class), eq(2));  
360 - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(101L),  
361 - any(LocalDateTime.class), eq(2));  
362 - verify(groupMemberService).getQuitGroupMemberListByUserId(1L, boundarySendTime);  
363 - verify(groupMessageMapper).selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(8L),  
364 - any(LocalDateTime.class), eq(quitTime), eq(2));  
365 - verify(groupMessageMapper, never()).selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(101L),  
366 - any(LocalDateTime.class), eq(quitTime), eq(2));  
367 - }  
368 -  
369 - @Test  
370 - public void testPullMessages_receiverUserIdsFilter() {  
371 - // 准备  
372 - LocalDateTime now = LocalDateTime.now();  
373 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
374 - .groupId(10L).userId(1L)  
375 - .status(CommonStatusEnum.ENABLE.getStatus())  
376 - .joinTime(now.minusDays(10)).build();  
377 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member));  
378 -  
379 - // 定向接收消息:只给用户 2 和 3  
380 - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder()  
381 - .id(1L).groupId(10L).senderId(5L)  
382 - .receiverUserIds(ListUtil.of(2L, 3L))  
383 - .sendTime(now.minusHours(2)).build();  
384 - // 全员消息  
385 - ImGroupMessageDO allMsg = ImGroupMessageDO.builder()  
386 - .id(2L).groupId(10L).senderId(5L)  
387 - .sendTime(now.minusHours(1)).build();  
388 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
389 - any(LocalDateTime.class), eq(100)))  
390 - .thenReturn(ListUtil.of(directedMsg, allMsg));  
391 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
392 - .thenReturn(ListUtil.of());  
393 -  
394 - // 调用  
395 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100); 205 + public void testSendMessage_quoteDirectedMessageRejected() {
  206 + // 准备:发送人 1 在群 10,当前有效成员(广播受众)= {1,2,3}
  207 + ImGroupMessageSendReqVO reqVO = buildSendReqVO();
  208 + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}");
  209 + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null);
  210 + ImGroupDO group = new ImGroupDO();
  211 + group.setId(10L);
  212 + when(groupService.validateGroupExists(10L)).thenReturn(group);
  213 + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L)
  214 + .status(CommonStatusEnum.ENABLE.getStatus()).build();
  215 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
  216 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L));
  217 + // 被引用原消息:定向给 {1,2}(对发送人 1 可见,但不覆盖广播受众 {1,2,3})
  218 + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L)
  219 + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L))
  220 + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"密\"}")
  221 + .sendTime(LocalDateTime.now()).build();
  222 + when(groupMessageMapper.selectById(500L)).thenReturn(original);
396 223
397 - // 断言:定向接收的消息用户 1 看不到,只能看到全员消息  
398 - assertEquals(1, result.size());  
399 - assertEquals(2L, result.get(0).getId()); 224 + // 调用并断言:引用定向消息被拒绝,避免原文随广播泄漏给全群
  225 + ServiceException exception = assertThrows(ServiceException.class,
  226 + () -> groupMessageService.sendGroupMessage(1L, reqVO));
  227 + assertEquals(MESSAGE_QUOTE_INVALID.getCode(), exception.getCode());
  228 + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class));
400 } 229 }
401 230
402 @Test 231 @Test
403 - public void testPullMessages_atFieldCorrectReturn() {  
404 - // 准备  
405 - LocalDateTime now = LocalDateTime.now();  
406 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
407 - .groupId(10L).userId(1L)  
408 - .status(CommonStatusEnum.ENABLE.getStatus())  
409 - .joinTime(now.minusDays(10)).build();  
410 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member));  
411 -  
412 - ImGroupMessageDO atMsg = ImGroupMessageDO.builder()  
413 - .id(1L).groupId(10L).senderId(2L)  
414 - .atUserIds(ListUtil.of(1L, 3L))  
415 - .sendTime(now.minusHours(1)).build();  
416 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
417 - any(LocalDateTime.class), eq(100)))  
418 - .thenReturn(ListUtil.of(atMsg));  
419 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
420 - .thenReturn(ListUtil.of());  
421 -  
422 - // 调用  
423 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100); 232 + public void testSendMessage_quoteBroadcastAccepted() {
  233 + // 准备:广播受众 {1,2,3},被引用原消息快照覆盖全部受众 → 允许引用
  234 + ImGroupMessageSendReqVO reqVO = buildSendReqVO();
  235 + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}");
  236 + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null);
  237 + ImGroupDO group = new ImGroupDO();
  238 + group.setId(10L);
  239 + when(groupService.validateGroupExists(10L)).thenReturn(group);
  240 + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L)
  241 + .status(CommonStatusEnum.ENABLE.getStatus()).build();
  242 + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
  243 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L));
  244 + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L)
  245 + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L, 3L))
  246 + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"公开\"}")
  247 + .sendTime(LocalDateTime.now()).build();
  248 + when(groupMessageMapper.selectById(500L)).thenReturn(original);
  249 + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> {
  250 + invocation.<ImGroupMessageDO>getArgument(0).setId(99L);
  251 + return 1;
  252 + });
424 253
425 - // 断言:@ 字段正确返回  
426 - assertEquals(1, result.size());  
427 - assertEquals(ListUtil.of(1L, 3L), result.get(0).getAtUserIds()); 254 + // 调用并断言:正常入库
  255 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO);
  256 + assertEquals(99L, result.getId());
  257 + verify(groupMessageMapper).insert(any(ImGroupMessageDO.class));
428 } 258 }
429 259
430 - @Test  
431 - public void testPullMessages_pageTrimmedToSize() {  
432 - LocalDateTime now = LocalDateTime.now();  
433 - ImGroupMemberDO activeMember = ImGroupMemberDO.builder()  
434 - .groupId(10L).userId(1L)  
435 - .status(CommonStatusEnum.ENABLE.getStatus())  
436 - .joinTime(now.minusDays(10)).build();  
437 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember));  
438 -  
439 - ImGroupMessageDO activeMsg1 = ImGroupMessageDO.builder()  
440 - .id(1000L).groupId(10L).senderId(2L).sendTime(now.minusHours(2)).build();  
441 - ImGroupMessageDO activeMsg2 = ImGroupMessageDO.builder()  
442 - .id(2000L).groupId(10L).senderId(2L).sendTime(now.minusHours(1)).build();  
443 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L),  
444 - any(LocalDateTime.class), eq(2)))  
445 - .thenReturn(ListUtil.of(activeMsg1, activeMsg2));  
446 -  
447 - LocalDateTime quitTime = now.minusDays(1);  
448 - ImGroupMemberDO quitMember = ImGroupMemberDO.builder()  
449 - .groupId(20L).userId(1L)  
450 - .status(CommonStatusEnum.DISABLE.getStatus())  
451 - .joinTime(now.minusDays(20))  
452 - .quitTime(quitTime).build();  
453 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
454 - .thenReturn(ListUtil.of(quitMember));  
455 -  
456 - ImGroupMessageDO quit1 = ImGroupMessageDO.builder()  
457 - .id(150L).groupId(20L).senderId(99L).sendTime(now.minusDays(5)).build();  
458 - ImGroupMessageDO quit2 = ImGroupMessageDO.builder()  
459 - .id(160L).groupId(20L).senderId(99L).sendTime(now.minusDays(4)).build();  
460 - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(0L),  
461 - any(LocalDateTime.class), eq(quitTime), eq(2)))  
462 - .thenReturn(ListUtil.of(quit1, quit2));  
463 -  
464 - when(groupMessageReadRedisDAO.getReadMaxMessageId(anyLong(), eq(1L))).thenReturn(null);  
465 -  
466 - List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 2);  
467 -  
468 - assertEquals(2, result.size());  
469 - assertEquals(150L, result.get(0).getId());  
470 - assertEquals(160L, result.get(1).getId());  
471 - } 260 + // ========== pull 测试 ==========
472 261
473 @Test 262 @Test
474 - public void testPullMessages_pullSetsReadFromRedisCursor() { 263 + public void testPullMessages_doesNotOverwriteStatus() {
  264 + // 准备:拉到两条消息;Phase 2 起 pull 不再按读位置覆盖 status,已读由前端按读位置判断
475 LocalDateTime now = LocalDateTime.now(); 265 LocalDateTime now = LocalDateTime.now();
476 ImGroupMemberDO member = ImGroupMemberDO.builder() 266 ImGroupMemberDO member = ImGroupMemberDO.builder()
477 .groupId(10L).userId(1L) 267 .groupId(10L).userId(1L)
478 .status(CommonStatusEnum.ENABLE.getStatus()) 268 .status(CommonStatusEnum.ENABLE.getStatus())
479 .joinTime(now.minusDays(10)).build(); 269 .joinTime(now.minusDays(10)).build();
480 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); 270 + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member));
481 271
482 ImGroupMessageDO low = ImGroupMessageDO.builder() 272 ImGroupMessageDO low = ImGroupMessageDO.builder()
483 - .id(5L).groupId(10L).senderId(2L).status(ImMessageStatusEnum.UNREAD.getStatus()) 273 + .id(5L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L))
  274 + .status(ImMessageStatusEnum.NORMAL.getStatus())
484 .sendTime(now.minusHours(2)).build(); 275 .sendTime(now.minusHours(2)).build();
485 ImGroupMessageDO high = ImGroupMessageDO.builder() 276 ImGroupMessageDO high = ImGroupMessageDO.builder()
486 - .id(10L).groupId(10L).senderId(2L).status(ImMessageStatusEnum.UNREAD.getStatus()) 277 + .id(10L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L))
  278 + .status(ImMessageStatusEnum.NORMAL.getStatus())
487 .sendTime(now.minusHours(1)).build(); 279 .sendTime(now.minusHours(1)).build();
488 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), 280 + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L),
489 any(LocalDateTime.class), eq(100))) 281 any(LocalDateTime.class), eq(100)))
490 - .thenReturn(ListUtil.of(low, high));  
491 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
492 - .thenReturn(ListUtil.of());  
493 -  
494 - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(7L); 282 + .thenReturn(List.of(low, high));
495 283
496 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100); 284 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
497 285
  286 + // 断言:status 保持 DB 原值(都 NORMAL),pull 不覆盖
498 assertEquals(2, result.size()); 287 assertEquals(2, result.size());
499 - assertEquals(ImMessageStatusEnum.READ.getStatus(), result.get(0).getStatus());  
500 - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.get(1).getStatus()); 288 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus());
  289 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(1).getStatus());
501 } 290 }
502 291
503 @Test 292 @Test
504 public void testPullMessages_receiptReadCountForSender() { 293 public void testPullMessages_receiptReadCountForSender() {
  294 + // 准备:本人发送的回执消息,可见成员 {1,2,3},分母排除发送者后为 {2,3}
505 LocalDateTime now = LocalDateTime.now(); 295 LocalDateTime now = LocalDateTime.now();
506 ImGroupMemberDO member = ImGroupMemberDO.builder() 296 ImGroupMemberDO member = ImGroupMemberDO.builder()
507 .groupId(10L).userId(1L) 297 .groupId(10L).userId(1L)
508 .status(CommonStatusEnum.ENABLE.getStatus()) 298 .status(CommonStatusEnum.ENABLE.getStatus())
509 .joinTime(now.minusDays(10)).build(); 299 .joinTime(now.minusDays(10)).build();
510 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); 300 + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member));
511 301
512 ImGroupMessageDO receiptMsg = ImGroupMessageDO.builder() 302 ImGroupMessageDO receiptMsg = ImGroupMessageDO.builder()
513 .id(100L).groupId(10L).senderId(1L) 303 .id(100L).groupId(10L).senderId(1L)
514 - .status(ImMessageStatusEnum.UNREAD.getStatus())  
515 - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) 304 + .receiverUserIds(List.of(1L, 2L, 3L))
  305 + .status(ImMessageStatusEnum.NORMAL.getStatus())
  306 + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus())
516 .sendTime(now.minusHours(1)).build(); 307 .sendTime(now.minusHours(1)).build();
517 - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), 308 + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L),
518 any(LocalDateTime.class), eq(100))) 309 any(LocalDateTime.class), eq(100)))
519 - .thenReturn(ListUtil.of(receiptMsg));  
520 - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class)))  
521 - .thenReturn(ListUtil.of()); 310 + .thenReturn(List.of(receiptMsg));
522 311
523 - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(100L);  
524 Map<Long, Long> positions = new HashMap<>(); 312 Map<Long, Long> positions = new HashMap<>();
525 positions.put(1L, 100L); 313 positions.put(1L, 100L);
526 positions.put(2L, 100L); 314 positions.put(2L, 100L);
527 positions.put(3L, 50L); 315 positions.put(3L, 50L);
528 - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); 316 + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L)))
  317 + .thenReturn(positions);
529 318
530 - List<ImGroupMemberDO> allMembers = ListUtil.of( 319 + List<ImGroupMemberDO> allMembers = List.of(
531 member, 320 member,
532 ImGroupMemberDO.builder().groupId(10L).userId(2L) 321 ImGroupMemberDO.builder().groupId(10L).userId(2L)
533 .status(CommonStatusEnum.ENABLE.getStatus()) 322 .status(CommonStatusEnum.ENABLE.getStatus())
@@ -536,14 +325,44 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -536,14 +325,44 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
536 .status(CommonStatusEnum.ENABLE.getStatus()) 325 .status(CommonStatusEnum.ENABLE.getStatus())
537 .joinTime(now.minusDays(20)).build() 326 .joinTime(now.minusDays(20)).build()
538 ); 327 );
539 - when(groupMemberService.getGroupMemberListByGroupId(10L)).thenReturn(allMembers); 328 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers);
540 329
541 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100); 330 List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
542 331
  332 + // 断言:分母 {2,3},仅用户 2 读到 >=100 → readCount==1
543 assertEquals(1, result.size()); 333 assertEquals(1, result.size());
544 assertEquals(1, result.get(0).getReadCount()); 334 assertEquals(1, result.get(0).getReadCount());
545 } 335 }
546 336
  337 + @Test
  338 + public void testPullMessages_noReceiptMessageSkipsReadCount() {
  339 + // 准备:本人发送但不需要回执(NO_RECEIPT)的消息——不补 readCount,status 也不被覆盖
  340 + LocalDateTime now = LocalDateTime.now();
  341 + ImGroupMemberDO member = ImGroupMemberDO.builder()
  342 + .groupId(10L).userId(1L)
  343 + .status(CommonStatusEnum.ENABLE.getStatus())
  344 + .joinTime(now.minusDays(10)).build();
  345 + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member));
  346 +
  347 + ImGroupMessageDO noReceiptMsg = ImGroupMessageDO.builder()
  348 + .id(100L).groupId(10L).senderId(1L)
  349 + .receiverUserIds(List.of(1L, 2L, 3L))
  350 + .status(ImMessageStatusEnum.NORMAL.getStatus())
  351 + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus())
  352 + .sendTime(now.minusHours(1)).build();
  353 + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L),
  354 + any(LocalDateTime.class), eq(100)))
  355 + .thenReturn(List.of(noReceiptMsg));
  356 +
  357 + List<ImGroupMessageDO> result = groupMessageService.pullGroupMessageList(1L, 0L, 100);
  358 +
  359 + // 断言:status 保持 NORMAL(pull 不覆盖),NO_RECEIPT 不补 readCount,也不查 readCount 相关的读位置映射
  360 + assertEquals(1, result.size());
  361 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus());
  362 + assertNull(result.get(0).getReadCount());
  363 + verify(conversationReadService, never()).getUserReadMessageIdMap(anyInt(), anyLong());
  364 + }
  365 +
547 // ========== 撤回测试 ========== 366 // ========== 撤回测试 ==========
548 367
549 @Test 368 @Test
@@ -555,7 +374,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -555,7 +374,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
555 // 准备:消息由用户 1 发送,刚发送 5 分钟内 374 // 准备:消息由用户 1 发送,刚发送 5 分钟内
556 ImGroupMessageDO message = ImGroupMessageDO.builder() 375 ImGroupMessageDO message = ImGroupMessageDO.builder()
557 .id(50L).senderId(1L).groupId(10L) 376 .id(50L).senderId(1L).groupId(10L)
558 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 377 + .status(ImMessageStatusEnum.NORMAL.getStatus())
559 .sendTime(LocalDateTime.now()).build(); 378 .sendTime(LocalDateTime.now()).build();
560 when(groupMessageMapper.selectById(50L)).thenReturn(message); 379 when(groupMessageMapper.selectById(50L)).thenReturn(message);
561 // 撤回前需要校验用户仍在群中 380 // 撤回前需要校验用户仍在群中
@@ -566,7 +385,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -566,7 +385,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
566 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); 385 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1);
567 386
568 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) 387 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
569 - .thenReturn(ListUtil.of(1L, 2L)); 388 + .thenReturn(List.of(1L, 2L));
570 389
571 // 调用 390 // 调用
572 ImGroupMessageDO result = groupMessageService.recallGroupMessage(1L, 50L); 391 ImGroupMessageDO result = groupMessageService.recallGroupMessage(1L, 50L);
@@ -577,9 +396,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -577,9 +396,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
577 verify(groupMessageMapper).updateById(any(ImGroupMessageDO.class)); 396 verify(groupMessageMapper).updateById(any(ImGroupMessageDO.class));
578 verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); 397 verify(groupMessageMapper).insert(any(ImGroupMessageDO.class));
579 // 验证:给 2 个活跃成员推送撤回提示 398 // 验证:给 2 个活跃成员推送撤回提示
580 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection<Long> ids) -> 399 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
581 ids.size() == 2 && ids.contains(1L) && ids.contains(2L)), 400 ids.size() == 2 && ids.contains(1L) && ids.contains(2L)),
582 - any(ImGroupMessageDTO.class)); 401 + anyInt(), anyInt(), any());
583 } 402 }
584 } 403 }
585 404
@@ -588,7 +407,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -588,7 +407,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
588 // 准备 407 // 准备
589 ImGroupMessageDO message = ImGroupMessageDO.builder() 408 ImGroupMessageDO message = ImGroupMessageDO.builder()
590 .id(50L).senderId(2L).groupId(10L) 409 .id(50L).senderId(2L).groupId(10L)
591 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 410 + .status(ImMessageStatusEnum.NORMAL.getStatus())
592 .sendTime(LocalDateTime.now()).build(); 411 .sendTime(LocalDateTime.now()).build();
593 when(groupMessageMapper.selectById(50L)).thenReturn(message); 412 when(groupMessageMapper.selectById(50L)).thenReturn(message);
594 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( 413 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(
@@ -624,7 +443,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -624,7 +443,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
624 // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) 443 // 准备:消息发送于 10 分钟前(超过 5 分钟窗口)
625 ImGroupMessageDO message = ImGroupMessageDO.builder() 444 ImGroupMessageDO message = ImGroupMessageDO.builder()
626 .id(50L).senderId(1L).groupId(10L) 445 .id(50L).senderId(1L).groupId(10L)
627 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 446 + .status(ImMessageStatusEnum.NORMAL.getStatus())
628 .sendTime(LocalDateTime.now().minusMinutes(10)).build(); 447 .sendTime(LocalDateTime.now().minusMinutes(10)).build();
629 when(groupMessageMapper.selectById(50L)).thenReturn(message); 448 when(groupMessageMapper.selectById(50L)).thenReturn(message);
630 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( 449 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(
@@ -641,16 +460,16 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -641,16 +460,16 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
641 460
642 @Test 461 @Test
643 public void testReadMessages_messageInvisible() { 462 public void testReadMessages_messageInvisible() {
644 - // 准备:用户不是群成员  
645 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(null); 463 + // 准备:消息不在用户接收快照内
646 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 464 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
647 - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build()); 465 + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L))
  466 + .sendTime(LocalDateTime.now()).build());
648 467
649 // 调用并断言 468 // 调用并断言
650 ServiceException exception = assertThrows(ServiceException.class, 469 ServiceException exception = assertThrows(ServiceException.class,
651 () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); 470 () -> groupMessageService.readGroupMessages(1L, 10L, 100L));
652 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); 471 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode());
653 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); 472 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
654 } 473 }
655 474
656 @Test 475 @Test
@@ -662,18 +481,19 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -662,18 +481,19 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
662 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(); 481 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build();
663 when(groupMemberService.validateMemberInGroup(10L, 5L)).thenReturn(currentMember); 482 when(groupMemberService.validateMemberInGroup(10L, 5L)).thenReturn(currentMember);
664 483
665 - // 准备:消息由用户 5 发,发送时间在 2026-04-12 10:00 484 + // 准备:消息由用户 5 发,快照可见范围为 {1,2,5}(用户 3 不在快照内 → 不可见)
666 ImGroupMessageDO message = ImGroupMessageDO.builder() 485 ImGroupMessageDO message = ImGroupMessageDO.builder()
667 .id(80L).groupId(10L).senderId(5L) 486 .id(80L).groupId(10L).senderId(5L)
  487 + .receiverUserIds(List.of(1L, 2L, 5L))
668 .sendTime(LocalDateTime.of(2026, 4, 12, 10, 0, 0)).build(); 488 .sendTime(LocalDateTime.of(2026, 4, 12, 10, 0, 0)).build();
669 when(groupMessageMapper.selectById(80L)).thenReturn(message); 489 when(groupMessageMapper.selectById(80L)).thenReturn(message);
670 490
671 // 准备:群成员一览 491 // 准备:群成员一览
672 - // 用户 1: 正常,入群在消息之前  
673 - // 用户 2: 正常,入群在消息之前  
674 - // 用户 3: 正常,但入群在消息之后 → 不可见 492 + // 用户 1: 在快照内
  493 + // 用户 2: 在快照内
  494 + // 用户 3: 不在快照内 → 不可见
675 // 用户 5: 发送者,不计入回执 495 // 用户 5: 发送者,不计入回执
676 - List<ImGroupMemberDO> allMembers = ListUtil.of( 496 + List<ImGroupMemberDO> allMembers = List.of(
677 currentMember, 497 currentMember,
678 ImGroupMemberDO.builder().groupId(10L).userId(1L) 498 ImGroupMemberDO.builder().groupId(10L).userId(1L)
679 .status(CommonStatusEnum.ENABLE.getStatus()) 499 .status(CommonStatusEnum.ENABLE.getStatus())
@@ -683,19 +503,20 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -683,19 +503,20 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
683 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), 503 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(),
684 ImGroupMemberDO.builder().groupId(10L).userId(3L) 504 ImGroupMemberDO.builder().groupId(10L).userId(3L)
685 .status(CommonStatusEnum.ENABLE.getStatus()) 505 .status(CommonStatusEnum.ENABLE.getStatus())
686 - .joinTime(LocalDateTime.of(2026, 4, 13, 0, 0, 0)).build(), // 消息之后才入群 506 + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(),
687 ImGroupMemberDO.builder().groupId(10L).userId(5L) 507 ImGroupMemberDO.builder().groupId(10L).userId(5L)
688 .status(CommonStatusEnum.ENABLE.getStatus()) 508 .status(CommonStatusEnum.ENABLE.getStatus())
689 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build() // 发送者 509 .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build() // 发送者
690 ); 510 );
691 - when(groupMemberService.getGroupMemberListByGroupId(10L)).thenReturn(allMembers); 511 + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers);
692 512
693 - // 准备:Redis 已读位置 — 用户 1 读到 100, 用户 2 读到 50, 用户 3 读到 200 513 + // 准备:已读位置 — 用户 1 读到 100, 用户 2 读到 50, 用户 3 读到 200
694 Map<Long, Long> positions = new HashMap<>(); 514 Map<Long, Long> positions = new HashMap<>();
695 positions.put(1L, 100L); 515 positions.put(1L, 100L);
696 positions.put(2L, 50L); 516 positions.put(2L, 50L);
697 positions.put(3L, 200L); 517 positions.put(3L, 200L);
698 - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); 518 + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L)))
  519 + .thenReturn(positions);
699 520
700 // 调用:查询 messageId=80 的已读用户 521 // 调用:查询 messageId=80 的已读用户
701 List<Long> readUsers = groupMessageService.getGroupReadUserIds(5L, 10L, 80L); 522 List<Long> readUsers = groupMessageService.getGroupReadUserIds(5L, 10L, 80L);
@@ -703,7 +524,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -703,7 +524,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
703 // 断言: 524 // 断言:
704 // 用户 1: 可见 + readMaxId=100>=80 → 已读 ✓ 525 // 用户 1: 可见 + readMaxId=100>=80 → 已读 ✓
705 // 用户 2: 可见 + readMaxId=50<80 → 未读 ✗ 526 // 用户 2: 可见 + readMaxId=50<80 → 未读 ✗
706 - // 用户 3: 入群在消息之后 → 不可见 → 不算 527 + // 用户 3: 不在快照内 → 不可见 → 不算
707 // 用户 5: 发送者 → 排除 528 // 用户 5: 发送者 → 排除
708 assertEquals(1, readUsers.size()); 529 assertEquals(1, readUsers.size());
709 assertTrue(readUsers.contains(1L)); 530 assertTrue(readUsers.contains(1L));
@@ -724,7 +545,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -724,7 +545,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
724 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); 545 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode());
725 } 546 }
726 547
727 - // ========== 发送:补充边界 ========== 548 + // ========== 发送边界 ==========
728 549
729 @Test 550 @Test
730 public void testSendMessage_receiptPending() { 551 public void testSendMessage_receiptPending() {
@@ -743,14 +564,14 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -743,14 +564,14 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
743 ImGroupMemberDO member = ImGroupMemberDO.builder() 564 ImGroupMemberDO member = ImGroupMemberDO.builder()
744 .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); 565 .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build();
745 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); 566 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
746 - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); 567 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L));
747 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); 568 when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1);
748 569
749 // 调用 570 // 调用
750 ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); 571 ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO);
751 572
752 // 断言:receipt=true → 回执状态为 PENDING 573 // 断言:receipt=true → 回执状态为 PENDING
753 - assertEquals(ImGroupMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); 574 + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus());
754 } 575 }
755 } 576 }
756 577
@@ -775,10 +596,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -775,10 +596,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
775 assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); 596 assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode());
776 // 断言:不入库、不推送 597 // 断言:不入库、不推送
777 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); 598 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class));
778 - verify(imWebSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); 599 + verify(imWebSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any());
779 } 600 }
780 601
781 - // ========== 撤回:补充边界 ========== 602 + // ========== 撤回边界 ==========
782 603
783 @Test 604 @Test
784 public void testRecallMessage_notExists() { 605 public void testRecallMessage_notExists() {
@@ -794,7 +615,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -794,7 +615,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
794 // 准备:消息合法、可撤回时间内,但发送人已退群 615 // 准备:消息合法、可撤回时间内,但发送人已退群
795 ImGroupMessageDO message = ImGroupMessageDO.builder() 616 ImGroupMessageDO message = ImGroupMessageDO.builder()
796 .id(50L).senderId(1L).groupId(10L) 617 .id(50L).senderId(1L).groupId(10L)
797 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 618 + .status(ImMessageStatusEnum.NORMAL.getStatus())
798 .sendTime(LocalDateTime.now()).build(); 619 .sendTime(LocalDateTime.now()).build();
799 when(groupMessageMapper.selectById(50L)).thenReturn(message); 620 when(groupMessageMapper.selectById(50L)).thenReturn(message);
800 when(groupMemberService.validateMemberInGroup(10L, 1L)) 621 when(groupMemberService.validateMemberInGroup(10L, 1L))
@@ -808,7 +629,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -808,7 +629,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
808 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); 629 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class));
809 } 630 }
810 631
811 - // ========== 已读:happy path + 跳过 ========== 632 + // ========== 已读事件 ==========
812 633
813 @Test 634 @Test
814 public void testReadGroupMessages_success() { 635 public void testReadGroupMessages_success() {
@@ -816,24 +637,24 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -816,24 +637,24 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
816 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) 637 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class)))
817 .thenReturn(groupMessageService); 638 .thenReturn(groupMessageService);
818 639
819 - // 准备:用户在群;Redis 游标从 5 前进到 100  
820 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
821 - .groupId(10L).userId(1L)  
822 - .status(CommonStatusEnum.ENABLE.getStatus())  
823 - .joinTime(LocalDateTime.now().minusDays(1)).build();  
824 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); 640 + // 准备:消息对用户可见;已读位置从 5 前进到 100
825 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 641 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
826 - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build());  
827 - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(5L); 642 + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L))
  643 + .sendTime(LocalDateTime.now()).build());
  644 + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L))
  645 + .thenReturn(5L);
  646 + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L))
  647 + .thenReturn(true);
828 // readGroupMessageEvent 内部会调 selectListByGroupIdAndPendingReceipt → 返回空简化流程 648 // readGroupMessageEvent 内部会调 selectListByGroupIdAndPendingReceipt → 返回空简化流程
829 - when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 5L, 100L)).thenReturn(ListUtil.of()); 649 + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 5L, 100L)).thenReturn(List.of());
830 650
831 // 调用 651 // 调用
832 groupMessageService.readGroupMessages(1L, 10L, 100L); 652 groupMessageService.readGroupMessages(1L, 10L, 100L);
833 653
834 - // 断言:Redis 游标更新 + READ 事件  
835 - verify(groupMessageReadRedisDAO).updateReadMaxMessageId(10L, 1L, 100L);  
836 - verify(imWebSocketService).sendGroupMessageAsync(eq(1L), any(ImGroupMessageDTO.class)); 654 + // 断言:已读位置更新 + READ 事件
  655 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L);
  656 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
  657 + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L);
837 } 658 }
838 } 659 }
839 660
@@ -846,9 +667,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -846,9 +667,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
846 ServiceException exception = assertThrows(ServiceException.class, 667 ServiceException exception = assertThrows(ServiceException.class,
847 () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); 668 () -> groupMessageService.readGroupMessages(1L, 10L, 100L));
848 assertEquals(MESSAGE_GROUP_READ_DISABLED.getCode(), exception.getCode()); 669 assertEquals(MESSAGE_GROUP_READ_DISABLED.getCode(), exception.getCode());
849 - // 断言:Redis 不写、不推送  
850 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong());  
851 - verify(imWebSocketService, never()).sendGroupMessageAsync(anyLong(), any(ImGroupMessageDTO.class)); 670 + // 断言:已读位置不写、不推送
  671 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
  672 + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
852 } 673 }
853 674
854 @Test 675 @Test
@@ -880,11 +701,11 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -880,11 +701,11 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
880 ImGroupMemberDO member = ImGroupMemberDO.builder() 701 ImGroupMemberDO member = ImGroupMemberDO.builder()
881 .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); 702 .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build();
882 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); 703 when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member);
883 - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L, 2L)); 704 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L));
884 705
885 ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); 706 ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO);
886 707
887 - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus(), 708 + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus(),
888 "群已读关闭时即使发送方传 receipt=true 也强制落 NO_RECEIPT"); 709 "群已读关闭时即使发送方传 receipt=true 也强制落 NO_RECEIPT");
889 } 710 }
890 } 711 }
@@ -892,31 +713,26 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -892,31 +713,26 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
892 @Test 713 @Test
893 public void testReadGroupMessages_cursorAlreadyAhead() { 714 public void testReadGroupMessages_cursorAlreadyAhead() {
894 // 准备:已读游标已 >= 目标,直接返回 715 // 准备:已读游标已 >= 目标,直接返回
895 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
896 - .groupId(10L).userId(1L)  
897 - .status(CommonStatusEnum.ENABLE.getStatus())  
898 - .joinTime(LocalDateTime.now().minusDays(1)).build();  
899 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member);  
900 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 716 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
901 - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build());  
902 - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(200L); 717 + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L))
  718 + .sendTime(LocalDateTime.now()).build());
  719 + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L))
  720 + .thenReturn(200L);
  721 + // CAS 命中旧位置 200 > 100,单调更新不前进,返回 false
  722 + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L))
  723 + .thenReturn(false);
903 724
904 // 调用 725 // 调用
905 groupMessageService.readGroupMessages(1L, 10L, 100L); 726 groupMessageService.readGroupMessages(1L, 10L, 100L);
906 727
907 - // 断言:不更新、不推送  
908 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong());  
909 - verify(imWebSocketService, never()).sendGroupMessageAsync(anyLong(), any(ImGroupMessageDTO.class)); 728 + // 断言:调用了 CAS 更新但读位置未前进 → 不推送
  729 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L);
  730 + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
910 } 731 }
911 732
912 @Test 733 @Test
913 public void testReadGroupMessages_messageNotInGroup() { 734 public void testReadGroupMessages_messageNotInGroup() {
914 - // 准备:用户在群,但 messageId 属于其它群  
915 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
916 - .groupId(10L).userId(1L)  
917 - .status(CommonStatusEnum.ENABLE.getStatus())  
918 - .joinTime(LocalDateTime.now().minusDays(1)).build();  
919 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); 735 + // 准备:messageId 属于其它群
920 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 736 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
921 .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); 737 .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build());
922 738
@@ -924,7 +740,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -924,7 +740,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
924 ServiceException exception = assertThrows(ServiceException.class, 740 ServiceException exception = assertThrows(ServiceException.class,
925 () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); 741 () -> groupMessageService.readGroupMessages(1L, 10L, 100L));
926 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); 742 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode());
927 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); 743 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
928 } 744 }
929 745
930 @Test 746 @Test
@@ -933,62 +749,54 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -933,62 +749,54 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
933 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) 749 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class)))
934 .thenReturn(groupMessageService); 750 .thenReturn(groupMessageService);
935 751
936 - LocalDateTime now = LocalDateTime.now();  
937 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
938 - .groupId(10L).userId(1L)  
939 - .status(CommonStatusEnum.DISABLE.getStatus())  
940 - .joinTime(now.minusDays(1))  
941 - .quitTime(now.plusMinutes(1)).build();  
942 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); 752 + // 准备:历史消息的接收快照包含用户
943 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 753 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
944 - .id(100L).groupId(10L).senderId(2L).sendTime(now)  
945 - .receiverUserIds(ListUtil.of(1L, 2L)).build());  
946 - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(0L);  
947 - when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(ListUtil.of()); 754 + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L, 2L))
  755 + .sendTime(LocalDateTime.now().minusMinutes(1)).build());
  756 + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L))
  757 + .thenReturn(0L);
  758 + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L))
  759 + .thenReturn(true);
  760 + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(List.of());
948 761
949 // 调用 762 // 调用
950 groupMessageService.readGroupMessages(1L, 10L, 100L); 763 groupMessageService.readGroupMessages(1L, 10L, 100L);
951 764
952 // 断言:不校验当前在群,直接推进已读位置 765 // 断言:不校验当前在群,直接推进已读位置
953 verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); 766 verify(groupMemberService, never()).validateMemberInGroup(10L, 1L);
954 - verify(groupMessageReadRedisDAO).updateReadMaxMessageId(10L, 1L, 100L); 767 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L);
955 } 768 }
956 } 769 }
957 770
958 @Test 771 @Test
959 public void testReadGroupMessages_quitMemberInvisibleMessage() { 772 public void testReadGroupMessages_quitMemberInvisibleMessage() {
960 - LocalDateTime now = LocalDateTime.now();  
961 - ImGroupMemberDO member = ImGroupMemberDO.builder()  
962 - .groupId(10L).userId(1L)  
963 - .status(CommonStatusEnum.DISABLE.getStatus())  
964 - .joinTime(now.minusDays(2))  
965 - .quitTime(now.minusDays(1)).build();  
966 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); 773 + // 准备:消息不在历史接收快照内
967 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() 774 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder()
968 - .id(100L).groupId(10L).senderId(2L).sendTime(now)  
969 - .receiverUserIds(ListUtil.of(1L, 2L)).build()); 775 + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L))
  776 + .sendTime(LocalDateTime.now()).build());
970 777
971 // 调用并断言 778 // 调用并断言
972 ServiceException exception = assertThrows(ServiceException.class, 779 ServiceException exception = assertThrows(ServiceException.class,
973 () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); 780 () -> groupMessageService.readGroupMessages(1L, 10L, 100L));
974 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); 781 assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode());
975 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); 782 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong());
976 } 783 }
977 784
978 - // ========== 回执:DONE 状态迁移 ========== 785 + // ========== 回执状态迁移 ==========
979 786
980 @Test 787 @Test
981 public void testReadGroupMessageEvent_receiptDoneTransition() { 788 public void testReadGroupMessageEvent_receiptDoneTransition() {
982 // 准备:消息 100 发送者 5,群 10 活跃成员 {5,1,2},可见用户中除发送者外 {1,2} 789 // 准备:消息 100 发送者 5,群 10 活跃成员 {5,1,2},可见用户中除发送者外 {1,2}
983 - // Redis 位置 1→100、2→100 → 全部已读 → 迁移为 DONE 790 + // 已读位置 1→100、2→100 → 全部已读 → 迁移为 DONE
984 ImGroupMessageDO pending = ImGroupMessageDO.builder() 791 ImGroupMessageDO pending = ImGroupMessageDO.builder()
985 .id(100L).groupId(10L).senderId(5L) 792 .id(100L).groupId(10L).senderId(5L)
  793 + .receiverUserIds(List.of(5L, 1L, 2L))
986 .sendTime(LocalDateTime.now().minusMinutes(1)) 794 .sendTime(LocalDateTime.now().minusMinutes(1))
987 - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus())  
988 - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); 795 + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus())
  796 + .status(ImMessageStatusEnum.NORMAL.getStatus()).build();
989 when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) 797 when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L))
990 - .thenReturn(ListUtil.of(pending));  
991 - List<ImGroupMemberDO> activeMembers = ListUtil.of( 798 + .thenReturn(List.of(pending));
  799 + List<ImGroupMemberDO> activeMembers = List.of(
992 ImGroupMemberDO.builder().groupId(10L).userId(5L) 800 ImGroupMemberDO.builder().groupId(10L).userId(5L)
993 .status(CommonStatusEnum.ENABLE.getStatus()) 801 .status(CommonStatusEnum.ENABLE.getStatus())
994 .joinTime(LocalDateTime.now().minusDays(10)).build(), 802 .joinTime(LocalDateTime.now().minusDays(10)).build(),
@@ -1003,7 +811,8 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1003,7 +811,8 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1003 Map<Long, Long> positions = new HashMap<>(); 811 Map<Long, Long> positions = new HashMap<>();
1004 positions.put(1L, 100L); 812 positions.put(1L, 100L);
1005 positions.put(2L, 100L); 813 positions.put(2L, 100L);
1006 - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); 814 + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L))
  815 + .thenReturn(positions);
1007 816
1008 // 调用 817 // 调用
1009 groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); 818 groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L);
@@ -1012,9 +821,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1012,9 +821,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1012 ArgumentCaptor<ImGroupMessageDO> captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); 821 ArgumentCaptor<ImGroupMessageDO> captor = ArgumentCaptor.forClass(ImGroupMessageDO.class);
1013 verify(groupMessageMapper).updateById(captor.capture()); 822 verify(groupMessageMapper).updateById(captor.capture());
1014 assertEquals(100L, captor.getValue().getId()); 823 assertEquals(100L, captor.getValue().getId());
1015 - assertEquals(ImGroupMessageReceiptStatusEnum.DONE.getStatus(), captor.getValue().getReceiptStatus()); 824 + assertEquals(ImMessageReceiptStatusEnum.DONE.getStatus(), captor.getValue().getReceiptStatus());
1016 // 断言:给消息发送方(5)推送 RECEIPT 事件 825 // 断言:给消息发送方(5)推送 RECEIPT 事件
1017 - verify(imWebSocketService).sendGroupMessageAsync(eq(5L), any(ImGroupMessageDTO.class)); 826 + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any());
1018 } 827 }
1019 828
1020 @Test 829 @Test
@@ -1022,12 +831,13 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1022,12 +831,13 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1022 // 准备:用户 2 还没读到 100 → 仍为 PENDING,不更新 DB 831 // 准备:用户 2 还没读到 100 → 仍为 PENDING,不更新 DB
1023 ImGroupMessageDO pending = ImGroupMessageDO.builder() 832 ImGroupMessageDO pending = ImGroupMessageDO.builder()
1024 .id(100L).groupId(10L).senderId(5L) 833 .id(100L).groupId(10L).senderId(5L)
  834 + .receiverUserIds(List.of(5L, 1L, 2L))
1025 .sendTime(LocalDateTime.now().minusMinutes(1)) 835 .sendTime(LocalDateTime.now().minusMinutes(1))
1026 - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus())  
1027 - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); 836 + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus())
  837 + .status(ImMessageStatusEnum.NORMAL.getStatus()).build();
1028 when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) 838 when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L))
1029 - .thenReturn(ListUtil.of(pending));  
1030 - List<ImGroupMemberDO> activeMembers = ListUtil.of( 839 + .thenReturn(List.of(pending));
  840 + List<ImGroupMemberDO> activeMembers = List.of(
1031 ImGroupMemberDO.builder().groupId(10L).userId(5L) 841 ImGroupMemberDO.builder().groupId(10L).userId(5L)
1032 .status(CommonStatusEnum.ENABLE.getStatus()) 842 .status(CommonStatusEnum.ENABLE.getStatus())
1033 .joinTime(LocalDateTime.now().minusDays(10)).build(), 843 .joinTime(LocalDateTime.now().minusDays(10)).build(),
@@ -1042,33 +852,34 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1042,33 +852,34 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1042 Map<Long, Long> positions = new HashMap<>(); 852 Map<Long, Long> positions = new HashMap<>();
1043 positions.put(1L, 100L); 853 positions.put(1L, 100L);
1044 positions.put(2L, 50L); // 还没读到 100 854 positions.put(2L, 50L); // 还没读到 100
1045 - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); 855 + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L))
  856 + .thenReturn(positions);
1046 857
1047 groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); 858 groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L);
1048 859
1049 // 断言:回执状态保持 PENDING,不更新 DB 860 // 断言:回执状态保持 PENDING,不更新 DB
1050 verify(groupMessageMapper, never()).updateById(any(ImGroupMessageDO.class)); 861 verify(groupMessageMapper, never()).updateById(any(ImGroupMessageDO.class));
1051 // 仍然推送 RECEIPT 事件给发送方(携带已读人数) 862 // 仍然推送 RECEIPT 事件给发送方(携带已读人数)
1052 - verify(imWebSocketService).sendGroupMessageAsync(eq(5L), any(ImGroupMessageDTO.class)); 863 + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any());
1053 } 864 }
1054 865
1055 - // ========== sendGroupMessage(senderId, dto):receiverUserIds 定向过滤 ========== 866 + // ========== 定向群消息 ==========
1056 867
1057 @Test 868 @Test
1058 public void testSendGroupMessage_noReceiverUserIds_broadcastsToAllMembers() { 869 public void testSendGroupMessage_noReceiverUserIds_broadcastsToAllMembers() {
1059 // 准备:无定向 → 应发给所有 ENABLE 成员(含发送者自己,多端同步) 870 // 准备:无定向 → 应发给所有 ENABLE 成员(含发送者自己,多端同步)
1060 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 871 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1061 - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) 872 + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType())
1062 .setContent("{\"messageId\":1}"); 873 .setContent("{\"messageId\":1}");
1063 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) 874 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
1064 - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); 875 + .thenReturn(List.of(1L, 2L, 3L, 4L));
1065 876
1066 groupMessageService.sendGroupMessage(1L, dto); 877 groupMessageService.sendGroupMessage(1L, dto);
1067 878
1068 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection<Long> ids) -> 879 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
1069 ids.size() == 4 && ids.contains(1L) && ids.contains(2L) 880 ids.size() == 4 && ids.contains(1L) && ids.contains(2L)
1070 && ids.contains(3L) && ids.contains(4L)), 881 && ids.contains(3L) && ids.contains(4L)),
1071 - any(ImGroupMessageDTO.class)); 882 + anyInt(), anyInt(), any());
1072 } 883 }
1073 884
1074 @Test 885 @Test
@@ -1076,47 +887,48 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1076,47 +887,48 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1076 // 准备:定向给 {2,3},发送者 1;群成员 {1,2,3,4} 887 // 准备:定向给 {2,3},发送者 1;群成员 {1,2,3,4}
1077 // 预期推送目标:{1,2,3}(发送者自己多端同步 + 定向列表);4 被过滤 888 // 预期推送目标:{1,2,3}(发送者自己多端同步 + 定向列表);4 被过滤
1078 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 889 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1079 - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) 890 + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType())
1080 .setContent("{\"messageId\":1}") 891 .setContent("{\"messageId\":1}")
1081 - .setReceiverUserIds(ListUtil.of(2L, 3L)); 892 + .setReceiverUserIds(List.of(2L, 3L));
1082 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) 893 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
1083 - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); 894 + .thenReturn(List.of(1L, 2L, 3L, 4L));
1084 895
1085 groupMessageService.sendGroupMessage(1L, dto); 896 groupMessageService.sendGroupMessage(1L, dto);
1086 897
1087 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection<Long> ids) -> 898 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
1088 ids.size() == 3 && ids.contains(1L) && ids.contains(2L) 899 ids.size() == 3 && ids.contains(1L) && ids.contains(2L)
1089 && ids.contains(3L) && !ids.contains(4L)), 900 && ids.contains(3L) && !ids.contains(4L)),
1090 - any(ImGroupMessageDTO.class)); 901 + anyInt(), anyInt(), any());
1091 } 902 }
1092 903
1093 @Test 904 @Test
1094 - public void testSendGroupMessage_withReceiverUserIds_senderNotInGroup_senderStillExcluded() {  
1095 - // 边界:发送者不在群的 userId 列表里(理论上应先被 validateMemberInGroup 挡住,这里纯防御)  
1096 - // 预期:仅定向用户可见 905 + public void testSendGroupMessage_withReceiverUserIds_senderAlwaysIncluded() {
  906 + // 边界:发送者不在群成员缓存里(理论上应先被 validateMemberInGroup 挡住,这里纯防御)
  907 + // 预期:定向用户 + 发送者自己(始终纳入快照保证多端同步);非定向成员 4 被过滤
1097 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 908 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1098 - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) 909 + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType())
1099 .setContent("{\"messageId\":1}") 910 .setContent("{\"messageId\":1}")
1100 - .setReceiverUserIds(ListUtil.of(2L, 3L)); 911 + .setReceiverUserIds(List.of(2L, 3L));
1101 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) 912 when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L))
1102 - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); 913 + .thenReturn(List.of(1L, 2L, 3L, 4L));
1103 914
1104 groupMessageService.sendGroupMessage(99L, dto); 915 groupMessageService.sendGroupMessage(99L, dto);
1105 916
1106 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection<Long> ids) ->  
1107 - ids.size() == 2 && ids.contains(2L) && ids.contains(3L)),  
1108 - any(ImGroupMessageDTO.class)); 917 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
  918 + ids.size() == 3 && ids.contains(2L) && ids.contains(3L)
  919 + && ids.contains(99L) && !ids.contains(4L)),
  920 + anyInt(), anyInt(), any());
1109 } 921 }
1110 922
1111 - // ========== sendGroupMessage(senderId, dto):helper 行为 ========== 923 + // ========== DTO 群消息 ==========
1112 924
1113 @Test 925 @Test
1114 public void testSendGroupMessage_dto_persistsAndSerializesPojoContent() { 926 public void testSendGroupMessage_dto_persistsAndSerializesPojoContent() {
1115 // 准备:persistent=true 类型 + POJO content 927 // 准备:persistent=true 类型 + POJO content
1116 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 928 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1117 - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) 929 + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType())
1118 .setContent(new RecallMessage().setMessageId(50L)); 930 .setContent(new RecallMessage().setMessageId(50L));
1119 - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); 931 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L));
1120 932
1121 groupMessageService.sendGroupMessage(1L, dto); 933 groupMessageService.sendGroupMessage(1L, dto);
1122 934
@@ -1126,10 +938,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1126,10 +938,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1126 ImGroupMessageDO message = captor.getValue(); 938 ImGroupMessageDO message = captor.getValue();
1127 assertEquals(1L, message.getSenderId()); 939 assertEquals(1L, message.getSenderId());
1128 assertEquals(10L, message.getGroupId()); 940 assertEquals(10L, message.getGroupId());
1129 - assertEquals(ImMessageTypeEnum.RECALL.getType(), message.getType()); 941 + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType());
1130 assertEquals("{\"messageId\":50}", message.getContent()); 942 assertEquals("{\"messageId\":50}", message.getContent());
1131 - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), message.getStatus());  
1132 - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), message.getReceiptStatus()); 943 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus());
  944 + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), message.getReceiptStatus());
1133 assertNotNull(message.getClientMessageId()); 945 assertNotNull(message.getClientMessageId());
1134 assertNotNull(message.getSendTime()); 946 assertNotNull(message.getSendTime());
1135 } 947 }
@@ -1138,114 +950,103 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @@ -1138,114 +950,103 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest {
1138 public void testSendGroupMessage_dto_receiptPending() { 950 public void testSendGroupMessage_dto_receiptPending() {
1139 // 准备:dto.receipt=true → receiptStatus=PENDING 951 // 准备:dto.receipt=true → receiptStatus=PENDING
1140 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 952 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1141 - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) 953 + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType())
1142 .setContent("{\"messageId\":50}").setReceipt(true); 954 .setContent("{\"messageId\":50}").setReceipt(true);
1143 - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); 955 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L));
1144 956
1145 groupMessageService.sendGroupMessage(1L, dto); 957 groupMessageService.sendGroupMessage(1L, dto);
1146 958
1147 ArgumentCaptor<ImGroupMessageDO> captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); 959 ArgumentCaptor<ImGroupMessageDO> captor = ArgumentCaptor.forClass(ImGroupMessageDO.class);
1148 verify(groupMessageMapper).insert(captor.capture()); 960 verify(groupMessageMapper).insert(captor.capture());
1149 - assertEquals(ImGroupMessageReceiptStatusEnum.PENDING.getStatus(), captor.getValue().getReceiptStatus()); 961 + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), captor.getValue().getReceiptStatus());
1150 } 962 }
1151 963
1152 @Test 964 @Test
1153 public void testSendGroupMessage_dto_nonPersistentTypeNotInserted() { 965 public void testSendGroupMessage_dto_nonPersistentTypeNotInserted() {
1154 // 准备:persistent=false 类型(RECEIPT 回执)→ 不入库,仅推送 966 // 准备:persistent=false 类型(RECEIPT 回执)→ 不入库,仅推送
1155 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 967 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1156 - .setGroupId(10L).setType(ImMessageTypeEnum.RECEIPT.getType());  
1157 - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L, 2L)); 968 + .setGroupId(10L).setType(ImContentTypeEnum.RECEIPT.getType());
  969 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L));
1158 970
1159 groupMessageService.sendGroupMessage(1L, dto); 971 groupMessageService.sendGroupMessage(1L, dto);
1160 972
1161 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); 973 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class));
1162 - verify(imWebSocketService).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); 974 + verify(imWebSocketService).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any());
  975 + }
  976 +
  977 + @Test
  978 + public void testSendGroupMessage_dto_groupMemberNicknameUpdateNotInserted() {
  979 + // 准备:成员昵称变更只做在线同步,不入库
  980 + ImGroupMessageSendDTO dto = ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate(10L, 1L, "群昵称");
  981 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L));
  982 +
  983 + groupMessageService.sendGroupMessage(1L, dto);
  984 +
  985 + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class));
  986 + verify(imWebSocketService).sendNotificationAsync(argThat((Collection<Long> ids) ->
  987 + ids.size() == 2 && ids.contains(1L) && ids.contains(2L)),
  988 + anyInt(), eq(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()), any());
1163 } 989 }
1164 990
1165 @Test 991 @Test
1166 public void testSendGroupMessage_threeArg_explicitTargetsBypassActiveMembers() { 992 public void testSendGroupMessage_threeArg_explicitTargetsBypassActiveMembers() {
1167 // 准备:调用方传入显式 targets(解散场景成员已被批量 DISABLE,必须按移除前快照推送) 993 // 准备:调用方传入显式 targets(解散场景成员已被批量 DISABLE,必须按移除前快照推送)
1168 - Set<Long> targets = CollUtil.newHashSet(1L, 2L, 3L); 994 + Set<Long> targets = Set.of(1L, 2L, 3L);
1169 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() 995 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO()
1170 - .setGroupId(10L).setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()).setContent("{}"); 996 + .setGroupId(10L).setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent("{}");
1171 997
1172 groupMessageService.sendGroupMessage(1L, targets, dto); 998 groupMessageService.sendGroupMessage(1L, targets, dto);
1173 999
1174 // 断言:不读取 active members,按调用方 targets 推送 1000 // 断言:不读取 active members,按调用方 targets 推送
1175 verify(groupMemberService, never()).getActiveGroupMemberUserIdsByGroupId(anyLong()); 1001 verify(groupMemberService, never()).getActiveGroupMemberUserIdsByGroupId(anyLong());
1176 - verify(imWebSocketService).sendGroupMessageAsync(eq(targets), any(ImGroupMessageDTO.class)); 1002 + verify(imWebSocketService).sendNotificationAsync(eq(targets), anyInt(), anyInt(), any());
1177 } 1003 }
1178 1004
1179 // ========== getGroupMessageList ========== 1005 // ========== getGroupMessageList ==========
1180 1006
1181 @Test 1007 @Test
1182 - public void testGetGroupMessageList_delegatesToMapperWithJoinTime() {  
1183 - // 准备  
1184 - LocalDateTime joinTime = LocalDateTime.now().minusDays(5); 1008 + public void testGetGroupMessageList_delegatesToMapper() {
  1009 + // 准备:可见性由 SQL 按 receiver_user_ids 快照过滤,Service 直接透传 mapper 结果
1185 ImGroupMemberDO member = ImGroupMemberDO.builder() 1010 ImGroupMemberDO member = ImGroupMemberDO.builder()
1186 .id(50L).groupId(10L).userId(1L) 1011 .id(50L).groupId(10L).userId(1L)
1187 .status(CommonStatusEnum.ENABLE.getStatus()) 1012 .status(CommonStatusEnum.ENABLE.getStatus())
1188 - .joinTime(joinTime).build();  
1189 - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); 1013 + .joinTime(LocalDateTime.now().minusDays(5)).build();
  1014 + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member);
1190 1015
1191 ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); 1016 ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO();
1192 reqVO.setGroupId(10L); 1017 reqVO.setGroupId(10L);
1193 reqVO.setMaxId(100L); 1018 reqVO.setMaxId(100L);
1194 reqVO.setLimit(20); 1019 reqVO.setLimit(20);
1195 1020
1196 - List<ImGroupMessageDO> mockList = ListUtil.of( 1021 + List<ImGroupMessageDO> mockList = List.of(
1197 ImGroupMessageDO.builder().id(99L).groupId(10L).senderId(2L) 1022 ImGroupMessageDO.builder().id(99L).groupId(10L).senderId(2L)
1198 - .sendTime(joinTime.plusMinutes(1)).build() 1023 + .receiverUserIds(List.of(1L)).sendTime(LocalDateTime.now()).build()
1199 ); 1024 );
1200 - when(groupMessageMapper.selectHistoryList(10L, 100L, 20, joinTime)).thenReturn(mockList); 1025 + when(groupMessageMapper.selectHistoryListByUser(1L, 10L, 100L, 20)).thenReturn(mockList);
1201 1026
1202 // 调用 1027 // 调用
1203 List<ImGroupMessageDO> result = groupMessageService.getGroupMessageList(1L, reqVO); 1028 List<ImGroupMessageDO> result = groupMessageService.getGroupMessageList(1L, reqVO);
1204 1029
1205 - // 断言:使用成员的 joinTime 作为历史消息的下限 1030 + // 断言:透传到 mapper,参数一致
1206 assertEquals(1, result.size()); 1031 assertEquals(1, result.size());
1207 - verify(groupMessageMapper).selectHistoryList(10L, 100L, 20, joinTime); 1032 + assertEquals(99L, result.get(0).getId());
  1033 + verify(groupMessageMapper).selectHistoryListByUser(1L, 10L, 100L, 20);
1208 } 1034 }
1209 1035
1210 @Test 1036 @Test
1211 public void testGetGroupMessageList_notInGroup() { 1037 public void testGetGroupMessageList_notInGroup() {
1212 - // 准备:用户不在群中 1038 + // 准备:用户从未在群中(getGroupMember 返回 null)
1213 ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); 1039 ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO();
1214 reqVO.setGroupId(10L); 1040 reqVO.setGroupId(10L);
1215 reqVO.setLimit(20); 1041 reqVO.setLimit(20);
1216 - when(groupMemberService.validateMemberInGroup(10L, 1L))  
1217 - .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(),  
1218 - GROUP_MEMBER_NOT_IN_GROUP.getMsg())); 1042 + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(null);
1219 1043
1220 // 调用并断言:鉴权失败 1044 // 调用并断言:鉴权失败
1221 ServiceException exception = assertThrows(ServiceException.class, 1045 ServiceException exception = assertThrows(ServiceException.class,
1222 () -> groupMessageService.getGroupMessageList(1L, reqVO)); 1046 () -> groupMessageService.getGroupMessageList(1L, reqVO));
1223 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); 1047 assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode());
1224 // 断言:不查询 DB 1048 // 断言:不查询 DB
1225 - verify(groupMessageMapper, never()).selectHistoryList(anyLong(), anyLong(), anyInt(), any());  
1226 - }  
1227 -  
1228 - // ========== delete read cursor 委托 ==========  
1229 -  
1230 - @Test  
1231 - public void testDeleteReadMaxMessageId_delegatesToRedis() {  
1232 - groupMessageService.deleteReadMaxMessageId(10L, 1L);  
1233 -  
1234 - verify(groupMessageReadRedisDAO).deleteReadMaxMessageId(10L, 1L);  
1235 - }  
1236 -  
1237 - @Test  
1238 - public void testDeleteReadMaxMessageIds_delegatesToRedis() {  
1239 - groupMessageService.deleteReadMaxMessageIds(10L, ListUtil.of(1L, 2L));  
1240 -  
1241 - verify(groupMessageReadRedisDAO).deleteReadMaxMessageIds(10L, ListUtil.of(1L, 2L));  
1242 - }  
1243 -  
1244 - @Test  
1245 - public void testDeleteReadMaxMessageIdMap_delegatesToRedis() {  
1246 - groupMessageService.deleteReadMaxMessageIdMap(10L);  
1247 -  
1248 - verify(groupMessageReadRedisDAO).deleteReadMaxMessageIdMap(10L); 1049 + verify(groupMessageMapper, never()).selectHistoryListByUser(anyLong(), anyLong(), anyLong(), anyInt());
1249 } 1050 }
1250 1051
1251 } 1052 }
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
@@ -7,15 +7,20 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate @@ -7,15 +7,20 @@ import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivate
7 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; 7 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; 8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
9 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; 9 import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper;
  10 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  11 +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum;
10 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; 12 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
11 -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; 13 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
12 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 14 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
  15 +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService;
13 import cn.iocoder.yudao.module.im.service.friend.ImFriendService; 16 import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
14 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; 17 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
15 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; 18 import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO;
16 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 19 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
17 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
18 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; 20 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification;
  21 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification;
  22 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification;
  23 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage;
19 import org.junit.jupiter.api.Test; 24 import org.junit.jupiter.api.Test;
20 import org.mockito.ArgumentCaptor; 25 import org.mockito.ArgumentCaptor;
21 import org.mockito.InjectMocks; 26 import org.mockito.InjectMocks;
@@ -31,7 +36,7 @@ import static org.mockito.ArgumentMatchers.*; @@ -31,7 +36,7 @@ import static org.mockito.ArgumentMatchers.*;
31 import static org.mockito.Mockito.*; 36 import static org.mockito.Mockito.*;
32 37
33 /** 38 /**
34 - * IM 私聊消息 Service 单元测试 39 + * {@link ImPrivateMessageServiceImpl} 的单元测试
35 * 40 *
36 * @author 芋道源码 41 * @author 芋道源码
37 */ 42 */
@@ -47,9 +52,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -47,9 +52,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
47 @Mock 52 @Mock
48 private ImSensitiveWordService sensitiveWordService; 53 private ImSensitiveWordService sensitiveWordService;
49 @Mock 54 @Mock
  55 + private ImConversationReadService conversationReadService;
  56 + @Mock
50 private ImWebSocketService imWebSocketService; 57 private ImWebSocketService imWebSocketService;
51 58
52 - /** 用真实实例避免 NPE;默认值与生产保持一致(recallTimeoutMinutes=5、private/group read enabled=true);个别用例可改字段测分支 */  
53 @Spy 59 @Spy
54 private ImProperties imProperties = new ImProperties(); 60 private ImProperties imProperties = new ImProperties();
55 61
@@ -57,7 +63,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -57,7 +63,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
57 ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO(); 63 ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO();
58 reqVO.setClientMessageId("test-uuid-001"); 64 reqVO.setClientMessageId("test-uuid-001");
59 reqVO.setReceiverId(2L); 65 reqVO.setReceiverId(2L);
60 - reqVO.setType(ImMessageTypeEnum.TEXT.getType()); 66 + reqVO.setType(ImContentTypeEnum.TEXT.getType());
61 reqVO.setContent("{\"content\":\"你好\"}"); 67 reqVO.setContent("{\"content\":\"你好\"}");
62 return reqVO; 68 return reqVO;
63 } 69 }
@@ -83,8 +89,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -83,8 +89,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
83 assertNotNull(result); 89 assertNotNull(result);
84 assertEquals(1L, result.getSenderId()); 90 assertEquals(1L, result.getSenderId());
85 assertEquals(2L, result.getReceiverId()); 91 assertEquals(2L, result.getReceiverId());
86 - assertEquals(ImMessageTypeEnum.TEXT.getType(), result.getType());  
87 - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.getStatus()); 92 + assertEquals(ImContentTypeEnum.TEXT.getType(), result.getType());
  93 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus());
  94 + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus(),
  95 + "用户私聊消息默认需要回执(PENDING)");
88 assertNotNull(result.getSendTime()); 96 assertNotNull(result.getSendTime());
89 97
90 // 验证调用 98 // 验证调用
@@ -92,8 +100,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -92,8 +100,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
92 verify(sensitiveWordService).validateText(reqVO.getContent()); 100 verify(sensitiveWordService).validateText(reqVO.getContent());
93 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); 101 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class));
94 // 验证推送给接收方和发送方 102 // 验证推送给接收方和发送方
95 - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class));  
96 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); 103 + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any());
  104 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
97 } 105 }
98 106
99 @Test 107 @Test
@@ -162,38 +170,44 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -162,38 +170,44 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
162 170
163 @Test 171 @Test
164 public void testReadMessages_success() { 172 public void testReadMessages_success() {
165 - // 准备:前端上报已读到 messageId=5;mapper 返回更新行数 2 表示有未读被翻转  
166 - when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus( 173 + // 准备:前端上报已读到 messageId=5;mapper 返回更新行数 2 表示有待回执消息被标记完成
  174 + when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(
167 eq(2L), eq(1L), eq(5L), 175 eq(2L), eq(1L), eq(5L),
168 - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class))) 176 + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class)))
169 .thenReturn(2); 177 .thenReturn(2);
  178 + // 读位置前进 → 才下发事件
  179 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()))
  180 + .thenReturn(true);
170 181
171 // 调用 182 // 调用
172 privateMessageService.readPrivateMessages(1L, 2L, 5L); 183 privateMessageService.readPrivateMessages(1L, 2L, 5L);
173 184
174 - // 断言:更新了消息状态  
175 - verify(privateMessageMapper).updateBySenderIdAndReceiverIdAndIdLeAndStatus( 185 + // 断言:把待回执(PENDING)消息标记为已完成(DONE);status 不再写 READ
  186 + verify(privateMessageMapper).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(
176 eq(2L), eq(1L), eq(5L), 187 eq(2L), eq(1L), eq(5L),
177 - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class)); 188 + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class));
178 189
179 // 断言:发送了 READ + RECEIPT 事件,payload 字段正确 190 // 断言:发送了 READ + RECEIPT 事件,payload 字段正确
180 ArgumentCaptor<Long> userCaptor = ArgumentCaptor.forClass(Long.class); 191 ArgumentCaptor<Long> userCaptor = ArgumentCaptor.forClass(Long.class);
181 - ArgumentCaptor<ImPrivateMessageDTO> contentCaptor = ArgumentCaptor.forClass(ImPrivateMessageDTO.class);  
182 - verify(imWebSocketService, times(2)).sendPrivateMessageAsync(  
183 - userCaptor.capture(), contentCaptor.capture()); 192 + ArgumentCaptor<Integer> contentTypeCaptor = ArgumentCaptor.forClass(Integer.class);
  193 + ArgumentCaptor<Object> payloadCaptor = ArgumentCaptor.forClass(Object.class);
  194 + verify(imWebSocketService, times(2)).sendNotificationAsync(
  195 + userCaptor.capture(), eq(ImConversationTypeEnum.PRIVATE.getType()),
  196 + contentTypeCaptor.capture(), payloadCaptor.capture());
184 197
185 // 第一次:发给自己的 READ 事件 198 // 第一次:发给自己的 READ 事件
186 assertEquals(1L, userCaptor.getAllValues().get(0)); 199 assertEquals(1L, userCaptor.getAllValues().get(0));
187 - ImPrivateMessageDTO readPayload = contentCaptor.getAllValues().get(0);  
188 - assertEquals(ImMessageTypeEnum.READ.getType(), readPayload.getType()); 200 + assertEquals(ImContentTypeEnum.READ.getType(), contentTypeCaptor.getAllValues().get(0));
  201 + ImMessageReadNotification readPayload = (ImMessageReadNotification) payloadCaptor.getAllValues().get(0);
189 assertEquals(1L, readPayload.getSenderId()); 202 assertEquals(1L, readPayload.getSenderId());
190 assertEquals(2L, readPayload.getReceiverId()); 203 assertEquals(2L, readPayload.getReceiverId());
191 assertEquals(5L, readPayload.getId(), "READ id 应为前端上报的 messageId"); 204 assertEquals(5L, readPayload.getId(), "READ id 应为前端上报的 messageId");
192 205
193 // 第二次:发给对方的 RECEIPT 事件 206 // 第二次:发给对方的 RECEIPT 事件
194 assertEquals(2L, userCaptor.getAllValues().get(1)); 207 assertEquals(2L, userCaptor.getAllValues().get(1));
195 - ImPrivateMessageDTO receiptPayload = contentCaptor.getAllValues().get(1);  
196 - assertEquals(ImMessageTypeEnum.RECEIPT.getType(), receiptPayload.getType()); 208 + assertEquals(ImContentTypeEnum.RECEIPT.getType(), contentTypeCaptor.getAllValues().get(1));
  209 + ImMessageReceiptNotification receiptPayload =
  210 + (ImMessageReceiptNotification) payloadCaptor.getAllValues().get(1);
197 assertEquals(5L, receiptPayload.getId(), "RECEIPT id 应为前端上报的 messageId"); 211 assertEquals(5L, receiptPayload.getId(), "RECEIPT id 应为前端上报的 messageId");
198 } 212 }
199 213
@@ -204,7 +218,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -204,7 +218,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
204 // 准备 218 // 准备
205 ImPrivateMessageDO message = ImPrivateMessageDO.builder() 219 ImPrivateMessageDO message = ImPrivateMessageDO.builder()
206 .id(10L).senderId(1L).receiverId(2L) 220 .id(10L).senderId(1L).receiverId(2L)
207 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 221 + .status(ImMessageStatusEnum.NORMAL.getStatus())
208 .sendTime(LocalDateTime.now()).build(); // 刚发送,5 分钟内 222 .sendTime(LocalDateTime.now()).build(); // 刚发送,5 分钟内
209 when(privateMessageMapper.selectById(10L)).thenReturn(message); 223 when(privateMessageMapper.selectById(10L)).thenReturn(message);
210 when(privateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); 224 when(privateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1);
@@ -219,7 +233,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -219,7 +233,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
219 verify(privateMessageMapper).updateById(any(ImPrivateMessageDO.class)); 233 verify(privateMessageMapper).updateById(any(ImPrivateMessageDO.class));
220 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); 234 verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class));
221 // 验证推送了消息(给接收方和发送方) 235 // 验证推送了消息(给接收方和发送方)
222 - verify(imWebSocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 236 + verify(imWebSocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
223 } 237 }
224 238
225 @Test 239 @Test
@@ -227,7 +241,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -227,7 +241,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
227 // 准备 241 // 准备
228 ImPrivateMessageDO message = ImPrivateMessageDO.builder() 242 ImPrivateMessageDO message = ImPrivateMessageDO.builder()
229 .id(10L).senderId(2L).receiverId(1L) 243 .id(10L).senderId(2L).receiverId(1L)
230 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 244 + .status(ImMessageStatusEnum.NORMAL.getStatus())
231 .sendTime(LocalDateTime.now()).build(); 245 .sendTime(LocalDateTime.now()).build();
232 when(privateMessageMapper.selectById(10L)).thenReturn(message); 246 when(privateMessageMapper.selectById(10L)).thenReturn(message);
233 247
@@ -268,7 +282,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -268,7 +282,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
268 // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) 282 // 准备:消息发送于 10 分钟前(超过 5 分钟窗口)
269 ImPrivateMessageDO message = ImPrivateMessageDO.builder() 283 ImPrivateMessageDO message = ImPrivateMessageDO.builder()
270 .id(10L).senderId(1L).receiverId(2L) 284 .id(10L).senderId(1L).receiverId(2L)
271 - .status(ImMessageStatusEnum.UNREAD.getStatus()) 285 + .status(ImMessageStatusEnum.NORMAL.getStatus())
272 .sendTime(LocalDateTime.now().minusMinutes(10)).build(); 286 .sendTime(LocalDateTime.now().minusMinutes(10)).build();
273 when(privateMessageMapper.selectById(10L)).thenReturn(message); 287 when(privateMessageMapper.selectById(10L)).thenReturn(message);
274 288
@@ -295,7 +309,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -295,7 +309,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
295 assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); 309 assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode());
296 // 断言:不入库、不推送 310 // 断言:不入库、不推送
297 verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); 311 verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class));
298 - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 312 + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
299 } 313 }
300 314
301 @Test 315 @Test
@@ -308,33 +322,31 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -308,33 +322,31 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
308 () -> privateMessageService.readPrivateMessages(1L, 2L, 5L)); 322 () -> privateMessageService.readPrivateMessages(1L, 2L, 5L));
309 assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); 323 assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode());
310 // 断言:不更新消息状态、不推送 324 // 断言:不更新消息状态、不推送
311 - verify(privateMessageMapper, never()).updateBySenderIdAndReceiverIdAndIdLeAndStatus( 325 + verify(privateMessageMapper, never()).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(
312 anyLong(), anyLong(), anyLong(), anyInt(), any(ImPrivateMessageDO.class)); 326 anyLong(), anyLong(), anyLong(), anyInt(), any(ImPrivateMessageDO.class));
313 - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 327 + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
314 } 328 }
315 329
316 @Test 330 @Test
317 - public void testReadMessages_noUnread() {  
318 - // 准备:mapper 返回 0 表示 id <= messageId 范围内没有未读消息  
319 - when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus(  
320 - eq(2L), eq(1L), eq(5L),  
321 - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class)))  
322 - .thenReturn(0); 331 + public void testReadMessages_notAdvanced() {
  332 + // 准备:读位置未前进(已读过 / CAS 失败),不下发事件
  333 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()))
  334 + .thenReturn(false);
323 335
324 // 调用 336 // 调用
325 privateMessageService.readPrivateMessages(1L, 2L, 5L); 337 privateMessageService.readPrivateMessages(1L, 2L, 5L);
326 338
327 - // 断言:不推送 READ / RECEIPT  
328 - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); 339 + // 断言:读位置没前进,不推送 READ / RECEIPT
  340 + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any());
329 } 341 }
330 342
331 // ========== getMaxReadMessageId 测试 ========== 343 // ========== getMaxReadMessageId 测试 ==========
332 344
333 @Test 345 @Test
334 public void testGetMaxReadMessageId_hit() { 346 public void testGetMaxReadMessageId_hit() {
335 - // 准备:对方读到我发的最大 id=10  
336 - when(privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus(  
337 - 1L, 2L, ImMessageStatusEnum.READ.getStatus())).thenReturn(10L); 347 + // 准备:对方(2) 在与我(1) 的会话里读位置=10
  348 + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L)))
  349 + .thenReturn(10L);
338 350
339 // 调用 351 // 调用
340 Long result = privateMessageService.getMaxReadMessageId(1L, 2L); 352 Long result = privateMessageService.getMaxReadMessageId(1L, 2L);
@@ -345,9 +357,9 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -345,9 +357,9 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
345 357
346 @Test 358 @Test
347 public void testGetMaxReadMessageId_miss() { 359 public void testGetMaxReadMessageId_miss() {
348 - // 准备:对方一条都没读过  
349 - when(privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus(  
350 - 1L, 2L, ImMessageStatusEnum.READ.getStatus())).thenReturn(null); 360 + // 准备:对方一条都没读过(读位置为 null)
  361 + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L)))
  362 + .thenReturn(null);
351 363
352 // 调用 364 // 调用
353 Long result = privateMessageService.getMaxReadMessageId(1L, 2L); 365 Long result = privateMessageService.getMaxReadMessageId(1L, 2L);
@@ -367,13 +379,13 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -367,13 +379,13 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
367 assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); 379 assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode());
368 } 380 }
369 381
370 - // ========== sendPrivateMessage(senderId, dto):helper 行为 ========== 382 + // ========== DTO 私聊消息 ==========
371 383
372 @Test 384 @Test
373 public void testSendPrivateMessage_dto_persistsAndSerializesPojoContent() { 385 public void testSendPrivateMessage_dto_persistsAndSerializesPojoContent() {
374 // 准备:persistent=true 类型 + POJO content 386 // 准备:persistent=true 类型 + POJO content
375 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() 387 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO()
376 - .setReceiverId(2L).setType(ImMessageTypeEnum.RECALL.getType()) 388 + .setReceiverId(2L).setType(ImContentTypeEnum.RECALL.getType())
377 .setContent(new RecallMessage().setMessageId(50L)); 389 .setContent(new RecallMessage().setMessageId(50L));
378 390
379 privateMessageService.sendPrivateMessage(1L, dto); 391 privateMessageService.sendPrivateMessage(1L, dto);
@@ -384,27 +396,27 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @@ -384,27 +396,27 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest {
384 ImPrivateMessageDO message = captor.getValue(); 396 ImPrivateMessageDO message = captor.getValue();
385 assertEquals(1L, message.getSenderId()); 397 assertEquals(1L, message.getSenderId());
386 assertEquals(2L, message.getReceiverId()); 398 assertEquals(2L, message.getReceiverId());
387 - assertEquals(ImMessageTypeEnum.RECALL.getType(), message.getType()); 399 + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType());
388 assertEquals("{\"messageId\":50}", message.getContent()); 400 assertEquals("{\"messageId\":50}", message.getContent());
389 - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), message.getStatus()); 401 + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus());
390 assertNotNull(message.getClientMessageId()); 402 assertNotNull(message.getClientMessageId());
391 assertNotNull(message.getSendTime()); 403 assertNotNull(message.getSendTime());
392 // 断言:sender + receiver 双端推送 404 // 断言:sender + receiver 双端推送
393 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class));  
394 - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); 405 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
  406 + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any());
395 } 407 }
396 408
397 @Test 409 @Test
398 public void testSendPrivateMessage_dto_nonPersistentTypeNotInserted() { 410 public void testSendPrivateMessage_dto_nonPersistentTypeNotInserted() {
399 // 准备:persistent=false 类型(FRIEND_DELETE 通知)→ 不入库;仅推 sender 多端,receiver 不感知 411 // 准备:persistent=false 类型(FRIEND_DELETE 通知)→ 不入库;仅推 sender 多端,receiver 不感知
400 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() 412 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO()
401 - .setReceiverId(2L).setType(ImMessageTypeEnum.FRIEND_DELETE.getType()); 413 + .setReceiverId(2L).setType(ImContentTypeEnum.FRIEND_DELETE.getType());
402 414
403 privateMessageService.sendPrivateMessage(1L, dto); 415 privateMessageService.sendPrivateMessage(1L, dto);
404 416
405 verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); 417 verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class));
406 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class));  
407 - verify(imWebSocketService, never()).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); 418 + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any());
  419 + verify(imWebSocketService, never()).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any());
408 } 420 }
409 421
410 // ========== getPrivateMessageList ========== 422 // ========== getPrivateMessageList ==========
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java
@@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; @@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum;
20 import cn.iocoder.yudao.module.im.framework.config.ImProperties; 20 import cn.iocoder.yudao.module.im.framework.config.ImProperties;
21 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; 21 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
22 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; 22 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
23 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;  
24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 23 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 24 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
26 import org.junit.jupiter.api.Test; 25 import org.junit.jupiter.api.Test;
@@ -44,6 +43,11 @@ import static org.junit.jupiter.api.Assertions.*; @@ -44,6 +43,11 @@ import static org.junit.jupiter.api.Assertions.*;
44 import static org.mockito.ArgumentMatchers.*; 43 import static org.mockito.ArgumentMatchers.*;
45 import static org.mockito.Mockito.*; 44 import static org.mockito.Mockito.*;
46 45
  46 +/**
  47 + * {@link ImRtcCallServiceImpl} 的单元测试
  48 + *
  49 + * @author 芋道源码
  50 + */
47 public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { 51 public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest {
48 52
49 @InjectMocks 53 @InjectMocks
@@ -64,7 +68,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @@ -64,7 +68,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest {
64 @Mock 68 @Mock
65 private ImGroupMemberService groupMemberService; 69 private ImGroupMemberService groupMemberService;
66 70
67 - // ========== timeoutInvitingParticipants(Job 入口)========== 71 + // ========== timeoutInvitingParticipants ==========
68 72
69 @Test 73 @Test
70 public void testTimeoutInvitingParticipants_emptyCandidates_returnsZeroAndNoDownstream() { 74 public void testTimeoutInvitingParticipants_emptyCandidates_returnsZeroAndNoDownstream() {
@@ -144,7 +148,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @@ -144,7 +148,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest {
144 148
145 // 断言:成功 1 个;NO_ANSWER 信令推到主叫;不触发 endSession 149 // 断言:成功 1 个;NO_ANSWER 信令推到主叫;不触发 endSession
146 assertEquals(1, result); 150 assertEquals(1, result);
147 - verify(webSocketService).sendPrivateMessageAsync(eq(200L), any(ImPrivateMessageDTO.class)); 151 + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any());
148 verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); 152 verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any());
149 } 153 }
150 154
@@ -167,7 +171,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @@ -167,7 +171,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest {
167 verifyNoInteractions(webSocketService); 171 verifyNoInteractions(webSocketService);
168 } 172 }
169 173
170 - // ========== noAnswerCallCheck(前端 timer 入口)========== 174 + // ========== noAnswerCallCheck ==========
171 175
172 @Test 176 @Test
173 public void testNoAnswerCallCheck_authFails_silentNoOp() { 177 public void testNoAnswerCallCheck_authFails_silentNoOp() {
@@ -239,7 +243,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @@ -239,7 +243,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest {
239 rtcCallService.noAnswerCallCheck(100L, "r1"); 243 rtcCallService.noAnswerCallCheck(100L, "r1");
240 244
241 // 断言:NO_ANSWER 信令推到主叫 200L;不触发 endSession 245 // 断言:NO_ANSWER 信令推到主叫 200L;不触发 endSession
242 - verify(webSocketService).sendPrivateMessageAsync(eq(200L), any(ImPrivateMessageDTO.class)); 246 + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any());
243 verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); 247 verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any());
244 } 248 }
245 249
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java
@@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.*; @@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
24 import static org.mockito.Mockito.*; 24 import static org.mockito.Mockito.*;
25 25
26 /** 26 /**
27 - * IM 敏感词 Service 单元测试 27 + * {@link ImSensitiveWordServiceImpl} 的单元测试
28 * 28 *
29 * @author 芋道源码 29 * @author 芋道源码
30 */ 30 */
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java
@@ -19,7 +19,7 @@ import static org.mockito.ArgumentMatchers.eq; @@ -19,7 +19,7 @@ import static org.mockito.ArgumentMatchers.eq;
19 import static org.mockito.Mockito.when; 19 import static org.mockito.Mockito.when;
20 20
21 /** 21 /**
22 - * IM 数据看板 Service 单元测试 22 + * {@link ImStatisticsManagerServiceImpl} 的单元测试
23 * 23 *
24 * @author 芋道源码 24 * @author 芋道源码
25 */ 25 */
@@ -72,7 +72,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -72,7 +72,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
72 assertEquals(456L, service.getGroupMessageCount(BEGIN, END)); 72 assertEquals(456L, service.getGroupMessageCount(BEGIN, END));
73 } 73 }
74 74
75 - // ========== 每日序列 toDailyCountMap ========== 75 + // ========== 每日序列 ==========
76 76
77 @Test 77 @Test
78 public void testGetNewUserDailyCountMap_dateConvert() { 78 public void testGetNewUserDailyCountMap_dateConvert() {
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java
@@ -4,8 +4,11 @@ import cn.hutool.core.collection.ListUtil; @@ -4,8 +4,11 @@ import cn.hutool.core.collection.ListUtil;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 6 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
7 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;  
8 -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; 7 +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum;
  8 +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum;
  9 +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO;
  10 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification;
  11 +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification;
9 import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; 12 import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
10 import org.junit.jupiter.api.AfterEach; 13 import org.junit.jupiter.api.AfterEach;
11 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.Test;
@@ -21,10 +24,11 @@ import java.util.List; @@ -21,10 +24,11 @@ import java.util.List;
21 24
22 import static org.junit.jupiter.api.Assertions.assertEquals; 25 import static org.junit.jupiter.api.Assertions.assertEquals;
23 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.ArgumentMatchers.eq;
  27 +import static org.mockito.ArgumentMatchers.argThat;
24 import static org.mockito.Mockito.*; 28 import static org.mockito.Mockito.*;
25 29
26 /** 30 /**
27 - * IM WebSocket 推送 Service 单元测试 31 + * {@link ImWebSocketServiceImpl} 的单元测试
28 * 32 *
29 * @author 芋道源码 33 * @author 芋道源码
30 */ 34 */
@@ -44,28 +48,32 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { @@ -44,28 +48,32 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest {
44 } 48 }
45 } 49 }
46 50
47 - // ========== sendPrivateMessageAsync ========== 51 + // ========== 私聊推送 ==========
48 52
49 @Test 53 @Test
50 - public void testSendPrivateMessageAsync_noTransactionSendsImmediately() { 54 + public void testSendNotificationAsync_private_noTransactionSendsImmediately() {
51 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 55 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
52 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 56 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
53 .thenReturn(imWebSocketService); 57 .thenReturn(imWebSocketService);
54 58
55 // 准备 59 // 准备
56 - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); 60 + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L)
  61 + .setType(ImContentTypeEnum.TEXT.getType());
57 62
58 // 调用:无事务,应立即发送 63 // 调用:无事务,应立即发送
59 - imWebSocketService.sendPrivateMessageAsync(2L, dto); 64 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(),
  65 + ImContentTypeEnum.TEXT.getType(), dto);
60 66
61 // 断言 67 // 断言
62 verify(webSocketSenderApi).sendObject( 68 verify(webSocketSenderApi).sendObject(
63 - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImPrivateMessageDTO.TYPE), eq(dto)); 69 + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE),
  70 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE,
  71 + ImContentTypeEnum.TEXT, dto)));
64 } 72 }
65 } 73 }
66 74
67 @Test 75 @Test
68 - public void testSendPrivateMessageAsync_inTransactionDeferredUntilCommit() { 76 + public void testSendNotificationAsync_private_inTransactionDeferredUntilCommit() {
69 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 77 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
70 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 78 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
71 .thenReturn(imWebSocketService); 79 .thenReturn(imWebSocketService);
@@ -73,10 +81,12 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { @@ -73,10 +81,12 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest {
73 // 准备:开启事务同步 81 // 准备:开启事务同步
74 TransactionSynchronizationManager.initSynchronization(); 82 TransactionSynchronizationManager.initSynchronization();
75 try { 83 try {
76 - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); 84 + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L)
  85 + .setType(ImContentTypeEnum.TEXT.getType());
77 86
78 // 调用 87 // 调用
79 - imWebSocketService.sendPrivateMessageAsync(2L, dto); 88 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(),
  89 + ImContentTypeEnum.TEXT.getType(), dto);
80 90
81 // 断言:事务未提交,未推送 91 // 断言:事务未提交,未推送
82 verify(webSocketSenderApi, never()).sendObject(anyInt(), anyLong(), anyString(), any()); 92 verify(webSocketSenderApi, never()).sendObject(anyInt(), anyLong(), anyString(), any());
@@ -89,49 +99,60 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { @@ -89,49 +99,60 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest {
89 99
90 // 断言:提交后推送 100 // 断言:提交后推送
91 verify(webSocketSenderApi).sendObject( 101 verify(webSocketSenderApi).sendObject(
92 - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImPrivateMessageDTO.TYPE), eq(dto)); 102 + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE),
  103 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE,
  104 + ImContentTypeEnum.TEXT, dto)));
93 } finally { 105 } finally {
94 TransactionSynchronizationManager.clear(); 106 TransactionSynchronizationManager.clear();
95 } 107 }
96 } 108 }
97 } 109 }
98 110
99 - // ========== sendGroupMessageAsync ========== 111 + // ========== 群聊推送 ==========
100 112
101 @Test 113 @Test
102 - public void testSendGroupMessageAsync_fanOutToAllUsers() { 114 + public void testSendNotificationAsync_group_fanOutToAllUsers() {
103 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 115 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
104 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 116 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
105 .thenReturn(imWebSocketService); 117 .thenReturn(imWebSocketService);
106 118
107 - ImGroupMessageDTO dto = new ImGroupMessageDTO(); 119 + ImGroupMessageNotification dto = new ImGroupMessageNotification();
108 dto.setGroupId(10L); 120 dto.setGroupId(10L);
109 dto.setSenderId(1L); 121 dto.setSenderId(1L);
  122 + dto.setType(ImContentTypeEnum.TEXT.getType());
110 123
111 - imWebSocketService.sendGroupMessageAsync(ListUtil.of(1L, 2L, 3L), dto); 124 + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L),
  125 + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto);
112 126
113 verify(webSocketSenderApi).sendObject( 127 verify(webSocketSenderApi).sendObject(
114 - eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 128 + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE),
  129 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  130 + ImContentTypeEnum.TEXT, dto)));
115 verify(webSocketSenderApi).sendObject( 131 verify(webSocketSenderApi).sendObject(
116 - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 132 + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE),
  133 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  134 + ImContentTypeEnum.TEXT, dto)));
117 verify(webSocketSenderApi).sendObject( 135 verify(webSocketSenderApi).sendObject(
118 - eq(UserTypeEnum.ADMIN.getValue()), eq(3L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 136 + eq(UserTypeEnum.ADMIN.getValue()), eq(3L), eq(ImNotificationWebSocketDTO.TYPE),
  137 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  138 + ImContentTypeEnum.TEXT, dto)));
119 } 139 }
120 } 140 }
121 141
122 @Test 142 @Test
123 - public void testSendGroupMessageAsync_senderExceptionDoesNotBreakOthers() { 143 + public void testSendNotificationAsync_group_senderExceptionDoesNotBreakOthers() {
124 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 144 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
125 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 145 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
126 .thenReturn(imWebSocketService); 146 .thenReturn(imWebSocketService);
127 147
128 - ImGroupMessageDTO dto = new ImGroupMessageDTO(); 148 + ImGroupMessageNotification dto = new ImGroupMessageNotification();
129 dto.setGroupId(10L); 149 dto.setGroupId(10L);
130 // 给 1 号用户推送时抛异常,不能影响 2/3 号 150 // 给 1 号用户推送时抛异常,不能影响 2/3 号
131 doThrow(new RuntimeException("user offline")) 151 doThrow(new RuntimeException("user offline"))
132 .when(webSocketSenderApi).sendObject(anyInt(), eq(1L), anyString(), any()); 152 .when(webSocketSenderApi).sendObject(anyInt(), eq(1L), anyString(), any());
133 153
134 - imWebSocketService.sendGroupMessageAsync(ListUtil.of(1L, 2L, 3L), dto); 154 + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L),
  155 + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto);
135 156
136 // 2L 和 3L 也都被推送 157 // 2L 和 3L 也都被推送
137 verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); 158 verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any());
@@ -140,60 +161,96 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { @@ -140,60 +161,96 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest {
140 } 161 }
141 162
142 @Test 163 @Test
143 - public void testDoSendGroupMessage_emptyUserIds_noSend() {  
144 - ImGroupMessageDTO dto = new ImGroupMessageDTO().setGroupId(10L); 164 + public void testDoSendNotification_emptyUserIds_noSend() {
  165 + ImGroupMessageNotification dto = new ImGroupMessageNotification();
  166 + dto.setGroupId(10L);
  167 + dto.setType(ImContentTypeEnum.TEXT.getType());
  168 + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP,
  169 + ImContentTypeEnum.TEXT, dto);
145 170
146 - imWebSocketService.doSendGroupMessage(Collections.emptyList(), dto);  
147 - imWebSocketService.doSendGroupMessage(null, dto); 171 + imWebSocketService.doSendNotification(Collections.emptyList(), notification);
  172 + imWebSocketService.doSendNotification(null, notification);
148 173
149 verifyNoInteractions(webSocketSenderApi); 174 verifyNoInteractions(webSocketSenderApi);
150 } 175 }
151 176
152 @Test 177 @Test
153 - public void testDoSendGroupMessage_distinctUserIds() {  
154 - ImGroupMessageDTO dto = new ImGroupMessageDTO().setGroupId(10L); 178 + public void testDoSendNotification_distinctUserIds() {
  179 + ImGroupMessageNotification dto = new ImGroupMessageNotification();
  180 + dto.setGroupId(10L);
  181 + dto.setType(ImContentTypeEnum.TEXT.getType());
  182 + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP,
  183 + ImContentTypeEnum.TEXT, dto);
155 184
156 - imWebSocketService.doSendGroupMessage(Arrays.asList(1L, 2L, 1L, null), dto); 185 + imWebSocketService.doSendNotification(Arrays.asList(1L, 2L, 1L, null), notification);
157 186
158 verify(webSocketSenderApi).sendObject( 187 verify(webSocketSenderApi).sendObject(
159 - eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 188 + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE),
  189 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  190 + ImContentTypeEnum.TEXT, dto)));
160 verify(webSocketSenderApi).sendObject( 191 verify(webSocketSenderApi).sendObject(
161 - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 192 + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE),
  193 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  194 + ImContentTypeEnum.TEXT, dto)));
162 verifyNoMoreInteractions(webSocketSenderApi); 195 verifyNoMoreInteractions(webSocketSenderApi);
163 } 196 }
164 197
165 @Test 198 @Test
166 - public void testSendPrivateMessageAsync_exceptionSwallowed() { 199 + public void testSendNotificationAsync_private_exceptionSwallowed() {
167 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 200 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
168 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 201 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
169 .thenReturn(imWebSocketService); 202 .thenReturn(imWebSocketService);
170 203
171 // 准备:sender 抛异常 204 // 准备:sender 抛异常
172 - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); 205 + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L)
  206 + .setType(ImContentTypeEnum.TEXT.getType());
173 doThrow(new RuntimeException("user offline")) 207 doThrow(new RuntimeException("user offline"))
174 .when(webSocketSenderApi).sendObject(anyInt(), anyLong(), anyString(), any()); 208 .when(webSocketSenderApi).sendObject(anyInt(), anyLong(), anyString(), any());
175 209
176 // 调用:异常应被吞掉,不向上抛 210 // 调用:异常应被吞掉,不向上抛
177 - imWebSocketService.sendPrivateMessageAsync(2L, dto); 211 + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(),
  212 + ImContentTypeEnum.TEXT.getType(), dto);
178 213
179 verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); 214 verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any());
180 } 215 }
181 } 216 }
182 217
183 @Test 218 @Test
184 - public void testSendGroupMessageAsync_singleUserDefaultOverload() { 219 + public void testSendNotificationAsync_group_singleUserDefaultOverload() {
185 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { 220 try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
186 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) 221 springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class)))
187 .thenReturn(imWebSocketService); 222 .thenReturn(imWebSocketService);
188 223
189 - ImGroupMessageDTO dto = new ImGroupMessageDTO(); 224 + ImGroupMessageNotification dto = new ImGroupMessageNotification();
190 dto.setGroupId(10L); 225 dto.setGroupId(10L);
  226 + dto.setType(ImContentTypeEnum.TEXT.getType());
191 227
192 - imWebSocketService.sendGroupMessageAsync(42L, dto); 228 + imWebSocketService.sendNotificationAsync(42L, ImConversationTypeEnum.GROUP.getType(),
  229 + ImContentTypeEnum.TEXT.getType(), dto);
193 230
194 verify(webSocketSenderApi).sendObject( 231 verify(webSocketSenderApi).sendObject(
195 - eq(UserTypeEnum.ADMIN.getValue()), eq(42L), eq(ImGroupMessageDTO.TYPE), eq(dto)); 232 + eq(UserTypeEnum.ADMIN.getValue()), eq(42L), eq(ImNotificationWebSocketDTO.TYPE),
  233 + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP,
  234 + ImContentTypeEnum.TEXT, dto)));
196 } 235 }
197 } 236 }
198 237
  238 + private static boolean isNotification(Object object, ImConversationTypeEnum conversationType,
  239 + ImContentTypeEnum contentType, Object payload) {
  240 + if (!(object instanceof ImNotificationWebSocketDTO notification)) {
  241 + return false;
  242 + }
  243 + return conversationType.getType().equals(notification.getConversationType())
  244 + && contentType.getType().equals(notification.getContentType())
  245 + && payload == notification.getPayload();
  246 + }
  247 +
  248 + private static ImNotificationWebSocketDTO buildNotification(ImConversationTypeEnum conversationType,
  249 + ImContentTypeEnum contentType, Object payload) {
  250 + return new ImNotificationWebSocketDTO()
  251 + .setConversationType(conversationType.getType())
  252 + .setContentType(contentType.getType())
  253 + .setPayload(payload);
  254 + }
  255 +
199 } 256 }
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java
1 package cn.iocoder.yudao.module.im.util; 1 package cn.iocoder.yudao.module.im.util;
2 2
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; 4 +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage;
5 import org.junit.jupiter.api.Test; 5 import org.junit.jupiter.api.Test;
6 6
7 import java.util.Map; 7 import java.util.Map;
@@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
12 import static org.junit.jupiter.api.Assertions.assertTrue; 12 import static org.junit.jupiter.api.Assertions.assertTrue;
13 13
14 /** 14 /**
15 - * IM 消息内容工具类单元测试 15 + * {@link ImMessageUtils} 的单元测试
16 * 16 *
17 * @author 芋道源码 17 * @author 芋道源码
18 */ 18 */
yudao-module-im/src/test/resources/sql/clean.sql
1 DELETE FROM "im_private_message"; 1 DELETE FROM "im_private_message";
2 DELETE FROM "im_group_message"; 2 DELETE FROM "im_group_message";
  3 +DELETE FROM "im_conversation_read";
3 DELETE FROM "im_group"; 4 DELETE FROM "im_group";
4 DELETE FROM "im_group_member"; 5 DELETE FROM "im_group_member";
5 DELETE FROM "im_friend"; 6 DELETE FROM "im_friend";
yudao-module-im/src/test/resources/sql/create_tables.sql
@@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_private_message&quot; ( @@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_private_message&quot; (
6 "type" smallint NOT NULL COMMENT '消息类型', 6 "type" smallint NOT NULL COMMENT '消息类型',
7 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', 7 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容',
8 "status" tinyint NOT NULL COMMENT '消息状态', 8 "status" tinyint NOT NULL COMMENT '消息状态',
  9 + "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态',
9 "send_time" timestamp NOT NULL COMMENT '发送时间', 10 "send_time" timestamp NOT NULL COMMENT '发送时间',
10 "creator" varchar(64) DEFAULT '', 11 "creator" varchar(64) DEFAULT '',
11 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 12 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -26,7 +27,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_group_message&quot; ( @@ -26,7 +27,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_group_message&quot; (
26 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', 27 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容',
27 "status" tinyint NOT NULL COMMENT '消息状态', 28 "status" tinyint NOT NULL COMMENT '消息状态',
28 "send_time" timestamp NOT NULL COMMENT '发送时间', 29 "send_time" timestamp NOT NULL COMMENT '发送时间',
29 - "receiver_user_ids" varchar(1024) DEFAULT NULL COMMENT '定向接收用户编号列表', 30 + "receiver_user_ids" text DEFAULT NULL COMMENT '定向接收用户编号列表',
30 "at_user_ids" varchar(1024) DEFAULT NULL COMMENT '@ 目标用户编号列表', 31 "at_user_ids" varchar(1024) DEFAULT NULL COMMENT '@ 目标用户编号列表',
31 "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态', 32 "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态',
32 "creator" varchar(64) DEFAULT '', 33 "creator" varchar(64) DEFAULT '',
@@ -281,7 +282,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_channel_message&quot; ( @@ -281,7 +282,7 @@ CREATE TABLE IF NOT EXISTS &quot;im_channel_message&quot; (
281 "material_id" bigint NOT NULL COMMENT '关联素材编号', 282 "material_id" bigint NOT NULL COMMENT '关联素材编号',
282 "type" smallint NOT NULL COMMENT '消息类型', 283 "type" smallint NOT NULL COMMENT '消息类型',
283 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容;推送时 payload JSON 快照;不含富文本正文', 284 "content" varchar(8192) DEFAULT NULL COMMENT '消息内容;推送时 payload JSON 快照;不含富文本正文',
284 - "receiver_user_ids" varchar(1024) DEFAULT NULL COMMENT '接收人编号列表;逗号分隔;为空表示全员', 285 + "receiver_user_ids" text DEFAULT NULL COMMENT '接收人编号列表;逗号分隔;为空表示全员',
285 "send_time" timestamp NOT NULL COMMENT '发送时间', 286 "send_time" timestamp NOT NULL COMMENT '发送时间',
286 "creator" varchar(64) DEFAULT '', 287 "creator" varchar(64) DEFAULT '',
287 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 288 "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -292,6 +293,23 @@ CREATE TABLE IF NOT EXISTS &quot;im_channel_message&quot; ( @@ -292,6 +293,23 @@ CREATE TABLE IF NOT EXISTS &quot;im_channel_message&quot; (
292 PRIMARY KEY ("id") 293 PRIMARY KEY ("id")
293 ) COMMENT 'IM 频道消息表'; 294 ) COMMENT 'IM 频道消息表';
294 295
  296 +CREATE TABLE IF NOT EXISTS "im_conversation_read" (
  297 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
  298 + "user_id" bigint NOT NULL COMMENT '用户编号',
  299 + "conversation_type" tinyint NOT NULL COMMENT '会话类型',
  300 + "target_id" bigint NOT NULL COMMENT '目标编号',
  301 + "message_id" bigint NOT NULL COMMENT '最大已读消息编号',
  302 + "read_time" timestamp NOT NULL COMMENT '最近已读时间',
  303 + "creator" varchar(64) DEFAULT '',
  304 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  305 + "updater" varchar(64) DEFAULT '',
  306 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  307 + "deleted" bit NOT NULL DEFAULT FALSE,
  308 + "tenant_id" bigint NOT NULL DEFAULT 0,
  309 + PRIMARY KEY ("id"),
  310 + CONSTRAINT "uk_im_conversation_read_user_target" UNIQUE ("user_id", "conversation_type", "target_id", "tenant_id")
  311 +) COMMENT 'IM 会话读位置表';
  312 +
295 CREATE TABLE IF NOT EXISTS "im_sensitive_word" ( 313 CREATE TABLE IF NOT EXISTS "im_sensitive_word" (
296 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', 314 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
297 "word" varchar(128) NOT NULL COMMENT '敏感词', 315 "word" varchar(128) NOT NULL COMMENT '敏感词',