diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 0153b73..4f70c5c 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -11,7 +11,7 @@ Target Server Version : 80200 (8.2.0) File Encoding : 65001 - Date: 19/06/2026 18:41:15 + Date: 31/05/2026 22:29:18 */ SET NAMES utf8mb4; @@ -49,7 +49,7 @@ CREATE TABLE `infra_api_access_log` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 36333 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; +) ENGINE = InnoDB AUTO_INCREMENT = 36301 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; -- ---------------------------- -- Records of infra_api_access_log @@ -92,7 +92,7 @@ CREATE TABLE `infra_api_error_log` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 24233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; +) ENGINE = InnoDB AUTO_INCREMENT = 23958 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; -- ---------------------------- -- Records of infra_api_error_log @@ -253,7 +253,7 @@ CREATE TABLE `infra_file` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2260 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; +) ENGINE = InnoDB AUTO_INCREMENT = 2233 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; -- ---------------------------- -- Records of infra_file @@ -462,7 +462,7 @@ CREATE TABLE `system_dict_data` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1061137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1061122 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; -- ---------------------------- -- Records of system_dict_data @@ -1374,14 +1374,14 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st 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'); 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'); 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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); 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'); 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'); 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 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'); 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'); 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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); +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'); 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'); 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'); 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 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'); 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'); 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'); -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'); -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'); -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'); +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'); +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'); +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'); 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'); 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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); -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'); COMMIT; -- ---------------------------- @@ -1651,7 +1636,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat 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); 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); 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); -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); +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); 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); 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); 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 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); 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); 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'); -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); -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); +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); +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); 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); 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); -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); +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); 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); 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); 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` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_username`(`username` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 5226 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; +) ENGINE = InnoDB AUTO_INCREMENT = 4697 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; -- ---------------------------- -- Records of system_login_log @@ -2229,7 +2214,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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'); 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'); 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'); -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'); +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'); 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'); 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'); 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` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_access_token`(`access_token` ASC) USING BTREE, INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 107056 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 57395 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; -- ---------------------------- -- Records of system_oauth2_access_token @@ -3526,7 +3511,7 @@ BEGIN; 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'); 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'); 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'); -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'); +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'); COMMIT; -- ---------------------------- @@ -3579,7 +3564,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3244 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 2728 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; -- ---------------------------- -- Records of system_oauth2_refresh_token @@ -3615,7 +3600,7 @@ CREATE TABLE `system_operate_log` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_user_id`(`user_id` ASC) USING BTREE, INDEX `idx_create_time`(`create_time` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9207 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; +) ENGINE = InnoDB AUTO_INCREMENT = 9195 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; -- ---------------------------- -- Records of system_operate_log @@ -3641,7 +3626,7 @@ CREATE TABLE `system_post` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表'; +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表'; -- ---------------------------- -- Records of system_post @@ -3685,7 +3670,7 @@ INSERT INTO `system_role` (`id`, `name`, `code`, `sort`, `data_scope`, `data_sco 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); 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); 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); -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); +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); COMMIT; -- ---------------------------- @@ -3704,7 +3689,7 @@ CREATE TABLE `system_role_menu` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_role_id`(`role_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 6692 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表'; +) ENGINE = InnoDB AUTO_INCREMENT = 6381 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表'; -- ---------------------------- -- Records of system_role_menu @@ -3744,6 +3729,7 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t 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); 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); 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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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); 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); 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); +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); +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); +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); +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); +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); 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); 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); 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); 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); 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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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); 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); 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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); +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); 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); 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); 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 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); 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); 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); +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); 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); 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); 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 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); 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); 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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); -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); COMMIT; -- ---------------------------- @@ -4677,7 +4643,7 @@ CREATE TABLE `system_sms_code` ( `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号' -) ENGINE = InnoDB AUTO_INCREMENT = 694 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码'; +) ENGINE = InnoDB AUTO_INCREMENT = 690 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码'; -- ---------------------------- -- Records of system_sms_code @@ -4718,7 +4684,7 @@ CREATE TABLE `system_sms_log` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1568 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; +) ENGINE = InnoDB AUTO_INCREMENT = 1553 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; -- ---------------------------- -- Records of system_sms_log @@ -4895,7 +4861,7 @@ CREATE TABLE `system_tenant` ( BEGIN; 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'); 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'); -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'); +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'); COMMIT; -- ---------------------------- @@ -4914,14 +4880,13 @@ CREATE TABLE `system_tenant_package` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 114 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户套餐表'; +) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '租户套餐表'; -- ---------------------------- -- Records of system_tenant_package -- ---------------------------- BEGIN; 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'); -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'); COMMIT; -- ---------------------------- @@ -5039,26 +5004,26 @@ CREATE TABLE `system_users` ( -- Records of system_users -- ---------------------------- BEGIN; -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); -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); +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); +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); 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); -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); +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); 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); 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); 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); 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); 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); -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); +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); 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); 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); 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); 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); 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); 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); -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); -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); +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); +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); 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); -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); +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); COMMIT; -- ---------------------------- diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java new file mode 100644 index 0000000..aea2b9c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/ImConversationReadController.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.im.controller.admin.conversation; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.conversation.vo.ImConversationReadRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - IM 会话读位置") +@RestController +@RequestMapping("/im/conversation-read") +@Validated +public class ImConversationReadController { + + @Resource + private ImConversationReadService conversationReadService; + + @GetMapping("/pull") + @Operation(summary = "增量拉取当前用户的会话读位置(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyConversationRead( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = conversationReadService.pullConversationReadList( + getLoginUserId(), lastUpdateTime, lastId, limit); + return success(BeanUtils.toBean(list, ImConversationReadRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java new file mode 100644 index 0000000..2cafb46 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/conversation/vo/ImConversationReadRespVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.im.controller.admin.conversation.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * IM 会话读位置 Response VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - IM 会话读位置 Response VO") +@Data +public class ImConversationReadRespVO { + + @Schema(description = "读位置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "会话类型", example = "1") + private Integer conversationType; // 参见 ImConversationTypeEnum 枚举 + + @Schema(description = "目标编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long targetId; + + @Schema(description = "最大已读消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") + private Long messageId; + + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java index 43668f8..1cb8913 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java +++ b/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; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -46,11 +48,25 @@ public class ImFriendController { @GetMapping("/list") @Operation(summary = "获得当前登录用户的好友列表") public CommonResult> getMyFriendList() { - // 含 DISABLE 历史好友:保留给前端展示「已删除好友」的历史对话信息;前端按 status 决定会话级联清理 List friends = friendService.getFriendList(getLoginUserId()); return success(buildFriendRespVOList(friends)); } + @GetMapping("/pull") + @Operation(summary = "增量拉取当前用户的好友关系(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyFriendList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = friendService.pullFriendList(getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildFriendRespVOList(list)); + } + @GetMapping("/get") @Operation(summary = "获得好友详情") @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") @@ -62,8 +78,8 @@ public class ImFriendController { @DeleteMapping("/delete") @Operation(summary = "删除好友(单向软删除)") @Parameters({ - @Parameter(description = "好友的用户编号", required = true, example = "2048"), - @Parameter(description = "是否级联清理本端相关数据(如私聊会话)") + @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048"), + @Parameter(name = "clear", description = "是否级联清理本端相关数据(如私聊会话)") }) public CommonResult deleteFriend( @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId, diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java index 10b2351..c074568 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendRequestController.java +++ b/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; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -79,15 +80,32 @@ public class ImFriendRequestController { @GetMapping("/list") @Operation(summary = "查询「我相关」的好友申请列表(游标分页:传 maxId 加载更多)") + @Parameters({ + @Parameter(name = "maxId", description = "当前列表最旧记录的 id;首页不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) public CommonResult> getMyFriendRequestList( - @Parameter(description = "当前列表最旧记录的 id;首页不传") @RequestParam(value = "maxId", required = false) Long maxId, - @Parameter(description = "单次拉取条数", required = true) @RequestParam("limit") @Min(1) @Max(200) Integer limit) { List list = friendRequestService.getMyFriendRequestList(getLoginUserId(), maxId, limit); return success(buildList(list)); } + @GetMapping("/pull") + @Operation(summary = "增量拉取「我相关」的好友申请(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyFriendRequestList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = friendRequestService.pullFriendRequestList(getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildList(list)); + } + @GetMapping("/get") @Operation(summary = "按 id 单查「我相关」的申请记录(带越权过滤;WebSocket 通知到达后用)") @Parameter(name = "id", description = "申请记录编号", required = true) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java index 2e17b51..6f4b14e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java @@ -47,6 +47,9 @@ public class ImFriendRespVO { @Schema(description = "删除好友时间") private LocalDateTime deleteTime; + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + // ========== 下面是聚合字段,方便前端显示 ========== @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道") diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java index 6f8f93d..d32a098 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/request/ImFriendRequestRespVO.java +++ b/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 { @Schema(description = "申请创建时间") private LocalDateTime createTime; + @Schema(description = "最近更新时间(增量拉取游标用)") + private LocalDateTime updateTime; + // ========== 下面是聚合字段,方便前端显示 ========== @Schema(description = "发起方昵称(实时聚合自 AdminUser)", example = "芋道") diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java index 370fdab..587b216 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; @@ -187,8 +188,10 @@ public class ImGroupController { return Collections.emptyList(); } // 仅当前用户是有效成员的群才允许回填置顶消息 - Set activeGroupIds = convertSet( - groupMemberService.getActiveGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId); + Map memberMap = convertMap( + groupMemberService.getGroupMemberListByUserId(loginUserId), ImGroupMemberDO::getGroupId); + Set activeGroupIds = convertSet(memberMap.values(), ImGroupMemberDO::getGroupId, + member -> CommonStatusEnum.ENABLE.getStatus().equals(member.getStatus())); Set allMessageIds = convertSetByFlatMap(groups, group -> activeGroupIds.contains(group.getId()) ? CollUtil.emptyIfNull(group.getPinnedMessageIds()).stream() : Stream.empty()); Map messageMap = groupMessageService.getGroupMessageMap(allMessageIds); @@ -198,6 +201,8 @@ public class ImGroupController { // 标记登录用户在该群的成员状态,供前端区分当前群与历史退群 boolean joined = activeGroupIds.contains(group.getId()); groupVO.setJoinStatus(joined ? CommonStatusEnum.ENABLE.getStatus() : CommonStatusEnum.DISABLE.getStatus()); + MapUtils.findAndThen(memberMap, group.getId(), member -> + groupVO.setGroupRemark(member.getGroupRemark()).setSilent(Boolean.TRUE.equals(member.getSilent()))); if (!joined || CollUtil.isEmpty(group.getPinnedMessageIds())) { return groupVO; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java index 4c62469..c752eeb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java @@ -21,6 +21,7 @@ import jakarta.validation.Valid; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -88,7 +89,7 @@ public class ImGroupMemberController { } @GetMapping("/list") - @Operation(summary = "获得指定群的成员列表") + @Operation(summary = "获得指定群的成员列表(按群全量拉取,作为前端本地成员 cache 的完整基线)") @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { // 1.1 查询群成员列表(包含 DISABLE 已退群的成员,不按时间过滤) @@ -101,16 +102,30 @@ public class ImGroupMemberController { throw exception(GROUP_MEMBER_NOT_IN_GROUP); } - // 2.批量聚合 AdminUser 信息(昵称 / 头像) + // 2. 批量聚合昵称 / 头像,并对非本人置空私人字段 + return success(buildGroupMemberRespVOList(members)); + } + + // ========== 私有方法:VO 组装 ========== + + /** + * 群成员列表批量转 VO + 关联回填昵称 / 头像,并对非本人置空私人字段 + */ + private List buildGroupMemberRespVOList(List members) { + if (CollUtil.isEmpty(members)) { + return Collections.emptyList(); + } + Long loginUserId = getLoginUserId(); + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1 Map userMap = adminUserApi.getUserMap( convertList(members, ImGroupMemberDO::getUserId)); - return success(convertList(members, m -> { - ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class); - MapUtils.findAndThen(userMap, m.getUserId(), user -> + return convertList(members, member -> { + ImGroupMemberRespVO vo = BeanUtils.toBean(member, ImGroupMemberRespVO.class); + MapUtils.findAndThen(userMap, member.getUserId(), user -> vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())); - hidePrivateFieldsIfNotSelf(vo, m.getUserId(), loginUserId); + hidePrivateFieldsIfNotSelf(vo, member.getUserId(), loginUserId); return vo; - })); + }); } /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java index 440e30b..6ffebe5 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupRequestController.java +++ b/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; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import org.springframework.validation.annotation.Validated; @@ -88,6 +91,22 @@ public class ImGroupRequestController { return success(buildVOList(list)); } + @GetMapping("/pull") + @Operation(summary = "增量拉取「我管理的群」下的加群申请(重连 / 离线补偿)") + @Parameters({ + @Parameter(name = "lastUpdateTime", description = "上次拉取到的最新更新时间(毫秒时间戳);首次拉取不传"), + @Parameter(name = "lastId", description = "上次拉取到的最后一条记录 id;首次拉取不传"), + @Parameter(name = "limit", description = "单次拉取条数", required = true) + }) + public CommonResult> pullMyGroupRequestList( + @RequestParam(value = "lastUpdateTime", required = false) Long lastUpdateTime, + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam("limit") @Min(1) @Max(200) Integer limit) { + List list = groupRequestService.pullGroupRequestList( + getLoginUserId(), lastUpdateTime, lastId, limit); + return success(buildVOList(list)); + } + @GetMapping("/list-by-group") @Operation(summary = "查询指定群下的全部加群申请(含已处理);仅群主 / 管理员可查") @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java index 5f1ec5d..f15d766 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java @@ -50,6 +50,12 @@ public class ImGroupRespVO { @Schema(description = "当前登录用户在该群的成员状态:在群 / 已退群(历史退群群仍返回,供前端展示离线消息的群名 / 头像)") private Integer joinStatus; // 参见 CommonStatusEnum 枚举 + @Schema(description = "当前登录用户对该群的备注") + private String groupRemark; + + @Schema(description = "当前登录用户是否免打扰") + private Boolean silent; + @Schema(description = "群置顶消息列表,按 pin 顺序(最先置顶的在前);非该群有效成员时为空") private List pinnedMessages; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java index 587ca8d..9e21cae 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/request/ImGroupRequestRespVO.java +++ b/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 { @Schema(description = "申请创建时间") private LocalDateTime createTime; + @Schema(description = "申请更新时间;增量拉取游标") + private LocalDateTime updateTime; + // ========== 下面是聚合字段,方便前端显示 ========== @Schema(description = "申请人 / 被邀请人昵称(实时聚合自 AdminUser)", example = "芋道") diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java index 2afc95e..65e06f7 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/channel/ImChannelManagerController.java @@ -75,7 +75,6 @@ public class ImChannelManagerController { @GetMapping("/simple-list") @Operation(summary = "获得启用的频道精简列表;前端表单选择频道时调用") public CommonResult> getSimpleChannelList() { - // TODO DONE @AI:getChannelListByStatus 统一命名 List list = channelService.getChannelListByStatus(CommonStatusEnum.ENABLE.getStatus()); return success(BeanUtils.toBean(list, ImChannelRespVO.class)); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java index 9f76ed2..cf1bd64 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/channel/ImChannelMessageRespVO.java +++ b/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 { private String materialCoverUrl; @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "125") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息内容;payload JSON 快照") private String content; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java index 86d236d..121ac90 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java +++ b/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 { private Long senderId; @Schema(description = "消息类型", example = "1") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息内容", example = "你好") private String content; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java index 836b3b3..fcc964a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java +++ b/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 { private String senderNickname; @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) private String content; @@ -43,7 +43,7 @@ public class ImGroupMessageManagerRespVO { private List atUserNicknames; @Schema(description = "回执状态", example = "0") - private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类 + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime sendTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java index e6a72da..6fa711b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java +++ b/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 { private Long receiverId; @Schema(description = "消息类型", example = "1") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息内容", example = "你好") private String content; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java index c467a0a..941ef8c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java +++ b/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 { private String receiverNickname; @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) private String content; @@ -36,6 +36,9 @@ public class ImPrivateMessageManagerRespVO { @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") private Integer status; // 参见 ImMessageStatusEnum 枚举类 + @Schema(description = "回执状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime sendTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java index 82403e3..0a5f1d5 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/ImStatisticsManagerController.java @@ -112,7 +112,7 @@ public class ImStatisticsManagerController { } @GetMapping("/message-type-distribution") - @Operation(summary = "获得消息类型分布(最近 30 天)") + @Operation(summary = "获得内容类型分布(最近 30 天)") @PreAuthorize("@ss.hasPermission('im:manager:statistics:query')") public CommonResult> getMessageTypeDistribution() { LocalDateTime endTime = LocalDate.now().plusDays(1).atStartOfDay(); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java index 2da0827..fce2275 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/statistics/vo/ImStatisticsManagerMessageTypeRespVO.java +++ b/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; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@Schema(description = "管理后台 - IM 数据看板消息类型分布项 Response VO") +@Schema(description = "管理后台 - IM 数据看板内容类型分布项 Response VO") @Data public class ImStatisticsManagerMessageTypeRespVO { @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer type; // 参见 ImMessageTypeEnum 枚举类 + private Integer type; // 参见 ImContentTypeEnum 枚举类 @Schema(description = "消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000") private Long value; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java index 08e557a..41a8c01 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImChannelMessageController.java +++ b/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; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.controller.admin.message.vo.channel.ImChannelMessagePullRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.service.message.ImChannelMessageService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -40,25 +40,25 @@ public class ImChannelMessageController { @GetMapping("/pull") @Operation(summary = "拉取频道消息(离线增量);按 minId 游标分页") - public CommonResult> pull( + public CommonResult> pullChannelMessageList( @RequestParam(value = "minId", defaultValue = "0") @PositiveOrZero(message = "minId 不能小于 0") Long minId, @RequestParam(value = "size", defaultValue = "100") @Min(value = 1, message = "size 必须大于 0") @Max(value = 200, message = "size 一次最多 200 条") Integer size) { // 1. 拉取消息列表 Long userId = getLoginUserId(); - List list = channelMessageService.getMessageListForPull(userId, minId, size); + List list = channelMessageService.pullChannelMessageList(userId, minId, size); if (CollUtil.isEmpty(list)) { return success(Collections.emptyList()); } - // 2. 按 Redis 已读游标补 status;device A 已读后 device B 拉到这条不再算入未读 + // 2. 按已读游标补 receiptStatus(已读 DONE / 未读 PENDING) Map readMaxByChannel = channelMessageService.getChannelReadMaxMessageIdMap( userId, convertSet(list, ImChannelMessageDO::getChannelId)); return success(BeanUtils.toBean(list, ImChannelMessagePullRespVO.class, vo -> { Long readMax = readMaxByChannel.get(vo.getChannelId()); - vo.setStatus(readMax != null && readMax >= vo.getId() - ? ImMessageStatusEnum.READ.getStatus() - : ImMessageStatusEnum.UNREAD.getStatus()); + vo.setReceiptStatus(readMax != null && readMax >= vo.getId() + ? ImMessageReceiptStatusEnum.DONE.getStatus() + : ImMessageReceiptStatusEnum.PENDING.getStatus()); })); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java index d375ec7..277dc97 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/ImPrivateMessageController.java @@ -61,7 +61,7 @@ public class ImPrivateMessageController { @GetMapping("/max-read-message-id") @Operation(summary = "查询对方已读到我发的最大消息 id", - description = "用于多端 / 离线场景下的已读位置补齐:进入会话或断线重连后调用,结果用于翻转本地自发消息状态") + description = "用于多端 / 离线场景下的已读位置补齐:进入会话或断线重连后调用,据此把本地自发消息更新为已读") @Parameter(name = "peerId", description = "对方用户编号", required = true, example = "2") public CommonResult getMaxReadMessageId(@RequestParam("peerId") Long peerId) { return success(privateMessageService.getMaxReadMessageId(getLoginUserId(), peerId)); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java index 41bbbf9..8a30fcb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/channel/ImChannelMessagePullRespVO.java +++ b/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 { @Schema(description = "消息内容;payload JSON 快照", requiredMode = Schema.RequiredMode.REQUIRED) private String content; - @Schema(description = "当前用户的已读态;按 Redis 游标计算填充", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - private Integer status; // 参见 ImMessageStatusEnum 枚举类 + @Schema(description = "当前用户回执 / 已读态;按 Redis 读位置计算(已读 DONE,未读 PENDING)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime sendTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java index f02d919..813e35a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/group/ImGroupMessageSendReqVO.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.im.controller.admin.message.vo.group; import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; @@ -28,7 +28,7 @@ public class ImGroupMessageSendReqVO { @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") @NotNull(message = "消息类型不能为空") - @InEnum(ImMessageTypeEnum.class) + @InEnum(ImContentTypeEnum.class) private Integer type; @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") @@ -47,7 +47,7 @@ public class ImGroupMessageSendReqVO { @AssertTrue(message = "消息类型不允许") @JsonIgnore public boolean isTypeNormal() { - return type == null || ImMessageTypeEnum.validate(type).isNormal(); + return type == null || ImContentTypeEnum.validate(type).isNormal(); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java index 3484b12..9b62689 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageRespVO.java +++ b/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 { private String content; @Schema(description = "消息状态", example = "0") - private Integer status; + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "回执状态", example = "0") + private Integer receiptStatus; // 参见 ImMessageReceiptStatusEnum 枚举类 @Schema(description = "发送时间") private LocalDateTime sendTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java index d6c703f..6552234 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/message/vo/privates/ImPrivateMessageSendReqVO.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.im.controller.admin.message.vo.privates; import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; @@ -26,20 +26,23 @@ public class ImPrivateMessageSendReqVO { @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") @NotNull(message = "消息类型不能为空") - @InEnum(ImMessageTypeEnum.class) + @InEnum(ImContentTypeEnum.class) private Integer type; @Schema(description = "消息内容,JSON 格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"content\":\"你好\"}") @NotEmpty(message = "消息内容不能为空") private String content; + @Schema(description = "是否需要回执", example = "false") + private Boolean receipt; + /** * 仅允许用户消息(normal)类型 */ @AssertTrue(message = "消息类型不允许") @JsonIgnore public boolean isTypeNormal() { - return type == null || ImMessageTypeEnum.validate(type).isNormal(); + return type == null || ImContentTypeEnum.validate(type).isNormal(); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java new file mode 100644 index 0000000..bdd9fa0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/conversation/ImConversationReadDO.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.im.dal.dataobject.conversation; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * IM 会话读位置 DO + *

+ * 只表达「用户在某个会话的最大已读位置」,私聊 / 群聊 / 频道统一落这张表,是读位置的唯一权威。 + * + * @author 芋道源码 + */ +@TableName("im_conversation_read") +@KeySequence("im_conversation_read_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ImConversationReadDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long userId; + /** + * 会话类型 + *

+ * 枚举 {@link ImConversationTypeEnum} + */ + private Integer conversationType; + /** + * 目标编号 + *

+ * 私聊关联 AdminUserDO 的 id;群聊关联 {@link ImGroupDO#getId()};频道关联 {@link ImChannelDO#getId()} + */ + private Long targetId; + /** + * 最大已读消息编号 + *

+ * 关联 {@link ImPrivateMessageDO#getId()}、{@link ImGroupMessageDO#getId()}、{@link ImChannelMessageDO#getId()} + */ + private Long messageId; + /** + * 最近已读时间 + */ + private LocalDateTime readTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java index 2572828..d71ee7c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImChannelMessageDO.java +++ b/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; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelDO; import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -54,7 +54,7 @@ public class ImChannelMessageDO extends BaseDO { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java index f6c3d60..bdab8a8 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImGroupMessageDO.java +++ b/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; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -54,7 +54,7 @@ public class ImGroupMessageDO extends BaseDO { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** @@ -67,8 +67,6 @@ public class ImGroupMessageDO extends BaseDO { * 消息状态 *

* 枚举 {@link ImMessageStatusEnum} - * - * 为什么没有 READ 状态?与单聊的差异:单聊用 UNREAD/READ 跟踪每条消息状态,群聊用 Redis 存储每个成员的已读位置(游标模型) */ private Integer status; /** @@ -94,7 +92,7 @@ public class ImGroupMessageDO extends BaseDO { /** * 回执状态 *

- * 枚举 {@link ImGroupMessageReceiptStatusEnum} + * 枚举 {@link ImMessageReceiptStatusEnum} */ private Integer receiptStatus; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java index bb87939..808c1ee 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/ImPrivateMessageDO.java @@ -1,8 +1,9 @@ package cn.iocoder.yudao.module.im.dal.dataobject.message; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -49,7 +50,7 @@ public class ImPrivateMessageDO extends BaseDO { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** @@ -65,6 +66,12 @@ public class ImPrivateMessageDO extends BaseDO { */ private Integer status; /** + * 回执状态 + *

+ * 枚举 {@link ImMessageReceiptStatusEnum} + */ + private Integer receiptStatus; + /** * 发送时间 */ private LocalDateTime sendTime; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java index dd37cc0..2422467 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/AudioMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/AudioMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/CardMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java index 39bef39..d99ed12 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/CardMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/CardMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java index f5c6b6c..6d6a642 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FaceMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FaceMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java index 7feb9ff..bd4fcc3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/FileMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/FileMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java index bc8e41e..052342a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/ImageMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/ImageMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/LocationMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java index 8c36d1a..fdbb7c7 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/LocationMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/LocationMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MaterialMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java index 9ef4308..41fbdce 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MaterialMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MaterialMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; import lombok.experimental.Accessors; @@ -6,7 +6,7 @@ import lombok.experimental.Accessors; /** * 频道素材消息 payload *

- * 对应 {@link cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum#MATERIAL}(type=125)。 + * 对应 {@link cn.iocoder.yudao.module.im.enums.ImContentTypeEnum#MATERIAL}(type=125)。 * 客户端按本字段集渲染图文卡片; * 富文本正文不在本 payload 中传递,点击详情时另调 /im/channel/material/get-content?id= 按需拉取。 */ diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MergeMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java index 62a051d..9bc675b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/MergeMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/MergeMessage.java @@ -1,6 +1,6 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import lombok.Data; import java.util.List; @@ -47,7 +47,7 @@ public class MergeMessage { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java index 20fa9f3..a06d417 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/QuoteMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/QuoteMessage.java @@ -1,6 +1,6 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import lombok.Data; import lombok.experimental.Accessors; @@ -39,7 +39,7 @@ public class QuoteMessage { /** * 被引用消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/RecallMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java index 5df78f2..a9ba575 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/RecallMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/RecallMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; import lombok.experimental.Accessors; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java index 9d54b7d..4d8a66c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/TextMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/TextMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java index a5ba53a..5bceef1 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/message/VideoMessage.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/message/content/VideoMessage.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.message; +package cn.iocoder.yudao.module.im.dal.dataobject.message.content; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java new file mode 100644 index 0000000..45a702f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapper.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.im.dal.mysql.conversation; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * IM 会话读位置 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ImConversationReadMapper extends BaseMapperX { + + default ImConversationReadDO selectByUserIdAndConversation(Long userId, Integer conversationType, Long conversationId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId) + .eq(ImConversationReadDO::getConversationType, conversationType) + .eq(ImConversationReadDO::getTargetId, conversationId)); + } + + default List selectListByConversation(Integer conversationType, Long conversationId) { + return selectList(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getConversationType, conversationType) + .eq(ImConversationReadDO::getTargetId, conversationId)); + } + + default List selectListByUserIdAndConversations(Long userId, Integer conversationType, + Collection conversationIds) { + return selectList(new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId) + .eq(ImConversationReadDO::getConversationType, conversationType) + .in(ImConversationReadDO::getTargetId, conversationIds)); + } + + /** + * 增量拉取当前用户的会话读位置(按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 会话读位置列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(ImConversationReadDO::getUserId, userId); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImConversationReadDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImConversationReadDO::getUpdateTime, lastTime).gt(ImConversationReadDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImConversationReadDO::getUpdateTime).orderByAsc(ImConversationReadDO::getId) + .last("LIMIT " + limit)); + } + + /** + * 单调递增更新读位置:仅当新位置更大时才更新 + *

+ * 通过 {@code read_message_id < messageId} 的 CAS 条件,保证乱序 / 并发上报时读位置不会回退。 + * + * @param id 记录编号 + * @param messageId 新的最大已读消息编号 + * @param readTime 已读时间 + * @return 影响行数;0 表示新位置不大于已有位置,未更新 + */ + default int updateReadMessageIdToLarger(Long id, Long messageId, LocalDateTime readTime) { + return update(null, Wrappers.lambdaUpdate() + .set(ImConversationReadDO::getMessageId, messageId) + .set(ImConversationReadDO::getReadTime, readTime) + .set(ImConversationReadDO::getUpdateTime, readTime) + .eq(ImConversationReadDO::getId, id) + .lt(ImConversationReadDO::getMessageId, messageId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java index 83752a9..8673100 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.dal.mysql.friend; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -31,6 +32,28 @@ public interface ImFriendMapper extends BaseMapperX { .eq(ImFriendDO::getUserId, userId) .orderByDesc(ImFriendDO::getId)); } + + /** + * 增量拉取当前用户的好友关系(含已删除,按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友关系列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(ImFriendDO::getUserId, userId); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImFriendDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImFriendDO::getUpdateTime, lastTime).gt(ImFriendDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImFriendDO::getUpdateTime).orderByAsc(ImFriendDO::getId) + .last("LIMIT " + limit)); + } + default List selectListByUserIdAndStatus(Long userId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(ImFriendDO::getUserId, userId) @@ -68,12 +91,14 @@ public interface ImFriendMapper extends BaseMapperX { @SuppressWarnings("UnusedReturnValue") default int updateReAddFields(Long id, Integer status, LocalDateTime addTime, + LocalDateTime updateTime, Boolean silent, Boolean pinned, Boolean blocked, String displayName, Integer addSource) { return update(null, Wrappers.lambdaUpdate() .eq(ImFriendDO::getId, id) .set(ImFriendDO::getStatus, status) .set(ImFriendDO::getAddTime, addTime) + .set(ImFriendDO::getUpdateTime, updateTime) .set(ImFriendDO::getSilent, silent) .set(ImFriendDO::getPinned, pinned) .set(ImFriendDO::getBlocked, blocked) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java index e81def0..6dffaa5 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.dal.mysql.friend; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -28,6 +29,12 @@ public interface ImFriendRequestMapper extends BaseMapperX { /** * 拉取「我相关」的好友申请列表 + * + * @param userId 当前用户编号 + * @param maxRequestUpdateTime 起始更新时间;首次拉取传 null + * @param maxId 起始记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友申请列表 */ default List selectMyList(Long userId, LocalDateTime maxRequestUpdateTime, Long maxId, int limit) { @@ -45,6 +52,28 @@ public interface ImFriendRequestMapper extends BaseMapperX { return selectList(wrapper); } + /** + * 增量拉取「我相关」的好友申请(双向 OR,按 update_time + id 正向游标) + * + * @param userId 当前用户编号 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 好友申请列表 + */ + default List selectPullListByUserId(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX<>(); + query.and(w -> w.eq(ImFriendRequestDO::getFromUserId, userId) + .or().eq(ImFriendRequestDO::getToUserId, userId)); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImFriendRequestDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImFriendRequestDO::getUpdateTime, lastTime).gt(ImFriendRequestDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImFriendRequestDO::getUpdateTime).orderByAsc(ImFriendRequestDO::getId) + .last("LIMIT " + limit)); + } + default int updateByIdAndHandleResult(Long id, Integer handleResult, ImFriendRequestDO updateObj) { return update(updateObj, new LambdaUpdateWrapper() .eq(ImFriendRequestDO::getId, id).eq(ImFriendRequestDO::getHandleResult, handleResult)); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java index 2ef54da..2afa559 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -59,20 +58,8 @@ public interface ImGroupMemberMapper extends BaseMapperX { .eq(ImGroupMemberDO::getStatus, status)); } - /** - * 查询用户已退群的成员记录 - *

- * 当 {@code minQuitTime} 非空时额外按 {@code quitTime ≥ minQuitTime} 过滤。 - * - * @param userId 用户编号 - * @param minQuitTime 最早退群时间(含),可空 - * @return 已退群成员记录列表 - */ - default List selectQuitListByUserId(Long userId, LocalDateTime minQuitTime) { - return selectList(new LambdaQueryWrapperX() - .eq(ImGroupMemberDO::getUserId, userId) - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.DISABLE.getStatus()) - .geIfPresent(ImGroupMemberDO::getQuitTime, minQuitTime)); + default List selectListByUserId(Long userId) { + return selectList(ImGroupMemberDO::getUserId, userId); } @SuppressWarnings("UnusedReturnValue") @@ -143,7 +130,8 @@ public interface ImGroupMemberMapper extends BaseMapperX { .set(ImGroupMemberDO::getAddSource, addSource) .set(ImGroupMemberDO::getInviterUserId, inviterUserId) .set(ImGroupMemberDO::getQuitTime, null) - .set(ImGroupMemberDO::getMuteEndTime, null)); + .set(ImGroupMemberDO::getMuteEndTime, null) + .set(ImGroupMemberDO::getUpdateTime, joinTime)); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java index 72fe756..c344493 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupRequestMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -43,6 +44,28 @@ public interface ImGroupRequestMapper extends BaseMapperX { .orderByDesc(ImGroupRequestDO::getId)); } + /** + * 增量拉取「我管理的群」下的加群申请(含已处理,按 update_time + id 正向游标) + * + * @param groupIds 群编号集合 + * @param lastUpdateTime 上次拉取到的更新时间;首次拉取传 null + * @param lastId 上次拉取到的记录编号;首次拉取传 null + * @param limit 拉取数量 + * @return 加群申请列表 + */ + default List selectPullListByGroupIds(Collection groupIds, Long lastUpdateTime, + Long lastId, Integer limit) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .in(ImGroupRequestDO::getGroupId, groupIds); + if (lastUpdateTime != null && lastId != null) { + LocalDateTime lastTime = LocalDateTimeUtil.of(lastUpdateTime); + query.and(w -> w.gt(ImGroupRequestDO::getUpdateTime, lastTime) + .or(n -> n.eq(ImGroupRequestDO::getUpdateTime, lastTime).gt(ImGroupRequestDO::getId, lastId))); + } + return selectList(query.orderByAsc(ImGroupRequestDO::getUpdateTime).orderByAsc(ImGroupRequestDO::getId) + .last("LIMIT " + limit)); + } + default int updateByIdAndHandleResult(Long id, Integer expectedHandleResult, ImGroupRequestDO updateObj) { return update(updateObj, new LambdaUpdateWrapper() .eq(ImGroupRequestDO::getId, id).eq(ImGroupRequestDO::getHandleResult, expectedHandleResult)); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java index 84736fd..d0d5584 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java +++ b/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; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; +import java.util.Collection; import java.util.List; /** @@ -22,48 +24,22 @@ import java.util.List; public interface ImGroupMessageMapper extends BaseMapperX { /** - * 根据 minId + 时间窗口增量拉取群聊消息(在群成员使用) + * 根据 minId + 时间窗口增量拉取群聊消息 * - * @param groupIds 用户当前仍在群内的群编号列表 + * @param userId 当前用户编号 + * @param groupIds 候选群编号集合(当前在群 ∪ 窗口内退群),不能为空 * @param minId 最小消息 id(不含) * @param minSendTime 最早发送时间(不含),限制离线消息时间窗口 * @param size 拉取数量 * @return 消息列表(按 id 升序) */ - default List selectListByMinId(List groupIds, Long minId, + default List selectListByMinId(Long userId, Collection groupIds, Long minId, LocalDateTime minSendTime, Integer size) { QueryWrapperX wrapper = new QueryWrapperX<>(); wrapper.in("group_id", groupIds) .gt("id", minId) .gt("send_time", minSendTime) - .orderByAsc("id"); - wrapper.limitN(size); - return selectList(wrapper); - } - - /** - * 查询"退群前"的离线消息(退群成员使用) - *

- * 语义:用户已退出某群,但仍需把 {@code minId} 之后、{@code minSendTime} 之后、不晚于退群时间的消息补齐到本地,便于前端看到完整上下文。 - *

- * 撤回消息(status=RECALL)保留返回,与在群成员的 {@link #selectListByMinId} 行为一致,由前端按撤回信号渲染「消息已撤回」气泡。 - * - * @param groupId 群编号 - * @param minId 最小消息 id(不含) - * @param minSendTime 最早发送时间(不含) - * @param quitTime 退群时间(含),仅返回退群当时已存在的消息 - * @param size 拉取数量 - * @return 消息列表(按 id 升序) - */ - default List selectListByGroupIdAndMinIdAndQuitTimeBefore(Long groupId, Long minId, - LocalDateTime minSendTime, - LocalDateTime quitTime, - Integer size) { - QueryWrapperX wrapper = new QueryWrapperX<>(); - wrapper.eq("group_id", groupId) - .gt("id", minId) - .gt("send_time", minSendTime) - .le("send_time", quitTime) + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId) .orderByAsc("id"); wrapper.limitN(size); return selectList(wrapper); @@ -72,17 +48,17 @@ public interface ImGroupMessageMapper extends BaseMapperX { /** * 查询群聊历史消息(游标拉取) * - * @param groupId 群编号 - * @param maxId 起始消息 id(不含),为空则从最新开始 - * @param limit 拉取数量 - * @param joinTime 入群时间,仅返回入群之后的消息 + * @param userId 当前用户编号 + * @param groupId 群编号 + * @param maxId 起始消息 id(不含),为空则从最新开始 + * @param limit 拉取数量 * @return 消息列表(按 id 倒序) */ - default List selectHistoryList(Long groupId, Long maxId, Integer limit, LocalDateTime joinTime) { + default List selectHistoryListByUser(Long userId, Long groupId, Long maxId, Integer limit) { QueryWrapperX wrapper = new QueryWrapperX<>(); wrapper.eq("group_id", groupId) .lt(maxId != null, "id", maxId) - .ge(joinTime != null, "send_time", joinTime) + .apply(MyBatisUtils.findInSet("receiver_user_ids"), userId) .orderByDesc("id"); wrapper.limitN(limit); return selectList(wrapper); @@ -108,7 +84,7 @@ public interface ImGroupMessageMapper extends BaseMapperX { default List selectListByGroupIdAndPendingReceipt(Long groupId, Long minId, Long maxId) { return selectList(new LambdaQueryWrapperX() .eq(ImGroupMessageDO::getGroupId, groupId) - .eq(ImGroupMessageDO::getReceiptStatus, ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) + .eq(ImGroupMessageDO::getReceiptStatus, ImMessageReceiptStatusEnum.PENDING.getStatus()) .gt(minId != null, ImGroupMessageDO::getId, minId) .le(ImGroupMessageDO::getId, maxId) .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java index a33f29f..b666dfb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java +++ b/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 .eq(ImPrivateMessageDO::getClientMessageId, clientMessageId)); } - default Long selectMaxIdBySenderIdAndReceiverIdAndStatus(Long senderId, Long receiverId, Integer status) { - ImPrivateMessageDO message = selectOne(new LambdaQueryWrapperX() - .eq(ImPrivateMessageDO::getSenderId, senderId) - .eq(ImPrivateMessageDO::getReceiverId, receiverId) - .eq(ImPrivateMessageDO::getStatus, status) - .orderByDesc(ImPrivateMessageDO::getId) - .last("LIMIT 1")); - return message != null ? message.getId() : null; - } - - default int updateBySenderIdAndReceiverIdAndIdLeAndStatus(Long senderId, Long receiverId, Long maxMessageId, - Integer whereStatus, ImPrivateMessageDO updateObj) { + default int updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus(Long senderId, Long receiverId, Long maxMessageId, + Integer whereReceiptStatus, ImPrivateMessageDO updateObj) { return update(updateObj, new LambdaQueryWrapperX() .eq(ImPrivateMessageDO::getSenderId, senderId) .eq(ImPrivateMessageDO::getReceiverId, receiverId) .le(ImPrivateMessageDO::getId, maxMessageId) - .eq(ImPrivateMessageDO::getStatus, whereStatus)); + .eq(ImPrivateMessageDO::getReceiptStatus, whereReceiptStatus)); } default PageResult selectPage(ImPrivateMessageManagerPageReqVO reqVO) { diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java index 141f6b8..26e3d98 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java @@ -154,7 +154,7 @@ public interface ImStatisticsManagerMapper { @Param("endTime") LocalDateTime endTime); /** - * 区间内消息类型分布(私聊+群聊合并) + * 区间内内容类型分布(私聊+群聊合并) * * @return [{type: 0, count: 123}, ...] */ diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java index 247ad33..92d3890 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java +++ b/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; public interface RedisKeyConstants { /** - * 群消息已读位置 - * KEY 格式: im:group:message:read:{groupId} - * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId) - */ - String GROUP_MESSAGE_READ = "im:group:message:read:%s"; - - /** - * 频道消息已读位置 - * KEY 格式: im:channel:message:read:{channelId} - * VALUE 数据类型: Hash (field: userId, value: maxReadMessageId) - */ - String CHANNEL_MESSAGE_READ = "im:channel:message:read:%s"; - - /** * 好友关系状态缓存(合并「是否好友」+「是否拉黑」两态) *

* KEY 格式:friend_state:{userId}_{friendUserId} @@ -44,7 +30,7 @@ public interface RedisKeyConstants { * KEY 格式:group_member_ids:{groupId} * VALUE 数据类型:List *

- * 说明:只缓存轻量的 userId 列表,适合"群消息推送目标"这类只关心 userId 的场景。 + * 说明:只缓存轻量的 userId 列表,适合「群消息推送目标」这类只关心 userId 的场景 */ String GROUP_MEMBER_IDS = "group_member_ids"; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java deleted file mode 100644 index bda4b33..0000000 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImChannelMessageReadRedisDAO.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.im.dal.redis.message; - -import cn.hutool.core.convert.Convert; -import jakarta.annotation.Resource; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Repository; - -import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.CHANNEL_MESSAGE_READ; - -/** - * IM 频道消息已读位置 Redis DAO - * - * @author 芋道源码 - */ -@Repository -public class ImChannelMessageReadRedisDAO { - - @Resource - private StringRedisTemplate stringRedisTemplate; - - /** - * 更新用户在某频道的最大已读消息编号 - * - * @param channelId 频道编号 - * @param userId 用户编号 - * @param maxMessageId 最大已读消息编号 - */ - public void updateReadMaxMessageId(Long channelId, Long userId, Long maxMessageId) { - String key = formatKey(channelId); - stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString()); - } - - /** - * 获取用户在某频道的最大已读消息编号 - * - * @param channelId 频道编号 - * @param userId 用户编号 - * @return 最大已读消息编号;不存在则返回 null - */ - public Long getReadMaxMessageId(Long channelId, Long userId) { - String key = formatKey(channelId); - Object val = stringRedisTemplate.opsForHash().get(key, userId.toString()); - return Convert.toLong(val); - } - - private static String formatKey(Long channelId) { - return String.format(CHANNEL_MESSAGE_READ, channelId); - } - -} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java deleted file mode 100644 index 5c76f79..0000000 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/redis/message/ImGroupMessageReadRedisDAO.java +++ /dev/null @@ -1,102 +0,0 @@ -package cn.iocoder.yudao.module.im.dal.redis.message; - -import cn.hutool.core.convert.Convert; -import jakarta.annotation.Resource; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Repository; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MESSAGE_READ; - -/** - * IM 群消息已读位置 Redis DAO - * - * @author 芋道源码 - */ -@Repository -public class ImGroupMessageReadRedisDAO { - - @Resource - private StringRedisTemplate stringRedisTemplate; - - /** - * 更新用户在某群的最大已读消息编号 - * - * @param groupId 群编号 - * @param userId 用户编号 - * @param maxMessageId 最大已读消息编号 - */ - public void updateReadMaxMessageId(Long groupId, Long userId, Long maxMessageId) { - String key = formatKey(groupId); - stringRedisTemplate.opsForHash().put(key, userId.toString(), maxMessageId.toString()); - } - - /** - * 获取用户在某群的最大已读消息编号 - * - * @param groupId 群编号 - * @param userId 用户编号 - * @return 最大已读消息编号,不存在则返回 null - */ - public Long getReadMaxMessageId(Long groupId, Long userId) { - String key = formatKey(groupId); - Object val = stringRedisTemplate.opsForHash().get(key, userId.toString()); - return Convert.toLong(val); - } - - /** - * 获取某群所有用户的已读位置 - * - * @param groupId 群编号 - * @return userId → maxReadMessageId 映射 - */ - public Map getReadMaxMessageIdMap(Long groupId) { - String key = formatKey(groupId); - Map entries = stringRedisTemplate.opsForHash().entries(key); - // 转换为 Long → Long 的 Map - Map result = new HashMap<>(entries.size()); - entries.forEach((k, v) -> result.put(Long.parseLong(k.toString()), Long.parseLong(v.toString()))); - return result; - } - - /** - * 删除用户在某群的已读位置 - * - * @param groupId 群编号 - * @param userId 用户编号 - */ - public void deleteReadMaxMessageId(Long groupId, Long userId) { - String key = formatKey(groupId); - stringRedisTemplate.opsForHash().delete(key, userId.toString()); - } - - /** - * 批量删除用户在某群的已读位置 - * - * @param groupId 群编号 - * @param userIds 用户编号集合 - */ - public void deleteReadMaxMessageIds(Long groupId, Collection userIds) { - String key = formatKey(groupId); - Object[] hashKeys = userIds.stream().map(String::valueOf).toArray(); - stringRedisTemplate.opsForHash().delete(key, hashKeys); - } - - /** - * 删除某群所有用户的已读位置(整个 Hash Key) - * - * @param groupId 群编号 - */ - public void deleteReadMaxMessageIdMap(Long groupId) { - String key = formatKey(groupId); - stringRedisTemplate.delete(key); - } - - private static String formatKey(Long groupId) { - return String.format(GROUP_MESSAGE_READ, groupId); - } - -} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java index d854306..2f68ad8 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImContentTypeEnum.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.enums.message; +package cn.iocoder.yudao.module.im.enums; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; @@ -11,13 +11,13 @@ import java.util.Arrays; import java.util.Set; /** - * IM 消息类型枚举 + * IM 内容类型枚举 * * @author 芋道源码 */ @Getter @RequiredArgsConstructor -public enum ImMessageTypeEnum implements ArrayValuable { +public enum ImContentTypeEnum implements ArrayValuable { // ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ========== /** @@ -128,10 +128,6 @@ public enum ImMessageTypeEnum implements ArrayValuable { RTC_CALL_END(1611, "通话结束", true, false), // ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ========== - // TODO @芋艿:FRIEND_REQUEST_* 与 GROUP_REQUEST_* 都是 persistent=false 的 SysMsg,离线 pull 拉不到, - // 目前可能丢失「实时 toast 提醒」体验(业务状态不丢,前端上线 fetch{Friend,Group}RequestList 能补回来); - // 未来思考下怎么优化:候选方案 1)改 persistent=true 入私聊消息流 + 让客户端按 type 自渲染; - // 2)服务端补一个「未读通知拉取」接口给前端冷启动调用。 /** * 对应 OpenIM:FriendApplicationApprovedNotification 1201 * 对应自己的类:FriendRequestApprovedNotification @@ -285,9 +281,9 @@ public enum ImMessageTypeEnum implements ArrayValuable { /** * 对应 OpenIM:sdkws.GroupMemberInfoSetTips(GroupMemberInfoSetNotification 1516,窄化到 displayUserName) * 对应自己的类:GroupMemberNicknameUpdateNotification - * 场景:成员修改自己在群里的昵称,全员广播;前端按 displayUserName 局部更新对应 member + * 场景:成员修改自己在群里的昵称,在线成员同步对应 member */ - GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", true, false), + GROUP_MEMBER_NICKNAME_UPDATE(1516, "成员昵称变更", false, false), /** * 对应 OpenIM:GroupMemberSetToAdminNotification 1517 * 对应自己的类:GroupAdminAddNotification @@ -339,7 +335,7 @@ public enum ImMessageTypeEnum implements ArrayValuable { */ GROUP_BANNED(1533, "群封禁变更", true, false); - public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImContentTypeEnum::getType).toArray(Integer[]::new); private static final Set FRIEND_NOTIFICATION_TYPES = CollUtil.newHashSet( FRIEND_REQUEST_APPROVED.type, @@ -396,8 +392,8 @@ public enum ImMessageTypeEnum implements ArrayValuable { * @param type 消息类型 * @return 枚举实例 */ - public static ImMessageTypeEnum validate(Integer type) { - ImMessageTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values()); + public static ImContentTypeEnum validate(Integer type) { + ImContentTypeEnum result = ArrayUtil.firstMatch(item -> item.type.equals(type), values()); Assert.notNull(result, "未注册的消息类型 type={}", type); return result; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java index 0f3f446..eb3c053 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ImConversationTypeEnum.java @@ -16,6 +16,7 @@ import java.util.Objects; @Getter public enum ImConversationTypeEnum implements ArrayValuable { + NONE(0, "无会话"), // 无会话 PRIVATE(1, "私聊"), // 私聊 GROUP(2, "群聊"), // 群聊 CHANNEL(3, "频道"); // 频道 / 公众号 @@ -36,6 +37,10 @@ public enum ImConversationTypeEnum implements ArrayValuable { return ARRAYS; } + public static boolean isNone(Integer type) { + return Objects.equals(NONE.type, type); + } + public static boolean isPrivate(Integer type) { return Objects.equals(PRIVATE.type, type); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImGroupMessageReceiptStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java index 256560c..41e3cf0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImGroupMessageReceiptStatusEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageReceiptStatusEnum.java @@ -7,19 +7,19 @@ import lombok.Getter; import java.util.Arrays; /** - * IM 群消息回执状态枚举 + * IM 消息回执状态枚举 * * @author 芋道源码 */ @Getter @AllArgsConstructor -public enum ImGroupMessageReceiptStatusEnum implements ArrayValuable { +public enum ImMessageReceiptStatusEnum implements ArrayValuable { NO_RECEIPT(0, "不需要回执"), PENDING(1, "待完成"), DONE(2, "已完成"); - public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImGroupMessageReceiptStatusEnum::getStatus).toArray(Integer[]::new); + public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageReceiptStatusEnum::getStatus).toArray(Integer[]::new); /** * 状态 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java index 79cb8d8..537f95c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageStatusEnum.java @@ -8,9 +8,6 @@ import java.util.Arrays; /** * IM 消息状态枚举 - *

- * 私聊:SENDING(-1, 仅客户端) / UNREAD(0) / RECALL(2) / READ(3) - * 群聊:SENDING(-1, 仅客户端) / UNREAD(0, 作为正常状态) / RECALL(2) * * @author 芋道源码 */ @@ -19,9 +16,8 @@ import java.util.Arrays; public enum ImMessageStatusEnum implements ArrayValuable { SENDING(-1, "发送中"), // 仅客户端使用 - UNREAD(0, "未读"), // 私聊=未读,群聊=正常(初始状态) - RECALL(2, "已撤回"), - READ(3, "已读"); // 仅私聊使用;群聊已读通过 Redis 已读位置实现 + NORMAL(0, "正常"), // 私聊 / 群聊的正常(初始)状态 + RECALL(2, "已撤回"); public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageStatusEnum::getStatus).toArray(Integer[]::new); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java index 3f3acc4..c4af81b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/rtc/ImRtcParticipantStatusEnum.java @@ -24,7 +24,6 @@ import java.util.Objects; @Getter public enum ImRtcParticipantStatusEnum implements ArrayValuable { - // TODO @芋艿:hand up 要不要也搞下。 INVITING(10, "邀请中"), // 已发出 invite,等被叫响应 JOINED(20, "已加入"), // 已 connect 进 LiveKit 房间 REJECTED(30, "已拒绝"), // 接通前点拒接 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java index b39ce13..13eeee6 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/mq/consumer/friend/AdminUserProfileUpdateConsumer.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendInfoUpdatedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendInfoUpdatedNotification; import cn.iocoder.yudao.module.system.api.message.user.AdminUserProfileUpdateMessage; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -52,8 +52,9 @@ public class AdminUserProfileUpdateConsumer { try { FriendInfoUpdatedNotification payload = (FriendInfoUpdatedNotification) new FriendInfoUpdatedNotification() .setOperatorUserId(userId).setFriendUserId(userId); - websocketService.sendPrivateMessageAsync(friend.getFriendUserId(), ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_INFO_UPDATED.getType(), userId, friend.getFriendUserId(), payload)); + websocketService.sendNotificationAsync(friend.getFriendUserId(), + ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_INFO_UPDATED.getType(), payload); successCount++; } catch (Exception e) { log.warn("[onMessage][userId({}) friendUserId({}) 推送失败]", diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java new file mode 100644 index 0000000..9c057ae --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadService.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.im.service.conversation; + +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * IM 会话读位置 Service 接口 + * + * @author 芋道源码 + */ +public interface ImConversationReadService { + + /** + * 更新用户在某会话的最大已读位置(单调递增,乱序 / 并发上报不会回退) + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @param readMessageId 已读到的最大消息编号 + * @return 读位置是否前进;true 时调用方才需要下发已读 / 回执事件 + */ + boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId); + + /** + * 获取用户在某会话的最大已读位置 + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @return 最大已读消息编号;不存在则返回 null + */ + Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId); + + /** + * 获取某会话内所有用户的读位置(用于群回执人数聚合) + * + * @param conversationType 会话类型 + * @param conversationId 会话编号 + * @return userId → 最大已读消息编号 + */ + Map getUserReadMessageIdMap(Integer conversationType, Long conversationId); + + /** + * 批量获取某用户在多个会话的读位置(用于频道批量读位置、重连后按活跃会话补偿) + * + * @param userId 用户编号 + * @param conversationType 会话类型 + * @param conversationIds 会话编号集合 + * @return conversationId → 最大已读消息编号 + */ + Map getConversationReadMessageIdMap(Long userId, Integer conversationType, Collection conversationIds); + + /** + * 增量拉取当前用户的会话读位置(重连 / 离线补偿:按 update_time + id 游标) + * + * @param userId 用户编号 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null + * @param limit 单次拉取条数 + * @return 会话读位置列表,按更新时间、id 正序 + */ + List pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java new file mode 100644 index 0000000..a22d616 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/conversation/ImConversationReadServiceImpl.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.im.service.conversation; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.dal.mysql.conversation.ImConversationReadMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * IM 会话读位置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class ImConversationReadServiceImpl implements ImConversationReadService { + + @Resource + private ImConversationReadMapper conversationReadMapper; + + @Override + public boolean updateConversationReadPosition(Long userId, Integer conversationType, Long conversationId, Long readMessageId) { + LocalDateTime now = LocalDateTime.now(); + // 1. 不存在则插入;并发下唯一键冲突,降级为回查 + CAS 更新 + ImConversationReadDO existing = conversationReadMapper.selectByUserIdAndConversation( + userId, conversationType, conversationId); + if (existing == null) { + try { + conversationReadMapper.insert(new ImConversationReadDO().setUserId(userId) + .setConversationType(conversationType).setTargetId(conversationId) + .setMessageId(readMessageId).setReadTime(now)); + return true; + } catch (DuplicateKeyException e) { + log.warn("[updateConversationReadPosition][userId({}) type({}) conversationId({}) 并发插入冲突,回查更新]", + userId, conversationType, conversationId); + existing = conversationReadMapper.selectByUserIdAndConversation(userId, conversationType, conversationId); + } + } + if (existing == null) { + return false; + } + + // 2. CAS 单调更新:mapper 内 WHERE message_id < ? 保证乱序 / 并发不回退;影响行数 > 0 即读位置前进 + return conversationReadMapper.updateReadMessageIdToLarger(existing.getId(), readMessageId, now) > 0; + } + + @Override + public Long getConversationReadMessageId(Long userId, Integer conversationType, Long conversationId) { + ImConversationReadDO read = conversationReadMapper.selectByUserIdAndConversation( + userId, conversationType, conversationId); + return read != null ? read.getMessageId() : null; + } + + @Override + public Map getUserReadMessageIdMap(Integer conversationType, Long conversationId) { + return convertMap(conversationReadMapper.selectListByConversation(conversationType, conversationId), + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId); + } + + @Override + public Map getConversationReadMessageIdMap(Long userId, Integer conversationType, + Collection conversationIds) { + if (CollUtil.isEmpty(conversationIds)) { + return Collections.emptyMap(); + } + List list = conversationReadMapper.selectListByUserIdAndConversations( + userId, conversationType, conversationIds); + return convertMap(list, ImConversationReadDO::getTargetId, ImConversationReadDO::getMessageId); + } + + @Override + public List pullConversationReadList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return conversationReadMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java index a78bb38..8470206 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestService.java @@ -51,6 +51,17 @@ public interface ImFriendRequestService { List getMyFriendRequestList(Long userId, Long maxId, Integer limit); /** + * 增量拉取「我相关」的好友申请(重连 / 离线补偿:双向 OR,按 update_time + id 游标) + * + * @param userId 用户编号 + * @param lastUpdateTime 上次拉取到的最新更新时间(毫秒时间戳);首次拉取传 null + * @param lastId 上次拉取到的最后一条记录 id;首次拉取传 null + * @param limit 单次拉取条数 + * @return 申请记录列表,按更新时间、id 正序 + */ + List pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** * 按 id 单查申请记录;通用读接口,调用方自行做越权过滤 */ ImFriendRequestDO getFriendRequest(Long id); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java index d024e9d..eeed457 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendRequestMapper; import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestApprovedNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.FriendRequestRejectedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestApprovedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.FriendRequestRejectedNotification; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -105,8 +105,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { if (fromUser != null) { payload.setFromNickname(fromUser.getNickname()).setFromAvatar(fromUser.getAvatar()); } - websocketService.sendPrivateMessageAsync(toUserId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_REQUEST_RECEIVED.getType(), fromUserId, toUserId, payload)); + websocketService.sendNotificationAsync(toUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_RECEIVED.getType(), payload); // 4. 全局自动通过开关:注册 afterCommit 回调,事务提交后再走同意流程 // 回调内 try/catch 兜底 —— afterCommit 异常会被 Spring 静默吞掉,否则同意失败时申请方永远等不到 APPROVED @@ -172,8 +172,6 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { public void agreeFriendRequest(Long userId, Long requestId) { // 1.1 校验申请存在、未处理、操作人是接收方 ImFriendRequestDO request = validateRequestForHandle(userId, requestId); - // 1.2 复验双方用户有效 - adminUserApi.validateUserList(ListUtil.of(request.getFromUserId(), request.getToUserId())); // 2. 乐观锁更新申请处理结果 ImFriendRequestDO updateObj = new ImFriendRequestDO() @@ -192,8 +190,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { FriendRequestApprovedNotification payload = (FriendRequestApprovedNotification) new FriendRequestApprovedNotification().setRequestId(request.getId()) .setOperatorUserId(userId).setFriendUserId(userId); - websocketService.sendPrivateMessageAsync(request.getFromUserId(), ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_REQUEST_APPROVED.getType(), userId, request.getFromUserId(), payload)); + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_APPROVED.getType(), payload); } @Override @@ -217,8 +215,8 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { new FriendRequestRejectedNotification().setRequestId(request.getId()) .setHandleContent(handleContent) .setOperatorUserId(userId).setFriendUserId(userId); - websocketService.sendPrivateMessageAsync(request.getFromUserId(), ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_REQUEST_REJECTED.getType(), userId, request.getFromUserId(), payload)); + websocketService.sendNotificationAsync(request.getFromUserId(), ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_REQUEST_REJECTED.getType(), payload); } @Override @@ -234,6 +232,11 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { } @Override + public List pullFriendRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return friendRequestMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + + @Override public ImFriendRequestDO getFriendRequest(Long id) { return friendRequestMapper.selectById(id); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java index 84a6de9..1d1fc57 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java @@ -43,6 +43,11 @@ public interface ImFriendService { List getFriendList(Long userId); /** + * 增量拉取当前用户的好友关系(重连 / 离线补偿:含已删除,按 update_time + id 游标) + */ + List pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** * 获得当前用户的有效好友列表(仅 ENABLE 状态) */ List getEnableFriendList(Long userId); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java index 1919aae..f9dd452 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendRequestDO; import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.*; +import cn.iocoder.yudao.module.im.service.websocket.notification.friend.*; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; @@ -99,6 +99,11 @@ public class ImFriendServiceImpl implements ImFriendService { } @Override + public List pullFriendList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + return friendMapper.selectPullListByUserId(userId, lastUpdateTime, lastId, limit); + } + + @Override public List getEnableFriendList(Long userId) { return friendMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); } @@ -155,8 +160,8 @@ public class ImFriendServiceImpl implements ImFriendService { FriendUpdateNotification payload = (FriendUpdateNotification) new FriendUpdateNotification() .setDisplayName(reqVO.getDisplayName()).setSilent(reqVO.getSilent()).setPinned(reqVO.getPinned()) .setOperatorUserId(userId).setFriendUserId(reqVO.getFriendUserId()); - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_UPDATE.getType(), userId, userId, payload)); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_UPDATE.getType(), payload); } @Override @@ -178,7 +183,7 @@ public class ImFriendServiceImpl implements ImFriendService { FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() .setOperatorUserId(fromUserId).setFriendUserId(toUserId); privateMessageService.sendPrivateMessage(fromUserId, new ImPrivateMessageSendDTO() - .setReceiverId(toUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()).setContent(payload)); + .setReceiverId(toUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType()).setContent(payload)); } @Override @@ -197,7 +202,7 @@ public class ImFriendServiceImpl implements ImFriendService { FriendAddNotification payload = (FriendAddNotification) new FriendAddNotification() .setOperatorUserId(friendUserId).setFriendUserId(friendUserId); privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() - .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_ADD.getType()) + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_ADD.getType()) .setContent(payload).setPersistent(false)); } @@ -219,7 +224,7 @@ public class ImFriendServiceImpl implements ImFriendService { FriendDeleteNotification payload = ((FriendDeleteNotification) new FriendDeleteNotification() .setOperatorUserId(userId).setFriendUserId(friendUserId)).setClear(clear); privateMessageService.sendPrivateMessage(userId, new ImPrivateMessageSendDTO() - .setReceiverId(friendUserId).setType(ImMessageTypeEnum.FRIEND_DELETE.getType()) + .setReceiverId(friendUserId).setType(ImContentTypeEnum.FRIEND_DELETE.getType()) .setContent(payload).setPersistent(false)); } @@ -245,8 +250,8 @@ public class ImFriendServiceImpl implements ImFriendService { // 3. 推 FRIEND_BLOCK 给 A 多端 FriendBlockNotification payload = (FriendBlockNotification) new FriendBlockNotification() .setOperatorUserId(userId).setFriendUserId(friendUserId); - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_BLOCK.getType(), userId, userId, payload)); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_BLOCK.getType(), payload); } @Override @@ -271,8 +276,8 @@ public class ImFriendServiceImpl implements ImFriendService { // 3. 推 FRIEND_UNBLOCK 给 A 多端 FriendUnblockNotification payload = (FriendUnblockNotification) new FriendUnblockNotification() .setOperatorUserId(userId).setFriendUserId(friendUserId); - websocketService.sendPrivateMessageAsync(userId, ImPrivateMessageDTO.ofFriendNotification( - ImMessageTypeEnum.FRIEND_UNBLOCK.getType(), userId, userId, payload)); + websocketService.sendNotificationAsync(userId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.FRIEND_UNBLOCK.getType(), payload); } /** @@ -294,7 +299,8 @@ public class ImFriendServiceImpl implements ImFriendService { } // 情况二:复用 DISABLE 旧记录 → 恢复 ENABLE + 重置 silent / pinned / blocked,对齐"重新加好友"语义 if (exists != null) { - friendMapper.updateReAddFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), LocalDateTime.now(), + LocalDateTime now = LocalDateTime.now(); + friendMapper.updateReAddFields(exists.getId(), CommonStatusEnum.ENABLE.getStatus(), now, now, false, false, false, displayName, addSource); return; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java index d9a8000..a99c0f3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java @@ -87,13 +87,12 @@ public interface ImGroupMemberService { List getActiveGroupMemberListByUserId(Long userId); /** - * 查询用户已退群的群成员记录(DISABLE 状态) + * 查询用户曾经加入的所有群成员记录(含已退群) * - * @param userId 用户编号,必传 - * @param minQuitTime 最早退群时间(含),可空 - * @return 已退群成员记录列表 + * @param userId 用户编号 + * @return 群成员记录列表 */ - List getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime); + List getGroupMemberListByUserId(Long userId); /** * 添加群成员(入群),角色默认 MEMBER diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java index ed9e1bb..c712a0b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -77,7 +77,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @Override public List getGroupMemberListByOwnerAndAdmin(Long groupId) { - // TODO DONE @AI:把条件往下传;这样减少加载数据量! return groupMemberMapper.selectListByGroupIdAndStatusAndRoles(groupId, CommonStatusEnum.ENABLE.getStatus(), ListUtil.of(ImGroupMemberRoleEnum.OWNER.getRole(), ImGroupMemberRoleEnum.ADMIN.getRole())); } @@ -106,8 +105,8 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { } @Override - public List getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) { - return groupMemberMapper.selectQuitListByUserId(userId, minQuitTime); + public List getGroupMemberListByUserId(Long userId) { + return groupMemberMapper.selectListByUserId(userId); } @Override @@ -259,7 +258,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { .setId(member.getId()); groupMemberMapper.updateById(updateObj); - // 3.1 displayUserName 是公开字段,单独走 GROUP_MEMBER_NICKNAME_UPDATE 广播给全员;空串视为「清空昵称」也要广播;与旧值相同跳过 + // 3.1 displayUserName 是公开字段,单独走 GROUP_MEMBER_NICKNAME_UPDATE 在线同步给全员;空串视为「清空昵称」也要同步;与旧值相同跳过 if (updateReqVO.getDisplayUserName() != null && ObjUtil.notEqual(updateReqVO.getDisplayUserName(), member.getDisplayUserName())) { groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate( diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java index bd604f6..1c15d9b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestService.java @@ -64,6 +64,19 @@ public interface ImGroupRequestService { List getUnhandledRequestListByOwnerOrAdmin(Long userId); /** + * 增量拉取「我管理的群」下的加群申请(重连 / 离线补偿:含已处理,按 update_time + id 游标) + *

+ * 作用域与 {@link #getUnhandledRequestListByOwnerOrAdmin} 一致:按 ImGroupMember.role 取我作为 OWNER / ADMIN 的群 + * + * @param userId 当前用户编号 + * @param lastUpdateTime 游标:上次拉取的最后一条更新时间戳(毫秒) + * @param lastId 游标:上次拉取的最后一条申请编号 + * @param limit 单次拉取条数 + * @return 申请记录列表 + */ + List pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit); + + /** * 拉取指定群下的全部加群申请(含已处理);仅群主 / 管理员可查 *

* 用于群「进群申请」子页:最新一条卡片化突出 + 历史申请按 id 倒序 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java index 71d3d76..40d1cad 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.BaseGroupNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestApprovedNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestReceivedNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.GroupRequestRejectedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.BaseGroupNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestApprovedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestReceivedNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.GroupRequestRejectedNotification; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -97,12 +97,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { // 3. 情况二:群开启了审批,创建或复用一条主动申请记录 ImGroupRequestDO request = createOrResetApplyRequest(groupId, userId, reqVO); - // 4. 1503 私聊定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像 + // 4. 1503 定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像 AdminUserRespDTO applyUser = adminUserApi.getUser(userId); GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); for (Long receiverUserId : getGroupMemberListByOwnerAndAdminUserIds(group)) { - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification( - ImMessageTypeEnum.GROUP_REQUEST_RECEIVED.getType(), userId, receiverUserId, payload)); + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload); } return request; } @@ -139,12 +139,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { groupMemberService.addGroupMember(request.getGroupId(), request.getUserId(), ImGroupMemberRoleEnum.NORMAL.getRole(), request.getAddSource(), request.getInviterUserId()); - // 5.1 1505 私聊推送给申请人 + 群主 + 全部管理员(每端单推) + // 5.1 1505 定向推送给申请人 + 群主 + 全部管理员(每端单推) GroupRequestApprovedNotification payload = (GroupRequestApprovedNotification) new GroupRequestApprovedNotification() .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) .setOperatorUserId(userId); broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, - ImMessageTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId); + ImContentTypeEnum.GROUP_REQUEST_APPROVED.getType(), userId); // 5.2 群事件:主动申请 → 1510 自由进群;被邀请 → 1509 成员加入 if (request.getInviterUserId() == null) { groupMessageService.sendGroupMessage(userId, @@ -174,12 +174,12 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { throw exception(GROUP_REQUEST_HANDLED); } - // 3. 1506 私聊推送给申请人 + 群主 + 全部管理员 + // 3. 1506 定向推送给申请人 + 群主 + 全部管理员 GroupRequestRejectedNotification payload = (GroupRequestRejectedNotification) new GroupRequestRejectedNotification() .setRequestId(request.getId()).setGroupId(request.getGroupId()).setUserId(request.getUserId()) .setHandleContent(handleContent).setOperatorUserId(userId); broadcastToOwnerAdminsAndApplicant(request.getGroupId(), request.getUserId(), payload, - ImMessageTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId); + ImContentTypeEnum.GROUP_REQUEST_REJECTED.getType(), userId); } @Override @@ -201,8 +201,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { AdminUserRespDTO applyUser = userMap.get(request.getUserId()); GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); for (Long receiverUserId : ownerAndAdmins) { - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification( - ImMessageTypeEnum.GROUP_REQUEST_RECEIVED.getType(), inviterUserId, receiverUserId, payload)); + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.GROUP_REQUEST_RECEIVED.getType(), payload); } } } @@ -212,8 +212,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); Set ownerOrAdminGroupIds = convertSet(myMembers, - ImGroupMemberDO::getGroupId, - m -> ImGroupMemberRoleEnum.isOwnerOrAdmin(m.getRole())); + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())); if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { return Collections.emptyList(); } @@ -223,6 +222,19 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } @Override + public List pullGroupRequestList(Long userId, Long lastUpdateTime, Long lastId, Integer limit) { + // 1. 找出当前用户作为 OWNER / ADMIN 的所有群 + List myMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); + Set ownerOrAdminGroupIds = convertSet(myMembers, + ImGroupMemberDO::getGroupId, member -> ImGroupMemberRoleEnum.isOwnerOrAdmin(member.getRole())); + if (CollUtil.isEmpty(ownerOrAdminGroupIds)) { + return Collections.emptyList(); + } + // 2. 按游标增量拉取这些群下的申请 + return groupRequestMapper.selectPullListByGroupIds(ownerOrAdminGroupIds, lastUpdateTime, lastId, limit); + } + + @Override public List getGroupRequestListByGroupId(Long userId, Long groupId) { // 1. 校验群存在 + 当前用户是群主 / 管理员 groupService.validateGroupExists(groupId); @@ -383,7 +395,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { } /** - * 1505 / 1506 受众:申请人 + 群主 + 全部管理员;每端单独推一帧,前端按 receiver 是否申请人区分文案 + * 1505 / 1506 受众:申请人 + 群主 + 全部管理员 */ private void broadcastToOwnerAdminsAndApplicant(Long groupId, Long applicantUserId, BaseGroupNotification payload, Integer messageType, Long operatorUserId) { @@ -394,8 +406,8 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { Set receivers = new LinkedHashSet<>(getGroupMemberListByOwnerAndAdminUserIds(group)); receivers.add(applicantUserId); for (Long receiverUserId : receivers) { - websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification( - messageType, operatorUserId, receiverUserId, payload)); + websocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + messageType, payload); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java index 61f0273..93fa996 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; @@ -149,7 +149,9 @@ public class ImGroupServiceImpl implements ImGroupService { boolean nameChanged = StrUtil.isNotEmpty(updateReqVO.getName()); boolean noticeChanged = updateReqVO.getNotice() != null; boolean avatarChanged = StrUtil.isNotEmpty(updateReqVO.getAvatar()); - if (nameChanged || noticeChanged || avatarChanged) { + boolean joinApprovalChanged = updateReqVO.getJoinApproval() != null + && ObjUtil.notEqual(group.getJoinApproval(), updateReqVO.getJoinApproval()); + if (nameChanged || noticeChanged || avatarChanged || joinApprovalChanged) { List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(groupId); if (nameChanged) { groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupNameUpdate( @@ -161,7 +163,11 @@ public class ImGroupServiceImpl implements ImGroupService { } if (avatarChanged) { groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate( - groupId, userId, group.getAvatar(), updateReqVO.getAvatar())); + groupId, userId, group.getAvatar(), updateReqVO.getAvatar(), null, null)); + } + if (joinApprovalChanged) { + groupMessageService.sendGroupMessage(userId, memberUserIds, ImGroupMessageSendDTO.ofGroupInfoUpdate( + groupId, userId, null, null, group.getJoinApproval(), updateReqVO.getJoinApproval())); } } @@ -193,8 +199,6 @@ public class ImGroupServiceImpl implements ImGroupService { .setStatus(CommonStatusEnum.DISABLE.getStatus()).setDissolvedTime(LocalDateTime.now())); // 2.2 移除全部群成员 groupMemberService.removeGroupMembersByGroupId(id); - // 2.3 清理已读缓存 - groupMessageService.deleteReadMaxMessageIdMap(id); } // ==================== 群成员的写操作 ==================== @@ -267,10 +271,8 @@ public class ImGroupServiceImpl implements ImGroupService { // 2. 先发广播,后移成员(见类 javadoc) groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberQuit(groupId, userId)); - // 3.1 移除群成员 + // 3. 移除群成员 groupMemberService.removeGroupMember(groupId, userId); - // 3.2 清理已读缓存 - groupMessageService.deleteReadMaxMessageId(groupId, userId); } @Override @@ -307,10 +309,8 @@ public class ImGroupServiceImpl implements ImGroupService { groupMessageService.sendGroupMessage(userId, ImGroupMessageSendDTO.ofGroupMemberKick(groupId, userId, validTargetUserIds)); - // 3.1 批量移除群成员 + // 3. 批量移除群成员;不清理读位置(保留退群前历史已读,供离线补偿) groupMemberService.removeGroupMembers(groupId, validTargetUserIds); - // 3.2 批量清理已读缓存 - groupMessageService.deleteReadMaxMessageIds(groupId, validTargetUserIds); } @Override @@ -442,7 +442,7 @@ public class ImGroupServiceImpl implements ImGroupService { if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { throw exception(MESSAGE_NOT_IN_GROUP); } - if (!ImMessageTypeEnum.validate(message.getType()).isNormal() + if (!ImContentTypeEnum.validate(message.getType()).isNormal() || ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { throw exception(MESSAGE_NOT_IN_GROUP); } @@ -601,11 +601,8 @@ public class ImGroupServiceImpl implements ImGroupService { @Override public List getMyGroupList(Long userId) { - // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) - List members = groupMemberService.getActiveGroupMemberListByUserId(userId); - // 1.2 再查最近 N 天(与群消息离线拉取窗口一致)内退群的成员记录(退群前可能有离线消息需要展示,一并返回作为前端缓存) - LocalDateTime minQuitTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); - members.addAll(groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime)); + // 1. 查用户曾经加入的所有群(含退群,前端按需过滤);退群前的离线消息也要能展示 + List members = groupMemberService.getGroupMemberListByUserId(userId); if (CollUtil.isEmpty(members)) { return Collections.emptyList(); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java index 8b05161..b1e00ee 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java @@ -27,7 +27,7 @@ public interface ImChannelMessageService { * @param size 返回条数 * @return 频道消息列表;按 id 升序 */ - List getMessageListForPull(Long userId, Long minId, Integer size); + List pullChannelMessageList(Long userId, Long minId, Integer size); /** * 上报频道消息已读位置;同步推 READ 事件给自己多端 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java index cbdf710..fc9ad8e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java +++ b/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; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; 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 import cn.iocoder.yudao.module.im.dal.dataobject.channel.ImChannelMaterialDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; -import cn.iocoder.yudao.module.im.dal.redis.message.ImChannelMessageReadRedisDAO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.MaterialMessage; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImChannelMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.MaterialMessage; import jakarta.annotation.Resource; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -25,7 +28,6 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,39 +52,41 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { private ImWebSocketService webSocketService; @Resource - private ImChannelMessageReadRedisDAO channelMessageReadRedisDAO; + private ImConversationReadService conversationReadService; // ==================== 用户端 ==================== @Override - public List getMessageListForPull(Long userId, Long minId, Integer size) { + public List pullChannelMessageList(Long userId, Long minId, Integer size) { return channelMessageMapper.selectListByUserAndMinId(userId, minId, size); } @Override public Map getChannelReadMaxMessageIdMap(Long userId, Collection channelIds) { - Map result = new HashMap<>(channelIds.size()); - for (Long channelId : channelIds) { - Long max = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId); - if (max != null) { - result.put(channelId, max); - } - } - return result; + return conversationReadService.getConversationReadMessageIdMap( + userId, ImConversationTypeEnum.CHANNEL.getType(), channelIds); } @Override public void readChannelMessages(Long userId, Long channelId, Long messageId) { Assert.notNull(channelId, "频道编号不能为空"); Assert.notNull(messageId, "已读消息编号不能为空"); - // 1. 已读位置未前进,直接返回 - Long prevMaxMessageId = channelMessageReadRedisDAO.getReadMaxMessageId(channelId, userId); - if (prevMaxMessageId != null && prevMaxMessageId >= messageId) { + // 1.1 校验消息真实存在且属于该频道,避免未来 / 伪造 messageId 污染读位置 + ImChannelMessageDO message = channelMessageMapper.selectById(messageId); + if (message == null || ObjUtil.notEqual(message.getChannelId(), channelId)) { + return; + } + // 1.2 定向消息校验对当前用户可见(receiver_user_ids 为空表示全员可见) + if (CollUtil.isNotEmpty(message.getReceiverUserIds()) && !message.getReceiverUserIds().contains(userId)) { return; } - // 2. 更新 Redis 频道已读位置 - channelMessageReadRedisDAO.updateReadMaxMessageId(channelId, userId, messageId); + // 2. 更新频道已读位置;读位置未前进则不推 + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.CHANNEL.getType(), channelId, messageId); + if (!advanced) { + return; + } // 3. 异步推 READ 事件给自己多端同步 getSelf().readChannelMessageEvent(userId, channelId, messageId); @@ -93,7 +97,8 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { */ @Async public void readChannelMessageEvent(Long userId, Long channelId, Long readId) { - webSocketService.sendChannelMessageAsync(userId, ImChannelMessageDTO.ofRead(channelId, readId)); + webSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.CHANNEL.getType(), + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofChannel(channelId, readId)); } private ImChannelMessageServiceImpl getSelf() { @@ -113,15 +118,16 @@ public class ImChannelMessageServiceImpl implements ImChannelMessageService { String payloadJson = JsonUtils.toJsonString(payload); // 2.2 落库 1 行 message;reqVO 同名字段(materialId / receiverUserIds)自动拷贝,剩余字段补 set ImChannelMessageDO message = BeanUtils.toBean(reqVO, ImChannelMessageDO.class).setChannelId(material.getChannelId()) - .setType(ImMessageTypeEnum.MATERIAL.getType()).setContent(payloadJson).setSendTime(LocalDateTime.now()); + .setType(ImContentTypeEnum.MATERIAL.getType()).setContent(payloadJson).setSendTime(LocalDateTime.now()); channelMessageMapper.insert(message); // 3. 异步推 WebSocket:指定用户走点对点;全员(receiverUserIds 为空)走广播 - ImChannelMessageDTO dto = ImChannelMessageDTO.ofSend(message); + ImChannelMessageNotification dto = ImChannelMessageNotification.ofSend(message); if (CollUtil.isNotEmpty(reqVO.getReceiverUserIds())) { - webSocketService.sendChannelMessageAsync(reqVO.getReceiverUserIds(), dto); + webSocketService.sendNotificationAsync(reqVO.getReceiverUserIds(), ImConversationTypeEnum.CHANNEL.getType(), + dto.getType(), dto); } else { - webSocketService.broadcastChannelMessageAsync(dto); + webSocketService.broadcastNotificationAsync(ImConversationTypeEnum.CHANNEL.getType(), dto.getType(), dto); } return message.getId(); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java index 51a28f1..77426d1 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java @@ -98,35 +98,6 @@ public interface ImGroupMessageService { */ List getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO); - /** - * 清理用户在某群的已读位置缓存 - *

- * 用于成员退群场景 - * - * @param groupId 群编号 - * @param userId 用户编号 - */ - void deleteReadMaxMessageId(Long groupId, Long userId); - - /** - * 批量清理用户在某群的已读位置缓存 - *

- * 用于批量踢出场景 - * - * @param groupId 群编号 - * @param userIds 用户编号集合 - */ - void deleteReadMaxMessageIds(Long groupId, Collection userIds); - - /** - * 清理某群所有用户的已读位置缓存 - *

- * 用于群解散场景 - * - * @param groupId 群编号 - */ - void deleteReadMaxMessageIdMap(Long groupId); - // ==================== 管理后台 ==================== /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index 2ed32cd..12914fb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/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; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -17,23 +16,27 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; -import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; import cn.iocoder.yudao.module.im.util.ImMessageUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -41,7 +44,6 @@ import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -57,17 +59,8 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @Slf4j public class ImGroupMessageServiceImpl implements ImGroupMessageService { - /** - * 仅用于规避群消息 pull 的"假空页": - * 首批消息可能因入群时间或定向接收过滤后变成空列表,但后续更大的 id 仍然存在可见消息。 - * 因此仅在过滤结果为空时,按本轮消息最大 id 向后再试几次。 - */ - private static final int PULL_GROUP_MESSAGE_EMPTY_RETRY_TIMES = 3; - @Resource private ImGroupMessageMapper groupMessageMapper; - @Resource - private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO; @Resource private ImGroupService groupService; @@ -75,6 +68,8 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { private ImGroupMemberService groupMemberService; @Resource private ImSensitiveWordService sensitiveWordService; + @Resource + private ImConversationReadService conversationReadService; @Resource private ImWebSocketService imWebSocketService; @@ -100,34 +95,44 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { // 1.4 禁言校验 validateMuteStatus(group, senderMember); // 1.5 文本消息敏感词过滤 - if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) { sensitiveWordService.validateText(reqVO.getContent()); } - // 2.1 引用 quote 消息规范化 - reqVO.setContent(normalizeQuoteContent(reqVO, senderMember)); - // 2.2 构建并保存消息 + // 2.1 固化发送当时可见成员快照:用户发送均为全员广播;getReceiverUserIds 兜底纳入发送者,钉死发送者必可见 + List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(reqVO.getGroupId()); + Set receiverUserIds = getReceiverUserIds(null, senderId, memberUserIds); + // 2.2 引用 quote 消息规范化(含可见性子集校验,防止定向消息内容被广播引用泄漏) + reqVO.setContent(normalizeQuoteContent(reqVO, senderMember, receiverUserIds)); + // 2.3 构建并保存消息;唯一键冲突时回查已存在消息返回 ImGroupMessageDO message = BeanUtils.toBean(reqVO, ImGroupMessageDO.class, m -> m - .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()) + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now()) + .setReceiverUserIds(new ArrayList<>(receiverUserIds)) .setReceiptStatus(resolveReceiptStatus(reqVO.getReceipt()))); - groupMessageMapper.insert(message); + try { + groupMessageMapper.insert(message); + } catch (DuplicateKeyException e) { + log.warn("[sendGroupMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]", + senderId, reqVO.getClientMessageId()); + return groupMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId()); + } - // 3. WebSocket 异步推送(群内可见成员 + 发送方多端同步) - List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(message.getGroupId()); - Set targetUserIds = getVisibleUserIds(message.getReceiverUserIds(), senderId, memberUserIds); - imWebSocketService.sendGroupMessageAsync(targetUserIds, ImGroupMessageDTO.ofSend(message)); + // 3. WebSocket 异步推送(快照内可见成员 + 发送方多端同步) + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(), + notification.getType(), notification); return message; } @Override public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendDTO dto) { List memberUserIds = groupMemberService.getActiveGroupMemberUserIdsByGroupId(dto.getGroupId()); - Set targetUserIds = getVisibleUserIds(dto.getReceiverUserIds(), senderId, memberUserIds); - return sendGroupMessage(senderId, targetUserIds, dto); + Set receiverUserIds = getReceiverUserIds(dto.getReceiverUserIds(), senderId, memberUserIds); + return sendGroupMessage(senderId, receiverUserIds, dto); } @Override - public ImGroupMessageDO sendGroupMessage(Long senderId, Collection targetUserIds, ImGroupMessageSendDTO dto) { + public ImGroupMessageDO sendGroupMessage(Long senderId, Collection receiverUserIds, ImGroupMessageSendDTO dto) { // 1.1 content 序列化:null / String 透传,POJO 走 JSON Object payload = dto.getContent(); String contentString = payload == null || payload instanceof String @@ -137,16 +142,18 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { ImGroupMessageDO message = new ImGroupMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) .setSenderId(senderId).setGroupId(dto.getGroupId()) .setType(dto.getType()).setContent(contentString) - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()) - .setAtUserIds(dto.getAtUserIds()).setReceiverUserIds(dto.getReceiverUserIds()) + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()).setSendTime(LocalDateTime.now()) + .setAtUserIds(dto.getAtUserIds()).setReceiverUserIds(new ArrayList<>(receiverUserIds)) .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())); // 1.3 按 type.persistent 决定是否入库 - if (ImMessageTypeEnum.validate(dto.getType()).isPersistent()) { + if (ImContentTypeEnum.validate(dto.getType()).isPersistent()) { groupMessageMapper.insert(message); } // 2. WebSocket 异步推送 - imWebSocketService.sendGroupMessageAsync(targetUserIds, ImGroupMessageDTO.ofSend(message)); + ImGroupMessageNotification notification = ImGroupMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(receiverUserIds, ImConversationTypeEnum.GROUP.getType(), + notification.getType(), notification); return message; } @@ -180,7 +187,7 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { // 3. 发送撤回事件 return sendGroupMessage(userId, new ImGroupMessageSendDTO().setGroupId(message.getGroupId()) - .setType(ImMessageTypeEnum.RECALL.getType()) + .setType(ImContentTypeEnum.RECALL.getType()) .setReceiverUserIds(message.getReceiverUserIds()) .setContent(new RecallMessage().setMessageId(messageId))); } @@ -195,155 +202,53 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { // 0. 拉取时间窗;超过窗口的老消息不再通过离线通道推送 LocalDateTime minSendTime = LocalDateTime.now().minusDays(imProperties.getMessage().getGroupPullMaxDays()); - // 1.1 主查询:仅用"当前仍在群"的成员记录驱动;若首批消息过滤后为空,则允许内部重试 - List activeMembers = groupMemberService.getActiveGroupMemberListByUserId(userId); - Map memberMap = convertMap(activeMembers, ImGroupMemberDO::getGroupId); - List messages = new ArrayList<>(); - if (CollUtil.isNotEmpty(activeMembers)) { - List groupIds = convertList(activeMembers, ImGroupMemberDO::getGroupId); - messages.addAll(pullActiveGroupMessageList(userId, groupIds, minId, size, minSendTime, memberMap)); - } - // 1.2 补齐"退群前"的消息: - // - 继续基于用户原始 minId 单次查询,不能被主查询内部探测游标带着前进; - // - 若 minId > 0 且能查到对应消息,则进一步把下限抬到该消息的 sendTime,避免把客户端已拥有的老消息再次推送。 - messages.addAll(pullQuitGroupMessageList(userId, minId, size, minSendTime, memberMap)); - - // 2. 合并后统一过滤,得到当前用户可见的结果 - List result = filterGroupMessageList(messages, memberMap, userId, size); - - // 3. 按当前用户补齐:消息已读态(相对 Redis 已读游标)、本人发送的回执消息的已读人数 - appendMessageStatusAndReceipt(userId, result); - log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", userId, minId, size, result.size()); - return result; - } - - /** - * 拉取当前仍在群的主路径消息。 - * - * 仅当首批消息过滤后仍无可见消息时,才按消息最大 id 继续向后探测, - * 直到命中可见消息或确认该来源已耗尽。 - */ - private List pullActiveGroupMessageList(Long userId, List groupIds, Long minId, - Integer size, LocalDateTime minSendTime, - Map memberMap) { - // 1. 主查询内部探测游标:仅用于向后探测,不代表客户端真实已送达边界 - Long activeMinId = minId; - for (int retryCount = 0; retryCount <= PULL_GROUP_MESSAGE_EMPTY_RETRY_TIMES; retryCount++) { - // 2. 查询本轮消息;若已无更多消息,则当前主路径直接结束 - List messages = groupMessageMapper.selectListByMinId(groupIds, activeMinId, - minSendTime, size); - if (CollUtil.isEmpty(messages)) { - return Collections.emptyList(); - } - boolean hasVisibleMessage = CollUtil.anyMatch(messages, - message -> isMessageVisible(message, memberMap.get(message.getGroupId()), userId)); - boolean sourceExhausted = messages.size() < size; - // 3. 本轮已命中可见消息,或主查询来源已耗尽,直接返回这一轮消息 - if (hasVisibleMessage || sourceExhausted) { - return messages; - } - - // 4. 按本轮消息最大 id 推进内部游标,跳过这段不可见区间;若游标未前进则直接停止 - Long maxMessageId = getMaxValue(messages, ImGroupMessageDO::getId); - if (maxMessageId == null || maxMessageId <= activeMinId) { - return Collections.emptyList(); - } - activeMinId = maxMessageId; + // 1. 候选群 = 用户曾经加入的所有群(含退群);可见性与时间窗都交给 SQL,退群群最多贡献 0 条 + Set groupIds = convertSet(groupMemberService.getGroupMemberListByUserId(userId), + ImGroupMemberDO::getGroupId); + if (CollUtil.isEmpty(groupIds)) { + return Collections.emptyList(); } - return Collections.emptyList(); - } - /** - * 拉取已离开群路径的消息(退群前消息) - */ - private List pullQuitGroupMessageList(Long userId, Long minId, Integer size, - LocalDateTime minSendTime, - Map memberMap) { - // 1. 退群补齐始终基于用户原始 minId 计算时间边界,避免被主查询内部重试游标误伤 - LocalDateTime minQuitTime = minSendTime; - if (minId != null && minId > 0) { - ImGroupMessageDO minMessage = groupMessageMapper.selectById(minId); - if (minMessage != null && minMessage.getSendTime() != null - && minMessage.getSendTime().isAfter(minSendTime)) { - minQuitTime = minMessage.getSendTime(); - } - } - // 2. 查询用户离开的群记录;若原始 minId 对应消息仍在窗口内,则用它的发送时间抬升退群筛选下限 - List messages = new ArrayList<>(); - List quitMembers = groupMemberService.getQuitGroupMemberListByUserId(userId, minQuitTime); - for (ImGroupMemberDO quitMember : quitMembers) { - // 3. 按原始 minId + 退群时间补齐该群退群前消息,并把成员记录写回 memberMap 供统一可见性过滤使用 - List quitGroupMessages = groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore( - quitMember.getGroupId(), minId, minSendTime, quitMember.getQuitTime(), size); - if (CollUtil.isEmpty(quitGroupMessages)) { - continue; - } - messages.addAll(quitGroupMessages); - memberMap.put(quitMember.getGroupId(), quitMember); - } - return messages; - } + // 2. 按 receiver_user_ids 快照过滤可见性,结果按 id 升序 + List messages = groupMessageMapper.selectListByMinId(userId, groupIds, minId, minSendTime, size); - /** - * 过滤一批原始群消息,得到当前用户可见的返回结果 - */ - private List filterGroupMessageList(List messages, - Map memberMap, - Long userId, Integer size) { - // 按可见性过滤(入群前不可见、定向消息排除),按 id 升序后仅取本页 size 条, - // 避免「在群 + 退群前」多路合并时一次响应跨度过大、游标直接跳到全局最大 id 而漏拉中间消息 - return messages.stream() - .filter(msg -> isMessageVisible(msg, memberMap.get(msg.getGroupId()), userId)) - .sorted(Comparator.comparing(ImGroupMessageDO::getId)) - .limit(size) - .collect(Collectors.toList()); + // 3. 补齐本人发送的回执消息的已读人数 + appendMessageReceipt(userId, messages); + log.info("[pullGroupMessageList][userId({}) minId({}) size({}) result({})]", userId, minId, size, messages.size()); + return messages; } /** - * 补全消息已读态和回执已读人数 - * - * 1. 消息已读态(status):根据 Redis 已读游标判断 READ / UNREAD - * 2. 回执已读人数(readCount):仅对本人发送的回执消息,计算可见成员中的已读人数 + * 补全本人发送消息的回执已读人数(readCount) + *

+ * 仅对本人发送、且需要回执(非 NO_RECEIPT)的消息,按 receiver_user_ids 快照 ∩ 当前有效成员 - 发送者 算已读人数 */ - @SuppressWarnings({"StatementWithEmptyBody", "DataFlowIssue"}) - private void appendMessageStatusAndReceipt(Long userId, List messages) { + @SuppressWarnings("DataFlowIssue") + private void appendMessageReceipt(Long userId, List messages) { if (CollUtil.isEmpty(messages)) { return; } - // 群已读关闭:不查 Redis 已读游标,status 保持 DB 原值(含撤回),readCount 不补齐 + // 群已读关闭:不补 readCount if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { return; } - Map readMaxMessageIdsByGroup = new HashMap<>(); // 群 → 已读位置 Map> readPositionsByGroup = new HashMap<>(); // 群 → (用户 → 已读位置) Map> membersByGroup = new HashMap<>(); // 群 → 全部成员列表 for (ImGroupMessageDO message : messages) { - // 消息已读态(status):撤回 > 已读 > 未读 - Long groupId = message.getGroupId(); - long readMaxMessageId = readMaxMessageIdsByGroup.computeIfAbsent(groupId, gid -> { - Long readMaxMsgId = groupMessageReadRedisDAO.getReadMaxMessageId(gid, userId); - return readMaxMsgId != null ? readMaxMsgId : -1L; - }); - if (ImMessageStatusEnum.RECALL.getStatus().equals(message.getStatus())) { - // 保持撤回态 - } else if (readMaxMessageId >= message.getId()) { - message.setStatus(ImMessageStatusEnum.READ.getStatus()); - } else { - message.setStatus(ImMessageStatusEnum.UNREAD.getStatus()); - } - - // 回执消息的已读人数(readCount):仅补齐本人发送的,其他消息不处理(回执消息才关心已读人数,且只对发送者可见) + // 仅补本人发送、且需要回执(非 NO_RECEIPT)的消息 if (ObjUtil.notEqual(message.getSenderId(), userId) - || ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus().equals(message.getReceiptStatus())) { + || ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus().equals(message.getReceiptStatus())) { continue; } + Long groupId = message.getGroupId(); Map positions = readPositionsByGroup.computeIfAbsent(groupId, - gid -> groupMessageReadRedisDAO.getReadMaxMessageIdMap(gid)); - List allMembers = membersByGroup.computeIfAbsent(groupId, - groupMemberService::getGroupMemberListByGroupId); - Set visibleUserIds = getVisibleUserIds(message, allMembers); - visibleUserIds.remove(message.getSenderId()); - int readCount = getSumValue(visibleUserIds, + gid -> conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), gid)); + // 应读分母取当前有效成员(剔除已退群),与异步回执刷新、前端已读弹层口径一致 + List activeMembers = membersByGroup.computeIfAbsent(groupId, + groupMemberService::getActiveGroupMemberListByGroupId); + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); + int readCount = getSumValue(receiverUserIds, uid -> positions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, Integer::sum, 0); message.setReadCount(readCount); @@ -357,25 +262,24 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { throw exception(MESSAGE_GROUP_READ_DISABLED); } Assert.notNull(messageId, "已读消息编号不能为空"); - // 1.1 获取群成员记录 - ImGroupMemberDO member = groupMemberService.getGroupMember(groupId, userId); - // 1.2 校验消息属于当前群,且对当前用户可见 + // 1. 校验消息属于当前群,且对当前用户可见(按快照) ImGroupMessageDO message = groupMessageMapper.selectById(messageId); if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId) - || !isMessageVisible(message, member, userId)) { + || !isMessageReceived(message, userId)) { throw exception(MESSAGE_NOT_IN_GROUP); } - // 2. 已读位置未前进,直接返回 - Long prevMaxMessageId = groupMessageReadRedisDAO.getReadMaxMessageId(groupId, userId); - if (prevMaxMessageId != null && prevMaxMessageId >= messageId) { + // 2. 取旧读位置(用于回执刷新区间),再更新群已读位置;读位置未前进则不推 + Long prevMaxMessageId = conversationReadService.getConversationReadMessageId( + userId, ImConversationTypeEnum.GROUP.getType(), groupId); + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.GROUP.getType(), groupId, messageId); + if (!advanced) { return; } - // 3. 更新 Redis 群已读位置 - groupMessageReadRedisDAO.updateReadMaxMessageId(groupId, userId, messageId); - // 4. 异步发送 READ 事件 + 刷新范围内的群回执 + // 3. 异步发送 READ 事件 + 刷新范围内的群回执 getSelf().readGroupMessageEvent(userId, groupId, prevMaxMessageId, messageId); } @@ -386,13 +290,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { throw exception(MESSAGE_GROUP_READ_DISABLED); } // 1.1 校验用户在群中(权限校验) - ImGroupMemberDO operator = groupMemberService.validateMemberInGroup(groupId, userId); + groupMemberService.validateMemberInGroup(groupId, userId); // 1.2 获取消息;并校验消息归属于该群、对调用者可见、调用者是发送方 ImGroupMessageDO message = groupMessageMapper.selectById(messageId); if (message == null || ObjUtil.notEqual(message.getGroupId(), groupId)) { return Collections.emptyList(); } - if (!isMessageVisible(message, operator, userId)) { + if (!isMessageReceived(message, userId)) { return Collections.emptyList(); } // 1.3 仅消息发送方关心已读人数;非发送方查询直接返回空 @@ -400,18 +304,19 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { return Collections.emptyList(); } - // 2. 获取所有成员和已读位置 - List allMembers = groupMemberService.getGroupMemberListByGroupId(groupId); - Map allPositions = groupMessageReadRedisDAO.getReadMaxMessageIdMap(groupId); + // 2. 获取当前有效成员和已读位置(剔除已退群,与回执分母口径一致) + List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); + Map allPositions = conversationReadService.getUserReadMessageIdMap( + ImConversationTypeEnum.GROUP.getType(), groupId); // 3. 计算该消息的可见成员集合(排除发送者自己) - Set visibleUserIds = getVisibleUserIds(message, allMembers); - visibleUserIds.remove(message.getSenderId()); + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); // 4. 只返回在可见范围内且已读位置 >= messageId 的用户 List readUserIds = new ArrayList<>(); allPositions.forEach((uid, readMaxMessageId) -> { - if (visibleUserIds.contains(uid) && readMaxMessageId >= messageId) { + if (receiverUserIds.contains(uid) && readMaxMessageId >= messageId) { readUserIds.add(uid); } }); @@ -420,15 +325,14 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @Override public List getGroupMessageList(Long userId, ImGroupMessageListReqVO reqVO) { - // 1. 校验用户在群中 - ImGroupMemberDO member = groupMemberService.validateMemberInGroup(reqVO.getGroupId(), userId); - - // 2. 查询历史消息(仅入群之后) - List messages = groupMessageMapper.selectHistoryList( - reqVO.getGroupId(), reqVO.getMaxId(), reqVO.getLimit(), member.getJoinTime()); + // 1. 校验用户曾经在群(当前在群或已退群),与 pull 退群窗口口径一致;内容仍由 receiver_user_ids 快照过滤 + if (groupMemberService.getGroupMember(reqVO.getGroupId(), userId) == null) { + throw exception(GROUP_MEMBER_NOT_IN_GROUP); + } - // 3. 过滤定向消息:仅保留当前用户可见的(receiverUserIds 为空 / 含当前用户 / 本人发送) - return filterList(messages, message -> isMessageVisible(message, member, userId)); + // 2. 查询历史消息:SQL 已按 receiver_user_ids 快照过滤当前用户可见 + return groupMessageMapper.selectHistoryListByUser( + userId, reqVO.getGroupId(), reqVO.getMaxId(), reqVO.getLimit()); } // ========== 异步 WebSocket 推送 ========== @@ -445,8 +349,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @SuppressWarnings("DataFlowIssue") public void readGroupMessageEvent(Long userId, Long groupId, Long prevMaxMessageId, Long newMaxMessageId) { // 1. 发送 READ 事件给自己的其他终端(多端同步) - imWebSocketService.sendGroupMessageAsync(userId, - ImGroupMessageDTO.ofRead(userId, groupId, newMaxMessageId)); + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.READ.getType(), + ImMessageReadNotification.ofGroup(userId, groupId, newMaxMessageId)); // 2. 刷新 (prevMaxMessageId, newMaxMessageId] 区间内的待回执消息 List pendingMessages = groupMessageMapper.selectListByGroupIdAndPendingReceipt( @@ -455,28 +360,30 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { return; } List activeMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); - Map allPositions = groupMessageReadRedisDAO.getReadMaxMessageIdMap(groupId); + Map allPositions = conversationReadService.getUserReadMessageIdMap( + ImConversationTypeEnum.GROUP.getType(), groupId); for (ImGroupMessageDO message : pendingMessages) { - // 2.1.1 统计可见成员中的已读人数 - Set visibleUserIds = getVisibleUserIds(message, activeMembers); - visibleUserIds.remove(message.getSenderId()); // 发送者自己不算已读 - if (CollUtil.isEmpty(visibleUserIds)) { + // 2.1.1 统计可见成员中的已读人数(应读分母 = 快照 ∩ 当前有效成员 - sender) + Set receiverUserIds = getReceiverUserIds(message, activeMembers); + receiverUserIds.remove(message.getSenderId()); // 发送者自己不算已读 + if (CollUtil.isEmpty(receiverUserIds)) { continue; } - int readCount = getSumValue(visibleUserIds, + int readCount = getSumValue(receiverUserIds, uid -> allPositions.getOrDefault(uid, -1L) >= message.getId() ? 1 : null, Integer::sum, 0); // 2.1.2 全部已读 → 标记回执完成 - Integer newReceiptStatus = ImGroupMessageReceiptStatusEnum.PENDING.getStatus(); - if (readCount >= visibleUserIds.size()) { - newReceiptStatus = ImGroupMessageReceiptStatusEnum.DONE.getStatus(); + Integer newReceiptStatus = ImMessageReceiptStatusEnum.PENDING.getStatus(); + if (readCount >= receiverUserIds.size()) { + newReceiptStatus = ImMessageReceiptStatusEnum.DONE.getStatus(); groupMessageMapper.updateById(new ImGroupMessageDO().setId(message.getId()) .setReceiptStatus(newReceiptStatus)); } // 2.2 发送 RECEIPT 事件给消息发送方(只有 ta 关心已读进度) - imWebSocketService.sendGroupMessageAsync(message.getSenderId(), - ImGroupMessageDTO.ofReceipt(message.getId(), groupId, readCount, newReceiptStatus)); + imWebSocketService.sendNotificationAsync(message.getSenderId(), ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.RECEIPT.getType(), + ImMessageReceiptNotification.ofGroup(message.getId(), groupId, readCount, newReceiptStatus)); } } @@ -487,9 +394,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { * * @param reqVO 发送请求 * @param senderMember 发送人成员 + * @param newAudience 新消息可见成员集合(发送当时快照) * @return 规范化后的 content */ - private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember) { + private String normalizeQuoteContent(ImGroupMessageSendReqVO reqVO, ImGroupMemberDO senderMember, + Set newAudience) { // 解析客户端 content 里的 quote.messageId Long quoteMessageId = ImMessageUtils.parseQuoteMessageId(reqVO.getContent()); @@ -508,12 +417,13 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { if (ObjUtil.notEqual(original.getGroupId(), reqVO.getGroupId())) { throw exception(MESSAGE_QUOTE_INVALID); } - // 拒绝定向消息(仅发送人可见的内容若被全员广播 quote.content,会泄漏给原本看不到的成员) - if (CollUtil.isNotEmpty(original.getReceiverUserIds())) { + // 校验对发送人可见(按快照) + if (!isMessageReceived(original, senderMember.getUserId())) { throw exception(MESSAGE_QUOTE_INVALID); } - // 校验对发送人可见(入群时间 / 退群时间) - if (!isMessageVisible(original, senderMember, senderMember.getUserId())) { + // 防泄漏:新消息可见集合必须是被引用消息可见集合的子集,否则禁止引用; + // 否则只对部分成员可见的定向消息内容,会随广播引用泄漏给原本看不到的成员 + if (!new HashSet<>(original.getReceiverUserIds()).containsAll(newAudience)) { throw exception(MESSAGE_QUOTE_INVALID); } // 构建 quote 对象并注入 content @@ -537,87 +447,48 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { } /** - * 判断一条群消息对某个群成员是否可见 + * 判断一条群消息是否被某个用户接收到 + *

+ * 接收范围以发送当时固化的 receiver_user_ids 快照为准;发送者也在快照内。 * - * @param msg 消息 - * @param member 群成员 - * @param userId 当前用户编号(用于定向消息过滤) - * @return 是否可见 + * @param message 消息 + * @param userId 当前用户编号 + * @return 是否接收到 */ - private boolean isMessageVisible(ImGroupMessageDO msg, ImGroupMemberDO member, Long userId) { - if (member == null) { - return false; - } - // 1. 入群时间晚于消息发送时间 → 不可见 - if (member.getJoinTime() != null && msg.getSendTime().isBefore(member.getJoinTime())) { - return false; - } - // 2. 已退群且退群时间早于消息发送时间 → 不可见 - if (CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus()) - && member.getQuitTime() != null && msg.getSendTime().isAfter(member.getQuitTime())) { - return false; - } - // 3.1 无定向接收列表 → 全员可见 - if (CollUtil.isEmpty(msg.getReceiverUserIds())) { - return true; - } - // 3.2 当前用户在定向列表中,或本人即发送者 → 可见 - return msg.getReceiverUserIds().contains(userId) - || ObjUtil.equal(msg.getSenderId(), userId); + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean isMessageReceived(ImGroupMessageDO message, Long userId) { + return CollUtil.contains(message.getReceiverUserIds(), userId); } /** - * 计算一条群消息的可见成员集合(含发送者) + * 计算一条群消息的可见成员集合(快照与传入成员的交集,含发送者) */ - private Set getVisibleUserIds(ImGroupMessageDO message, List members) { - return convertSet(members, ImGroupMemberDO::getUserId, - member -> isMessageVisible(message, member, member.getUserId())); + private Set getReceiverUserIds(ImGroupMessageDO message, List members) { + if (CollUtil.isEmpty(message.getReceiverUserIds())) { + return new HashSet<>(); + } + Set snapshot = new HashSet<>(message.getReceiverUserIds()); + return convertSet(members, ImGroupMemberDO::getUserId, member -> snapshot.contains(member.getUserId())); } /** - * 基于群成员 userId 列表,过滤出一条新消息的可见成员集合(含发送者)。 + * 基于群成员 userId 列表,计算一条新消息的可见成员快照(含发送者)。 *

- * 仅适用于「新消息」推送场景({@code sendTime = now}),不涉及 joinTime / quitTime 判定, - * 只应用 {@code receiverUserIds} 定向过滤;语义与 - * {@link #isMessageVisible(ImGroupMessageDO, ImGroupMemberDO, Long)} 的第 3 步保持一致。 + * 仅适用于「新消息」发送场景:无定向接收列表则全员可见,否则取定向用户与当前成员的交集; + * 发送者始终纳入快照,保证 receiver_user_ids 非空且发送方可见。 */ - private Set getVisibleUserIds(List receiverUserIds, Long senderId, Collection memberUserIds) { - if (CollUtil.isEmpty(memberUserIds)) { - return new HashSet<>(); - } - // 无定向接收列表 → 全员可见 - if (CollUtil.isEmpty(receiverUserIds)) { - return new HashSet<>(memberUserIds); - } - // 有定向接收列表 → 仅定向用户可见;发送者自己也能看到自己的消息(多端同步) - Set allowed = new HashSet<>(receiverUserIds); + private Set getReceiverUserIds(List directedUserIds, Long senderId, Collection memberUserIds) { + // 无定向接收列表 → 全员可见;否则取定向用户与当前成员的交集 + Set result = CollUtil.isEmpty(directedUserIds) + ? new HashSet<>(memberUserIds) + : new HashSet<>(CollUtil.intersection(memberUserIds, directedUserIds)); + // 发送者始终可见(多端同步),即便成员缓存暂未包含 if (senderId != null) { - allowed.add(senderId); - } - Set result = new HashSet<>(); - for (Long userId : memberUserIds) { - if (allowed.contains(userId)) { - result.add(userId); - } + result.add(senderId); } return result; } - @Override - public void deleteReadMaxMessageId(Long groupId, Long userId) { - groupMessageReadRedisDAO.deleteReadMaxMessageId(groupId, userId); - } - - @Override - public void deleteReadMaxMessageIds(Long groupId, Collection userIds) { - groupMessageReadRedisDAO.deleteReadMaxMessageIds(groupId, userIds); - } - - @Override - public void deleteReadMaxMessageIdMap(Long groupId) { - groupMessageReadRedisDAO.deleteReadMaxMessageIdMap(groupId); - } - // ==================== 管理后台 ==================== @Override @@ -652,11 +523,11 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { */ private Integer resolveReceiptStatus(Boolean receipt) { if (BooleanUtil.isFalse(imProperties.getMessage().isGroupReadEnabled())) { - return ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); } return BooleanUtil.isTrue(receipt) - ? ImGroupMessageReceiptStatusEnum.PENDING.getStatus() - : ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + ? ImMessageReceiptStatusEnum.PENDING.getStatus() + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); } /** diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java index b16257f..23ac9b3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java @@ -59,8 +59,8 @@ public interface ImPrivateMessageService { /** * 标记私聊消息已读 *

- * 语义:将「对方发给当前用户、id <= messageId 的未读消息」一次性翻转为已读, - * 与群聊 readGroupMessages 对称,避免"select-then-update"两步式带来的竞态。 + * 语义:把「对方发给当前用户、id <= messageId 的待回执消息」一次性更新为已完成(DONE)并前进读位置, + * 与群聊 readGroupMessages 对称,避免「select-then-update」两步式带来的竞态。 * * @param userId 当前用户编号 * @param receiverId 接收方用户编号(对方) @@ -72,7 +72,7 @@ public interface ImPrivateMessageService { * 查询对方已读到我发的最大消息 id *

* 用于多端 / 离线场景下的已读位置补齐:客户端进入会话或断线重连后, - * 调用此接口拿到对方的 maxReadId,再按 id <= maxReadId 翻转本地自发消息为已读,弥补离线期间错过的 RECEIPT 推送事件。 + * 调用此接口拿到对方的 maxReadId,再按 id <= maxReadId 更新本地自发消息的回执状态,弥补离线期间错过的 RECEIPT 推送事件。 * * @param userId 当前用户编号 * @param peerId 对方用户编号 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index cd83a2d..de79f9e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/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 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; import cn.iocoder.yudao.module.im.util.ImMessageUtils; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -52,6 +58,8 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { private ImFriendService friendService; @Resource private ImSensitiveWordService sensitiveWordService; + @Resource + private ImConversationReadService conversationReadService; @Resource private ImWebSocketService imWebSocketService; @@ -74,21 +82,32 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { // 1.3 好友校验 friendService.validateFriend(senderId, reqVO.getReceiverId()); // 1.4 文本消息敏感词过滤 - if (ImMessageTypeEnum.TEXT.getType().equals(reqVO.getType())) { + if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) { sensitiveWordService.validateText(reqVO.getContent()); } // 2.1 引用 quote 消息规范化 reqVO.setContent(normalizeQuoteContent(reqVO, senderId)); - // 2.2 构建并保存消息 + // 2.2 构建并保存消息;唯一键冲突时回查已存在消息返回 + // 用户私聊消息默认需要回执(receipt 不传按 true);系统通知走 DTO 通道,默认不回执 + Boolean receipt = reqVO.getReceipt() != null ? reqVO.getReceipt() : Boolean.TRUE; ImPrivateMessageDO message = BeanUtils.toBean(reqVO, ImPrivateMessageDO.class, m -> m - .setSenderId(senderId).setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now())); - privateMessageMapper.insert(message); + .setSenderId(senderId).setStatus(ImMessageStatusEnum.NORMAL.getStatus()) + .setReceiptStatus(resolveReceiptStatus(receipt)).setSendTime(LocalDateTime.now())); + try { + privateMessageMapper.insert(message); + } catch (DuplicateKeyException e) { + log.warn("[sendPrivateMessage][senderId({}) clientMessageId({}) 并发插入冲突,回查返回]", + senderId, reqVO.getClientMessageId()); + return privateMessageMapper.selectBySenderIdAndClientMessageId(senderId, reqVO.getClientMessageId()); + } // 3. WebSocket 异步推送:接收方 + 发送方多端同步 - ImPrivateMessageDTO websocketMessage = ImPrivateMessageDTO.ofSend(message); - imWebSocketService.sendPrivateMessageAsync(message.getReceiverId(), websocketMessage); - imWebSocketService.sendPrivateMessageAsync(senderId, websocketMessage); + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message); + imWebSocketService.sendNotificationAsync(message.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); return message; } @@ -103,24 +122,39 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { ImPrivateMessageDO message = new ImPrivateMessageDO().setClientMessageId(IdUtil.fastSimpleUUID()) .setSenderId(senderId).setReceiverId(dto.getReceiverId()) .setType(dto.getType()).setContent(contentString) - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()).setSendTime(LocalDateTime.now()); + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()) + .setReceiptStatus(resolveReceiptStatus(dto.getReceipt())).setSendTime(LocalDateTime.now()); // 1.3 决定是否持久化:dto.persistent 优先;为 null 时按 type 默认 boolean persistent = dto.getPersistent() != null ? dto.getPersistent() - : ImMessageTypeEnum.validate(dto.getType()).isPersistent(); + : ImContentTypeEnum.validate(dto.getType()).isPersistent(); if (persistent) { privateMessageMapper.insert(message); } // 2. WebSocket 异步推送:双向(默认);单边语义(persistent=false)下仅推 sender 多端,对方不感知 - ImPrivateMessageDTO websocketMessage = ImPrivateMessageDTO.ofSend(message); + ImPrivateMessageNotification notification = ImPrivateMessageNotification.ofSend(message); if (persistent) { - imWebSocketService.sendPrivateMessageAsync(dto.getReceiverId(), websocketMessage); + imWebSocketService.sendNotificationAsync(dto.getReceiverId(), ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); } - imWebSocketService.sendPrivateMessageAsync(senderId, websocketMessage); + imWebSocketService.sendNotificationAsync(senderId, ImConversationTypeEnum.PRIVATE.getType(), + notification.getType(), notification); return message; } + /** + * 计算私聊消息回执 status:私聊已读关闭时强制 NO_RECEIPT,忽略发送方传入的 receipt(receipt 为 null 等价 false) + */ + private Integer resolveReceiptStatus(Boolean receipt) { + if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { + return ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + return BooleanUtil.isTrue(receipt) + ? ImMessageReceiptStatusEnum.PENDING.getStatus() + : ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(); + } + @Override @Transactional(rollbackFor = Exception.class) public ImPrivateMessageDO recallPrivateMessage(Long userId, Long messageId) { @@ -149,7 +183,7 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { // 3. 发送撤回事件 return sendPrivateMessage(userId, new ImPrivateMessageSendDTO().setReceiverId(message.getReceiverId()) - .setType(ImMessageTypeEnum.RECALL.getType()).setContent(new RecallMessage().setMessageId(messageId))); + .setType(ImContentTypeEnum.RECALL.getType()).setContent(new RecallMessage().setMessageId(messageId))); } /** @@ -211,20 +245,26 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { throw exception(MESSAGE_PRIVATE_READ_DISABLED); } Assert.notNull(messageId, "已读消息编号不能为空"); - // 2. 把 (receiverId → userId) 这条会话上、id <= messageId 的未读消息一步更新为已读 - // 仅 UNREAD 行被命中,避免覆盖已撤回/已读的状态;select-then-update 合成单条 SQL 后也消除了竞态窗口 - int updated = privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus( - receiverId, userId, messageId, ImMessageStatusEnum.UNREAD.getStatus(), - new ImPrivateMessageDO().setStatus(ImMessageStatusEnum.READ.getStatus())); - if (updated == 0) { + + // 2. 回执置 DONE:把 (receiverId → userId) 上 id <= messageId、待回执(PENDING) 的消息标记为已完成 + // status 不再表达已读(保持 NORMAL);只翻 PENDING 行,避免覆盖 NO_RECEIPT / 已 DONE + privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( + receiverId, userId, messageId, ImMessageReceiptStatusEnum.PENDING.getStatus(), + new ImPrivateMessageDO().setReceiptStatus(ImMessageReceiptStatusEnum.DONE.getStatus())); + + // 3. 同步写 im_conversation_read(读位置唯一权威,单调递增);读位置前进才下发事件 + boolean advanced = conversationReadService.updateConversationReadPosition( + userId, ImConversationTypeEnum.PRIVATE.getType(), receiverId, messageId); + if (!advanced) { return; } - // 3. 异步发送 READ + RECEIPT 事件(已读位置以前端上报为准,与多端 / 对方 UI 显示一致) - imWebSocketService.sendPrivateMessageAsync(userId, - ImPrivateMessageDTO.ofRead(userId, receiverId, messageId)); - imWebSocketService.sendPrivateMessageAsync(receiverId, - ImPrivateMessageDTO.ofReceipt(userId, receiverId, messageId)); + // 4. 异步发送 READ + RECEIPT 事件(已读位置以前端上报为准,与多端 / 对方 UI 显示一致) + imWebSocketService.sendNotificationAsync(userId, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.READ.getType(), ImMessageReadNotification.ofPrivate(userId, receiverId, messageId)); + imWebSocketService.sendNotificationAsync(receiverId, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.RECEIPT.getType(), + ImMessageReceiptNotification.ofPrivate(userId, receiverId, messageId)); } @Override @@ -232,8 +272,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { if (BooleanUtil.isFalse(imProperties.getMessage().isPrivateReadEnabled())) { throw exception(MESSAGE_PRIVATE_READ_DISABLED); } - return privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus( - userId, peerId, ImMessageStatusEnum.READ.getStatus()); + // 对端 peer 在「与 userId 的会话」里的读位置 = peer 把 userId 发的消息读到哪 + return conversationReadService.getConversationReadMessageId( + peerId, ImConversationTypeEnum.PRIVATE.getType(), userId); } @Override diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java index 95f4234..87a7f88 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImGroupMessageSendDTO.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.module.im.service.message.dto; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.*; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.service.websocket.notification.group.*; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -26,7 +26,7 @@ public class ImGroupMessageSendDTO { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ @NotNull(message = "消息类型不能为空") private Integer type; @@ -53,30 +53,34 @@ public class ImGroupMessageSendDTO { */ private Boolean receipt; - // ========== 群广播事件静态工厂(对应 ImMessageTypeEnum 群事件) ========== + // ========== 群广播事件静态工厂(对应 ImContentTypeEnum 群事件) ========== public static ImGroupMessageSendDTO ofGroupCreate(Long groupId, Long operatorUserId, Collection memberUserIds) { GroupCreateNotification notification = new GroupCreateNotification(); notification.setOperatorUserId(operatorUserId); notification.setMemberUserIds(new ArrayList<>(memberUserIds)); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_CREATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_CREATE.getType()).setContent(notification); } - public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId, String oldAvatar, String newAvatar) { + public static ImGroupMessageSendDTO ofGroupInfoUpdate(Long groupId, Long operatorUserId, + String oldAvatar, String newAvatar, + Boolean oldJoinApproval, Boolean newJoinApproval) { GroupInfoUpdateNotification notification = new GroupInfoUpdateNotification(); notification.setOperatorUserId(operatorUserId); notification.setOldAvatar(oldAvatar); notification.setNewAvatar(newAvatar); + notification.setOldJoinApproval(oldJoinApproval); + notification.setNewJoinApproval(newJoinApproval); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_INFO_UPDATE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberQuit(Long groupId, Long operatorUserId) { GroupMemberQuitNotification notification = new GroupMemberQuitNotification(); notification.setOperatorUserId(operatorUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_QUIT.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupOwnerTransfer(Long groupId, Long operatorUserId, Long newOwnerUserId) { @@ -84,7 +88,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setNewOwnerUserId(newOwnerUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_OWNER_TRANSFER.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_OWNER_TRANSFER.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberKick(Long groupId, Long operatorUserId, Collection memberUserIds) { @@ -92,7 +96,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMemberUserIds(new ArrayList<>(memberUserIds)); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_KICK.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_KICK.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberInvite(Long groupId, Long operatorUserId, Collection memberUserIds) { @@ -100,7 +104,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMemberUserIds(new ArrayList<>(memberUserIds)); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberEnter(Long groupId, Long entrantUserId, Integer addSource) { @@ -109,14 +113,14 @@ public class ImGroupMessageSendDTO { notification.setEntrantUserId(entrantUserId); notification.setAddSource(addSource); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupDissolve(Long groupId, Long operatorUserId) { GroupDissolveNotification notification = new GroupDissolveNotification(); notification.setOperatorUserId(operatorUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberNicknameUpdate(Long groupId, Long operatorUserId, String displayUserName) { @@ -124,7 +128,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setDisplayUserName(displayUserName); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupAdminAdd(Long groupId, Long operatorUserId, Collection memberUserIds) { @@ -132,7 +136,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMemberUserIds(new ArrayList<>(memberUserIds)); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_ADMIN_ADD.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_ADMIN_ADD.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupAdminRemove(Long groupId, Long operatorUserId, Collection memberUserIds) { @@ -140,7 +144,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMemberUserIds(new ArrayList<>(memberUserIds)); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_ADMIN_REMOVE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_ADMIN_REMOVE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupNoticeUpdate(Long groupId, Long operatorUserId, String oldNotice, String newNotice) { @@ -148,7 +152,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setOldNotice(oldNotice).setNewNotice(newNotice); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_NOTICE_UPDATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_NOTICE_UPDATE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupNameUpdate(Long groupId, Long operatorUserId, String oldName, String newName) { @@ -156,7 +160,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setOldName(oldName).setNewName(newName); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_NAME_UPDATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_NAME_UPDATE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberSettingUpdate(Long groupId, Long operatorUserId, Boolean silent, String groupRemark) { @@ -164,7 +168,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setSilent(silent).setGroupRemark(groupRemark); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_SETTING_UPDATE.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_SETTING_UPDATE.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMessagePin(Long groupId, Long operatorUserId, ImGroupMessageDO message) { @@ -176,7 +180,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMessageId(message.getId()).setMessage(pinnedMessage); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MESSAGE_PIN.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MESSAGE_PIN.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMessageUnpin(Long groupId, Long operatorUserId, Long messageId) { @@ -184,7 +188,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMessageId(messageId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MESSAGE_UNPIN.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MESSAGE_UNPIN.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberMuted(Long groupId, Long operatorUserId, @@ -195,7 +199,7 @@ public class ImGroupMessageSendDTO { notification.setMutedUserId(mutedUserId); notification.setMuteEndTime(muteEndTime); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_MUTED.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_MUTED.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMemberCancelMuted(Long groupId, Long operatorUserId, Long mutedUserId) { @@ -203,21 +207,21 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setMutedUserId(mutedUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MEMBER_CANCEL_MUTED.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MEMBER_CANCEL_MUTED.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupMuted(Long groupId, Long operatorUserId) { GroupMutedNotification notification = new GroupMutedNotification(); notification.setOperatorUserId(operatorUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_MUTED.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_MUTED.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupCancelMuted(Long groupId, Long operatorUserId) { GroupCancelMutedNotification notification = new GroupCancelMutedNotification(); notification.setOperatorUserId(operatorUserId); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_CANCEL_MUTED.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_CANCEL_MUTED.getType()).setContent(notification); } public static ImGroupMessageSendDTO ofGroupBanned(Long groupId, Long operatorUserId, boolean banned) { @@ -225,7 +229,7 @@ public class ImGroupMessageSendDTO { notification.setOperatorUserId(operatorUserId); notification.setBanned(banned); return new ImGroupMessageSendDTO().setGroupId(groupId) - .setType(ImMessageTypeEnum.GROUP_BANNED.getType()).setContent(notification); + .setType(ImContentTypeEnum.GROUP_BANNED.getType()).setContent(notification); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java index f1c5375..5758c3b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/dto/ImPrivateMessageSendDTO.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.im.service.message.dto; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import lombok.Data; /** @@ -18,7 +18,7 @@ public class ImPrivateMessageSendDTO { /** * 消息类型 *

- * 枚举 {@link ImMessageTypeEnum} + * 枚举 {@link ImContentTypeEnum} */ private Integer type; /** @@ -30,10 +30,14 @@ public class ImPrivateMessageSendDTO { /** * 是否持久化 + 推送给接收方(单边语义开关) *

- * null:默认按 {@link ImMessageTypeEnum#isPersistent()} 决定是否入库 + 双向 WS 推送(保持原行为)
+ * null:默认按 {@link ImContentTypeEnum#isPersistent()} 决定是否入库 + 双向 WS 推送(保持原行为)
* false:覆盖为单边——不入库、仅推 sender 多端,对方不感知(如「你已删除 XXX」这类仅自己可见的 TIP)
* true:覆盖为双向 + 入库 */ private Boolean persistent; + /** + * 是否需要回执;null 等价 false(系统消息默认不需要回执) + */ + private Boolean receipt; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java index 28bf9b8..6aa4f56 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.im.dal.mysql.rtc.ImRtcParticipantMapper; import cn.iocoder.yudao.module.im.dal.redis.rtc.ImRtcCallLockRedisDAO; import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallStatusEnum; import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantRoleEnum; @@ -30,8 +30,7 @@ import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc.*; +import cn.iocoder.yudao.module.im.service.websocket.notification.rtc.*; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -57,7 +56,7 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; * 并发幂等:同好友对 / 同群活跃唯一性走 {@link ImRtcCallLockRedisDAO} 分布式锁 + 锁内 SELECT 兜底;webhook 兜底走条件 UPDATE; *

* 推送通道分流: - * 1601 RTC_CALL(INVITING / JOINED / REJECTED / NO_ANSWER / LEFT 子类型)→ {@link ImWebSocketService#sendPrivateMessageAsync} 仅推参与方; + * 1601 RTC_CALL(INVITING / JOINED / REJECTED / NO_ANSWER / LEFT 子类型)→ {@link ImWebSocketService#sendNotificationAsync} 仅推参与方; * 1602 / 1603 PARTICIPANT_CONNECTED / DISCONNECTED → {@link ImWebSocketService} 推参与方 + 群通话场景广播全群; * 1610 RTC_CALL_START + 1611 RTC_CALL_END → {@link ImPrivateMessageService} / {@link ImGroupMessageService} 入消息流当聊天 tip * (START 仅群通话;两者分别在 invite / cancel(leave) 事务里 INSERT,自增 id 自然保证顺序) @@ -707,7 +706,6 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { } // 3.1 群通话:推 RTC_CALL(NO_ANSWER) 让前端 banner 移除该人 + 级联关房判定 - // TODO DONE @AI:拆分独立 NO_ANSWER 信令,不再复用 REJECT if (ImConversationTypeEnum.isGroup(call.getConversationType())) { pushCallNoAnswerNotification(call, userId, userMap.get(userId)); endSessionIfTerminal(call, userId); @@ -1003,8 +1001,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { String token = signToken(inviteeId, resolveDisplayName(invitee, inviteeId), call.getRoom()); ImRtcCallNotification payload = ImRtcCallNotification.ofInvite( call, inviter, imProperties.getRtc().getLivekitUrl(), token, inviteeIds); - webSocketService.sendPrivateMessageAsync(inviteeId, ImPrivateMessageDTO.ofRtcNotification( - ImMessageTypeEnum.RTC_CALL.getType(), call.getInviterUserId(), inviteeId, payload)); + webSocketService.sendNotificationAsync(inviteeId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); } /** @@ -1019,8 +1017,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { AdminUserRespDTO operator = operatorUserId != null ? adminUserApi.getUser(operatorUserId) : null; ImRtcCallNotification payload = ImRtcCallNotification.ofReject(call, operatorUserId, operator); for (Long receiverUserId : getCallAudienceUserIdList(call)) { - webSocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofRtcNotification( - ImMessageTypeEnum.RTC_CALL.getType(), operatorUserId, receiverUserId, payload)); + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); } } @@ -1036,8 +1034,8 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { private void pushCallNoAnswerNotification(ImRtcCallDO call, Long operatorUserId, AdminUserRespDTO operator) { ImRtcCallNotification payload = ImRtcCallNotification.ofNoAnswer(call, operatorUserId, operator); for (Long receiverUserId : getCallAudienceUserIdList(call)) { - webSocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofRtcNotification( - ImMessageTypeEnum.RTC_CALL.getType(), operatorUserId, receiverUserId, payload)); + webSocketService.sendNotificationAsync(receiverUserId, ImConversationTypeEnum.NONE.getType(), + ImContentTypeEnum.RTC_CALL.getType(), payload); } } @@ -1048,7 +1046,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { * @param userId 加入的参与者用户编号 */ private void pushParticipantConnectedNotification(ImRtcCallDO call, Long userId) { - pushParticipantNotification(call, ImMessageTypeEnum.RTC_PARTICIPANT_CONNECTED.getType(), userId, + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_CONNECTED.getType(), userId, ImRtcParticipantConnectedNotification.of(call, userId)); } @@ -1059,7 +1057,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { * @param userId 离开的参与者用户编号 */ private void pushParticipantDisconnectedNotification(ImRtcCallDO call, Long userId) { - pushParticipantNotification(call, ImMessageTypeEnum.RTC_PARTICIPANT_DISCONNECTED.getType(), userId, + pushParticipantNotification(call, ImContentTypeEnum.RTC_PARTICIPANT_DISCONNECTED.getType(), userId, ImRtcParticipantDisconnectedNotification.of(call, userId)); } @@ -1076,8 +1074,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { if (CollUtil.isEmpty(receivers)) { return; } - ImPrivateMessageDTO dto = ImPrivateMessageDTO.ofRtcNotification(type, actorUserId, null, payload); - webSocketService.sendPrivateMessageAsync(receivers, dto); + webSocketService.sendNotificationAsync(receivers, ImConversationTypeEnum.NONE.getType(), type, payload); } /** @@ -1092,7 +1089,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { private void pushCallStartNotification(ImRtcCallDO call, AdminUserRespDTO inviter, Set invitees) { ImRtcCallStartNotification payload = ImRtcCallStartNotification.of(call, inviter); Long peerUserId = ImConversationTypeEnum.isGroup(call.getConversationType()) ? null : CollUtil.getFirst(invitees); - pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_START, payload, peerUserId); + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_START, payload, peerUserId); } /** @@ -1116,7 +1113,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { p -> ObjUtil.notEqual(p.getUserId(), call.getInviterUserId())); peerUserId = peer != null ? peer.getUserId() : null; } - pushCallChatMessage(call, ImMessageTypeEnum.RTC_CALL_END, payload, peerUserId); + pushCallChatMessage(call, ImContentTypeEnum.RTC_CALL_END, payload, peerUserId); } /** @@ -1129,7 +1126,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { * @param payload 推送 payload * @param peerUserId 私聊对端用户编号;群聊忽略,私聊缺失时回退为 senderId */ - private void pushCallChatMessage(ImRtcCallDO call, ImMessageTypeEnum type, Object payload, Long peerUserId) { + private void pushCallChatMessage(ImRtcCallDO call, ImContentTypeEnum type, Object payload, Long peerUserId) { Long senderId = call.getInviterUserId(); if (ImConversationTypeEnum.isGroup(call.getConversationType())) { ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO().setGroupId(call.getGroupId()) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java index 5337bff..a21fc03 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerService.java @@ -80,7 +80,7 @@ public interface ImStatisticsManagerService { Map getGroupMessageDailyCountMap(LocalDateTime beginTime, LocalDateTime endTime); /** - * 获取区间内消息类型分布 Map(key 为消息类型) + * 获取区间内内容类型分布 Map(key 为消息类型) */ Map getMessageTypeCountMap(LocalDateTime beginTime, LocalDateTime endTime); diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java index aedcfd3..2e7404c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketService.java @@ -1,82 +1,46 @@ package cn.iocoder.yudao.module.im.service.websocket; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; - import java.util.Collection; import java.util.Collections; /** * IM WebSocket 推送 Service 接口 *

- * 统一封装 WebSocket 消息推送,事务内调用时提交后异步执行。 + * 统一封装 WebSocket 通知推送,事务内调用时提交后异步执行。 * * @author 芋道源码 */ public interface ImWebSocketService { /** - * 异步推送私聊消息给指定用户 - * - * @param userId 目标用户编号 - * @param dto 私聊消息 DTO - */ - default void sendPrivateMessageAsync(Long userId, ImPrivateMessageDTO dto) { - sendPrivateMessageAsync(Collections.singleton(userId), dto); - } - - /** - * 异步批量推送私聊消息给多个用户;用于同一份 DTO 扇出到多个收件人 - *

- * 相比逐个发送,仅注册一次 afterCommit 回调和异步任务。 - * - * @param userIds 目标用户编号列表 - * @param dto 私聊消息 DTO - */ - void sendPrivateMessageAsync(Collection userIds, ImPrivateMessageDTO dto); - - /** - * 异步推送群聊消息给指定用户 - * - * @param userId 目标用户编号 - * @param dto 群聊消息 DTO - */ - default void sendGroupMessageAsync(Long userId, ImGroupMessageDTO dto) { - sendGroupMessageAsync(Collections.singleton(userId), dto); - } - - /** - * 异步批量推送群聊消息给多个用户 - * - * @param userIds 目标用户编号列表 - * @param dto 群聊消息 DTO - */ - void sendGroupMessageAsync(Collection userIds, ImGroupMessageDTO dto); - - /** - * 异步推送频道消息给指定用户 + * 异步推送 WebSocket 通知给指定用户 * - * @param userId 目标用户编号 - * @param dto 频道消息 DTO + * @param userId 目标用户编号 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload */ - default void sendChannelMessageAsync(Long userId, ImChannelMessageDTO dto) { - sendChannelMessageAsync(Collections.singleton(userId), dto); + default void sendNotificationAsync(Long userId, Integer conversationType, Integer contentType, Object payload) { + sendNotificationAsync(Collections.singleton(userId), conversationType, contentType, payload); } /** - * 异步批量推送频道消息给多个用户 + * 异步批量推送 WebSocket 通知给多个用户 * - * @param userIds 目标用户编号列表 - * @param dto 频道消息 DTO + * @param userIds 目标用户编号列表 + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload */ - void sendChannelMessageAsync(Collection userIds, ImChannelMessageDTO dto); + void sendNotificationAsync(Collection userIds, Integer conversationType, Integer contentType, Object payload); /** - * 异步广播频道消息给当前所有在线管理端用户;用于全员推送 + * 异步广播 WebSocket 通知给当前所有在线管理端用户 * - * @param dto 频道消息 DTO + * @param conversationType 会话类型,参见 ImConversationTypeEnum 枚举类 + * @param contentType 内容类型,参见 ImContentTypeEnum 枚举类 + * @param payload 通知 payload */ - void broadcastChannelMessageAsync(ImChannelMessageDTO dto); + void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java index 2253943..a3a1078 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImpl.java +++ b/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; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImChannelMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO; import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -36,83 +34,51 @@ public class ImWebSocketServiceImpl implements ImWebSocketService { private WebSocketSenderApi webSocketSenderApi; @Override - public void sendPrivateMessageAsync(Collection userIds, ImPrivateMessageDTO dto) { - // 说明:通过 executeAfterTransaction 保证事务提交后再推送,避免客户端收到消息后查询数据库时事务尚未提交 - // 通过 getSelf() 获取 Spring 代理对象调用 @Async 方法,确保异步 AOP 生效(直接 this 调用会绕过代理) - executeAfterTransaction(() -> getSelf().doSendPrivateMessage(userIds, dto)); + public void sendNotificationAsync(Collection userIds, Integer conversationType, Integer contentType, + Object payload) { + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload); + executeAfterTransaction(() -> getSelf().doSendNotification(userIds, notification)); } @Override - public void sendGroupMessageAsync(Collection userIds, ImGroupMessageDTO dto) { - executeAfterTransaction(() -> getSelf().doSendGroupMessage(userIds, dto)); + public void broadcastNotificationAsync(Integer conversationType, Integer contentType, Object payload) { + ImNotificationWebSocketDTO notification = buildNotification(conversationType, contentType, payload); + executeAfterTransaction(() -> getSelf().doBroadcastNotification(notification)); } - @Override - public void sendChannelMessageAsync(Collection userIds, ImChannelMessageDTO dto) { - executeAfterTransaction(() -> getSelf().doSendChannelMessage(userIds, dto)); - } - - @Override - public void broadcastChannelMessageAsync(ImChannelMessageDTO dto) { - executeAfterTransaction(() -> getSelf().doBroadcastChannelMessage(dto)); - } - - /** - * 异步发送私聊 WebSocket 消息;多收件人共享同一 dto,避免按收件人重复注册 afterCommit 回调 - */ - @Async - public void doSendPrivateMessage(Collection userIds, ImPrivateMessageDTO dto) { - for (Long userId : getDistinctUserIds(userIds)) { - try { - webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, - ImPrivateMessageDTO.TYPE, dto); - } catch (Exception e) { - log.error("[doSendPrivateMessage][userId({}) dto({}) 发送失败]", userId, dto, e); - } - } - } - - /** - * 异步发送群聊 WebSocket 消息 - */ - @Async - public void doSendGroupMessage(Collection userIds, ImGroupMessageDTO dto) { - for (Long userId : getDistinctUserIds(userIds)) { - try { - webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, - ImGroupMessageDTO.TYPE, dto); - } catch (Exception e) { - log.error("[doSendGroupMessage][userId({}) dto({}) 发送失败]", userId, dto, e); - } - } + private static ImNotificationWebSocketDTO buildNotification(Integer conversationType, Integer contentType, + Object payload) { + return new ImNotificationWebSocketDTO() + .setConversationType(conversationType) + .setContentType(contentType) + .setPayload(payload); } /** - * 异步发送频道 WebSocket 消息 + * 异步发送 WebSocket 通知 */ @Async - public void doSendChannelMessage(Collection userIds, ImChannelMessageDTO dto) { + public void doSendNotification(Collection userIds, ImNotificationWebSocketDTO notification) { for (Long userId : getDistinctUserIds(userIds)) { try { webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), userId, - ImChannelMessageDTO.TYPE, dto); + ImNotificationWebSocketDTO.TYPE, notification); } catch (Exception e) { - log.error("[doSendChannelMessage][userId({}) dto({}) 发送失败]", userId, dto, e); + log.error("[doSendNotification][userId({}) notification({}) 发送失败]", userId, notification, e); } } } /** - * 异步广播频道 WebSocket 消息给当前所有在线管理端用户; - * 依赖 infra WebSocketSenderApi 按 UserType 广播能力,离线用户由客户端上线 pull 兜底 + * 异步广播 WebSocket 通知 */ @Async - public void doBroadcastChannelMessage(ImChannelMessageDTO dto) { + public void doBroadcastNotification(ImNotificationWebSocketDTO notification) { try { webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), - ImChannelMessageDTO.TYPE, dto); + ImNotificationWebSocketDTO.TYPE, notification); } catch (Exception e) { - log.error("[doBroadcastChannelMessage][dto({}) 广播失败]", dto, e); + log.error("[doBroadcastNotification][notification({}) 广播失败]", notification, e); } } @@ -121,19 +87,15 @@ public class ImWebSocketServiceImpl implements ImWebSocketService { } /** - * 事务感知的任务调度: - * - 有事务:注册 afterCommit 回调,事务提交后再执行,防止客户端拿到消息去查库时数据还没落盘 - * - 无事务:直接执行(如非 @Transactional 方法中的调用) + * 事务感知的任务调度 * - * @param task 待执行的推送任务(内部通过 getSelf() 走 @Async 异步执行) + * @param task 待执行的推送任务 */ private void executeAfterTransaction(Runnable task) { - // 情况一:没有事务,直接执行 if (!TransactionSynchronizationManager.isSynchronizationActive()) { task.run(); return; } - // 情况二:有事务,注册 afterCommit 事件,在事务提交后执行 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java deleted file mode 100644 index 7c708d9..0000000 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java +++ /dev/null @@ -1,176 +0,0 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto; - -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend.BaseFriendNotification; -import cn.iocoder.yudao.module.im.service.websocket.dto.notification.group.BaseGroupNotification; -import lombok.Data; -import lombok.experimental.Accessors; - -import java.time.LocalDateTime; - -/** - * IM 私聊消息 WebSocket 统一推送 DTO - *

- * 发送、已读、回执、撤回都通过 {@link #type} 字段区分, - * 并统一复用 {@link #id} 字段表达目标消息或已读位置。 - * - * @author 芋道源码 - */ -@Data -@Accessors(chain = true) -public class ImPrivateMessageDTO { - - public static final String TYPE = "im-private-message"; - - /** - * 消息编号 - *

- * 普通消息:当前消息 id - * READ:已读位置(我已读到这条消息) - * RECEIPT:已读位置(对方已读到这条消息) - * RECALL:被撤回的原消息 id - */ - private Long id; - /** - * 客户端消息编号 - */ - private String clientMessageId; - /** - * 发送人编号 - */ - private Long senderId; - /** - * 接收人编号 - */ - private Long receiverId; - /** - * 消息类型 - */ - private Integer type; - /** - * 消息内容 - */ - private String content; - /** - * 消息状态 - */ - private Integer status; - /** - * 发送时间 - */ - private LocalDateTime sendTime; - - // ========== 静态工厂方法 ========== - - /** - * 构建发送消息 DTO - * - * @param message 私聊消息 DO - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofSend(ImPrivateMessageDO message) { - return BeanUtils.toBean(message, ImPrivateMessageDTO.class); - } - - /** - * 构建已读同步 DTO(多端同步:通知自己的其他终端"我已经读了某个会话") - * - * @param senderId 当前用户编号 - * @param receiverId 对方用户编号 - * @param readId 已读位置(最大已读消息编号) - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofRead(Long senderId, Long receiverId, Long readId) { - return new ImPrivateMessageDTO() - .setId(readId).setType(ImMessageTypeEnum.READ.getType()) - .setSenderId(senderId).setReceiverId(receiverId); - } - - /** - * 构建已读回执 DTO(通知对方"我已读了你的消息") - * - * @param senderId 已读方的用户编号 - * @param receiverId 对方用户编号 - * @param readId 已读位置(最大已读消息编号) - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofReceipt(Long senderId, Long receiverId, Long readId) { - return new ImPrivateMessageDTO() - .setId(readId).setType(ImMessageTypeEnum.RECEIPT.getType()) - .setSenderId(senderId).setReceiverId(receiverId); - } - - // ==================== 好友变更相关 ==================== - - /** - * 构建好友通知推送 DTO(统一入口) - * - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 FRIEND_* 段 - * @param operatorUserId 操作人用户编号;同时作为帧的 senderId 用于定位接收端 friendUserId - * @param receiverUserId 推送目标用户编号 - * @param payload 好友通知 payload(继承 {@link BaseFriendNotification}) - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofFriendNotification(Integer type, Long operatorUserId, - Long receiverUserId, BaseFriendNotification payload) { - validateNotification(type, payload, ImMessageTypeEnum.isFriendNotification(type)); - return new ImPrivateMessageDTO().setType(type) - .setSenderId(operatorUserId).setReceiverId(receiverUserId) - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now()); - } - - // ==================== 群定向私聊通知 ==================== - - // TODO DONE @AI:群申请定向通知继续走私聊通道 - /** - * 构建群通知推送 DTO(走私聊通道定向推送,不入群消息流) - *

- * 用于 GROUP_REQUEST_RECEIVED / GROUP_REQUEST_APPROVED / GROUP_REQUEST_REJECTED 段位; - * 与 {@link cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO} 走 sendGroupMessage 群广播路径不同 - * - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 GROUP_REQUEST_* 段 - * @param operatorUserId 操作人用户编号;同时作为帧的 senderId - * @param receiverUserId 推送目标用户编号 - * @param payload 群事件 payload(继承 {@link BaseGroupNotification}) - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofGroupNotification(Integer type, Long operatorUserId, - Long receiverUserId, BaseGroupNotification payload) { - validateNotification(type, payload, ImMessageTypeEnum.isGroupRequestNotification(type)); - return new ImPrivateMessageDTO().setType(type) - .setSenderId(operatorUserId).setReceiverId(receiverUserId) - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now()); - } - - // ==================== 实时通话信令 ==================== - - /** - * 构建通话信令推送 DTO(走私聊通道仅推参与方,不入消息流) - *

- * 用于 RTC_CALL(INVITE / REJECT 等)/ RTC_PARTICIPANT_CONNECTED / RTC_PARTICIPANT_DISCONNECTED - * - * @param type 消息类型;取自 {@link ImMessageTypeEnum} 中的 RTC_* 段 - * @param senderId 发送人编号;INVITE 时为发起人,REJECT 时为拒绝者,参与者事件时为加入 / 离开者 - * @param receiverUserId 推送目标用户编号 - * @param payload 通话事件 payload(任意 RTC 通知 DTO) - * @return 私聊 DTO - */ - public static ImPrivateMessageDTO ofRtcNotification(Integer type, Long senderId, - Long receiverUserId, Object payload) { - validateNotification(type, payload, ImMessageTypeEnum.isRtcNotification(type)); - return new ImPrivateMessageDTO().setType(type) - .setSenderId(senderId).setReceiverId(receiverUserId) - .setContent(JsonUtils.toJsonString(payload)).setSendTime(LocalDateTime.now()); - } - - private static void validateNotification(Integer type, Object payload, boolean supported) { - Assert.notNull(type, "消息类型不能为空"); - Assert.notNull(payload, "消息内容不能为空"); - Assert.isTrue(supported, "消息类型不匹配 type={}", type); - } - -} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java new file mode 100644 index 0000000..38a6d04 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/ImNotificationWebSocketDTO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM WebSocket 在线通知外壳 + *

+ * conversationType 定位会话维度;contentType 定位业务内容;payload 承载对应通知对象。 + * 会进入聊天流的私聊、群聊、频道事件走 message 子包;不进入聊天流的好友、加群申请、通话信令走 NONE 会话。 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImNotificationWebSocketDTO { + + public static final String TYPE = "im-notification"; + + /** + * 会话类型,参见 ImConversationTypeEnum 枚举类 + */ + private Integer conversationType; + /** + * 内容类型,参见 ImContentTypeEnum 枚举类 + */ + private Integer contentType; + /** + * 负载数据 + */ + private Object payload; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/BaseFriendNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java index 4ee8f87..d2d3a22 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/BaseFriendNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/BaseFriendNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendAddNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java index 5892174..781c94c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendAddNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendAddNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendBlockNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java index f8cb69e..abc0913 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendBlockNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendBlockNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendDeleteNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java index 7d8ab9f..814c4eb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendDeleteNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendDeleteNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java index 8f81489..60890b0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendInfoUpdatedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendInfoUpdatedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestApprovedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java index bc68f4f..5cb748c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestApprovedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestApprovedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java index 924e895..5d82e0e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestRejectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java index afe0a18..716ba4a 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendRequestRejectedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendRequestRejectedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUnblockNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java index a57f00d..00c246e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUnblockNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUnblockNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java index a1a52d6..2f3df5b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/friend/FriendUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/friend/FriendUpdateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.friend; +package cn.iocoder.yudao.module.im.service.websocket.notification.friend; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/BaseGroupNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java index 3e916b1..cd97756 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/BaseGroupNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/BaseGroupNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminAddNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java index cc31ad7..42dfa34 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminAddNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminAddNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 添加管理员事件通知(memberUserIds 为被设管理员的成员) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminRemoveNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java index 0f0e8d2..39b67dc 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupAdminRemoveNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupAdminRemoveNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 撤销管理员事件通知(memberUserIds 为被撤销管理员的成员) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupBannedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java index bdfe9d5..f72a79b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupBannedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupBannedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCancelMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java index bddf744..0dbc4b6 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCancelMutedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCancelMutedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCreateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java index b7e6563..2d10bab 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupCreateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupCreateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 群创建事件通知(memberUserIds 含创建者 + 初始邀请成员) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupDissolveNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java index bec91fa..acff6b0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupDissolveNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupDissolveNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 群解散事件通知 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupInfoUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java index 70a073d..7dbe87c 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupInfoUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupInfoUpdateNotification.java @@ -1,9 +1,9 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; /** - * 群信息变更事件通知(当前承载头像变更,NAME / NOTICE 走独立事件) + * 群信息变更事件通知(NAME / NOTICE 走独立事件) */ @Data public class GroupInfoUpdateNotification extends BaseGroupNotification { @@ -16,5 +16,13 @@ public class GroupInfoUpdateNotification extends BaseGroupNotification { * 新群头像 */ private String newAvatar; + /** + * 旧进群审批开关 + */ + private Boolean oldJoinApproval; + /** + * 新进群审批开关 + */ + private Boolean newJoinApproval; } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberCancelMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java index 48df8fc..70719ef 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberCancelMutedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberCancelMutedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberEnterNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java index 32ff290..337c744 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberEnterNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberEnterNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberInviteNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java index 5877ed1..7455845 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberInviteNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberInviteNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 成员加入事件通知(memberUserIds 为被邀请人) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberKickNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java index 30bbc34..24ff486 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberKickNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberKickNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 成员被移出事件通知(memberUserIds 为被移出成员) diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberListNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java index 4601858..5a57254 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberListNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberListNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java index 557d901..a636249 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberMutedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberMutedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberNicknameUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java index 0fbe63f..869fc2b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberNicknameUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberNicknameUpdateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberQuitNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java index cab23a5..663f104 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberQuitNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberQuitNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; /** * 成员退群事件通知 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberSettingUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java index e7eefcb..05f0215 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMemberSettingUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMemberSettingUpdateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessagePinNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java index 6dd5ebc..1717b1b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessagePinNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessagePinNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessageUnpinNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java index 1185b5c..8051967 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMessageUnpinNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMessageUnpinNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMutedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java index 2200766..dc49a29 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupMutedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupMutedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNameUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java index 689ef24..00fcc74 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNameUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNameUpdateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNoticeUpdateNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java index c2afa6c..ecd3e34 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupNoticeUpdateNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupNoticeUpdateNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupOwnerTransferNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java index 612a764..b4e8564 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupOwnerTransferNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupOwnerTransferNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java index 7965192..2e083f2 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestApprovedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestApprovedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; /** * 入群申请被同意通知 *

- * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 + * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestReceivedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java index a5d1f90..29b5aa3 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestReceivedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestReceivedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; /** * 收到新的入群申请通知 *

- * 走私聊通道定向推送给群主 + 全部管理员(多端同步);payload 已携带申请方昵称 / 头像,前端按 requestId 直接 push 进列表 + * 定向推送给群主 + 全部管理员(多端同步);payload 已携带申请方昵称 / 头像,前端按 requestId 直接 push 进列表 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java index e302085..dd6e153 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/group/GroupRequestRejectedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/group/GroupRequestRejectedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.group; +package cn.iocoder.yudao.module.im.service.websocket.notification.group; import lombok.Data; import lombok.EqualsAndHashCode; @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; /** * 入群申请被拒绝通知 *

- * 走私聊通道定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 + * 定向推送给申请人 + 群主 + 全部管理员;admin 侧据此把 unhandledCount 减 1 并从未处理列表移除 */ @EqualsAndHashCode(callSuper = true) @Data diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImChannelMessageDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java index 0478cd2..af7e4c2 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImChannelMessageDTO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImChannelMessageNotification.java @@ -1,15 +1,14 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto; +package cn.iocoder.yudao.module.im.service.websocket.notification.message; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import lombok.Data; import lombok.experimental.Accessors; import java.time.LocalDateTime; /** - * IM 频道消息 WebSocket 推送 DTO + * IM 频道消息 WebSocket 推送通知 *

* 单向:服务端运营推送 → C 端用户;C 端不能向频道发消息。 * 字段分层:顶层是消息元数据 + 检索维度,content 是 MaterialMessage payload 的 JSON 串。 @@ -18,9 +17,7 @@ import java.time.LocalDateTime; */ @Data @Accessors(chain = true) -public class ImChannelMessageDTO { - - public static final String TYPE = "im-channel-message"; +public class ImChannelMessageNotification { /** * 消息编号 @@ -48,23 +45,10 @@ public class ImChannelMessageDTO { private LocalDateTime sendTime; /** - * 由频道消息 DO 构建推送 DTO - */ - public static ImChannelMessageDTO ofSend(ImChannelMessageDO message) { - return BeanUtils.toBean(message, ImChannelMessageDTO.class); - } - - /** - * 构建已读同步 DTO;多端同步:通知自己的其他终端「我已经读了某频道」 - * - * @param channelId 频道编号 - * @param readId 已读位置(最大已读消息编号) - * @return 频道 DTO + * 由频道消息 DO 构建推送通知 */ - public static ImChannelMessageDTO ofRead(Long channelId, Long readId) { - return new ImChannelMessageDTO() - .setId(readId).setType(ImMessageTypeEnum.READ.getType()) - .setChannelId(channelId); + public static ImChannelMessageNotification ofSend(ImChannelMessageDO message) { + return BeanUtils.toBean(message, ImChannelMessageNotification.class); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java index 254df98..cbefa05 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImGroupMessageNotification.java @@ -1,32 +1,22 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto; +package cn.iocoder.yudao.module.im.service.websocket.notification.message; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; import lombok.Data; import java.time.LocalDateTime; import java.util.List; /** - * IM 群聊消息 WebSocket 统一推送 DTO - *

- * 发送、已读、回执都通过 {@link #type} 字段区分, - * 并统一复用 {@link #id} 字段表达目标消息或已读位置。 + * IM 群聊消息 WebSocket 统一推送通知 * * @author 芋道源码 */ @Data -public class ImGroupMessageDTO { - - public static final String TYPE = "im-group-message"; +public class ImGroupMessageNotification { /** * 消息编号 - *

- * 普通消息:当前消息 id - * READ:无(群已读不需要) - * RECEIPT:需要回执的目标消息 id */ private Long id; /** @@ -54,6 +44,14 @@ public class ImGroupMessageDTO { */ private Integer status; /** + * 回执状态 + */ + private Integer receiptStatus; + /** + * 已读人数 + */ + private Integer readCount; + /** * 发送时间 */ private LocalDateTime sendTime; @@ -66,58 +64,14 @@ public class ImGroupMessageDTO { */ private List receiverUserIds; /** - * 群回执状态 - */ - private Integer receiptStatus; - /** - * 群回执已读人数 - */ - private Integer readCount; - /** - * 已读位置 - */ - private Long readId; - - // ========== 静态工厂方法 ========== - - /** - * 构建发送消息 DTO + * 构建发送消息通知 * * @param message 群聊消息 DO - * @return 群聊 DTO - */ - public static ImGroupMessageDTO ofSend(ImGroupMessageDO message) { - return BeanUtils.toBean(message, ImGroupMessageDTO.class).setReceiverUserIds(null); - } - - /** - * 构建已读同步 DTO(多端同步:通知自己的其他终端"我已经读了某个群") - * - * @param senderId 当前用户编号 - * @param groupId 群编号 - * @param readId 已读位置(最大已读消息编号) - * @return 群聊 DTO - */ - public static ImGroupMessageDTO ofRead(Long senderId, Long groupId, Long readId) { - return new ImGroupMessageDTO() - .setId(readId).setReadId(readId).setType(ImMessageTypeEnum.READ.getType()) - .setSenderId(senderId).setGroupId(groupId); - } - - /** - * 构建群回执 DTO(广播回执状态和已读人数) - * - * @param messageId 消息编号 - * @param groupId 群编号 - * @param readCount 已读人数 - * @param receiptStatus 回执状态 - * @return 群聊 DTO + * @return 群聊通知 */ - public static ImGroupMessageDTO ofReceipt(Long messageId, Long groupId, - Integer readCount, Integer receiptStatus) { - return new ImGroupMessageDTO() - .setId(messageId).setType(ImMessageTypeEnum.RECEIPT.getType()) - .setGroupId(groupId).setReadCount(readCount).setReceiptStatus(receiptStatus); + public static ImGroupMessageNotification ofSend(ImGroupMessageDO message) { + return BeanUtils.toBean(message, ImGroupMessageNotification.class) + .setReadCount(message.getReadCount() != null ? message.getReadCount() : 0); } } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java new file mode 100644 index 0000000..c066fa0 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReadNotification.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM 消息已读同步通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImMessageReadNotification { + + /** + * 已读位置(最大已读消息编号) + */ + private Long id; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 私聊接收人编号 + */ + private Long receiverId; + /** + * 群编号 + */ + private Long groupId; + /** + * 频道编号 + */ + private Long channelId; + /** + * 已读位置(最大已读消息编号) + */ + private Long readId; + + public static ImMessageReadNotification ofPrivate(Long senderId, Long receiverId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setSenderId(senderId).setReceiverId(receiverId); + } + + public static ImMessageReadNotification ofGroup(Long senderId, Long groupId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setSenderId(senderId).setGroupId(groupId); + } + + public static ImMessageReadNotification ofChannel(Long channelId, Long readId) { + return new ImMessageReadNotification() + .setId(readId).setReadId(readId) + .setChannelId(channelId); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java new file mode 100644 index 0000000..f82df9f --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImMessageReceiptNotification.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * IM 消息回执通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImMessageReceiptNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 已读方的用户编号 + */ + private Long senderId; + /** + * 私聊接收人编号 + */ + private Long receiverId; + /** + * 群编号 + */ + private Long groupId; + /** + * 已读人数 + */ + private Integer readCount; + /** + * 回执状态 + */ + private Integer receiptStatus; + + public static ImMessageReceiptNotification ofPrivate(Long senderId, Long receiverId, Long readId) { + return new ImMessageReceiptNotification() + .setId(readId).setSenderId(senderId).setReceiverId(receiverId); + } + + public static ImMessageReceiptNotification ofGroup(Long messageId, Long groupId, + Integer readCount, Integer receiptStatus) { + return new ImMessageReceiptNotification() + .setId(messageId).setGroupId(groupId) + .setReadCount(readCount).setReceiptStatus(receiptStatus); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java new file mode 100644 index 0000000..0a88591 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/message/ImPrivateMessageNotification.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.im.service.websocket.notification.message; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * IM 私聊消息 WebSocket 统一推送通知 + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ImPrivateMessageNotification { + + /** + * 消息编号 + */ + private Long id; + /** + * 客户端消息编号 + */ + private String clientMessageId; + /** + * 发送人编号 + */ + private Long senderId; + /** + * 接收人编号 + */ + private Long receiverId; + /** + * 消息类型 + */ + private Integer type; + /** + * 消息内容 + */ + private String content; + /** + * 消息状态 + */ + private Integer status; + /** + * 回执状态 + */ + private Integer receiptStatus; + /** + * 发送时间 + */ + private LocalDateTime sendTime; + + // ========== 静态工厂方法 ========== + + /** + * 构建发送消息通知 + * + * @param message 私聊消息 DO + * @return 私聊通知 + */ + public static ImPrivateMessageNotification ofSend(ImPrivateMessageDO message) { + return BeanUtils.toBean(message, ImPrivateMessageNotification.class); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java new file mode 100644 index 0000000..7c2c251 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/package-info.java @@ -0,0 +1,14 @@ +/** + * IM WebSocket 在线通知对象。 + * + *

+ * 本包只放 WebSocket 外层通知对象,固定使用 im-notification 作为推送 type。 + * 具体业务 payload 放在子包中: + *

    + *
  • {@code message}:私聊、群聊、频道消息通知,以及消息内容结构;
  • + *
  • {@code friend}:好友关系和好友申请通知;
  • + *
  • {@code group}:群资料、群成员、加群申请通知;
  • + *
  • {@code rtc}:实时通话通知。
  • + *
+ */ +package cn.iocoder.yudao.module.im.service.websocket.notification; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallEndNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java index f0864e1..8c616db 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallEndNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallEndNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; import cn.iocoder.yudao.module.im.enums.rtc.ImRtcCallEndReasonEnum; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java index efa8037..e25f389 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; import cn.iocoder.yudao.module.im.enums.rtc.ImRtcParticipantStatusEnum; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallStartNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java index b73346b..a7a8596 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcCallStartNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcCallStartNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantConnectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java index 4bbfb48..dee51c9 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantConnectedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantConnectedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantDisconnectedNotification.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java index 94168c0..4aec61b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/notification/rtc/ImRtcParticipantDisconnectedNotification.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/websocket/notification/rtc/ImRtcParticipantDisconnectedNotification.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.im.service.websocket.dto.notification.rtc; +package cn.iocoder.yudao.module.im.service.websocket.notification.rtc; import cn.iocoder.yudao.module.im.dal.dataobject.rtc.ImRtcCallDO; import lombok.Data; diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java index 1ff958a..bdfa83f 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/util/ImMessageUtils.java @@ -4,8 +4,8 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; import java.util.Collection; import java.util.LinkedHashMap; @@ -39,9 +39,9 @@ public class ImMessageUtils { * @param content 消息内容 */ public static void validateUserMessageContent(Integer type, String content) { - ImMessageTypeEnum messageType; + ImContentTypeEnum messageType; try { - messageType = ImMessageTypeEnum.validate(type); + messageType = ImContentTypeEnum.validate(type); } catch (IllegalArgumentException e) { throw exception(MESSAGE_CONTENT_INVALID); } @@ -52,23 +52,23 @@ public class ImMessageUtils { if (map == null) { throw exception(MESSAGE_CONTENT_INVALID); } - if (ImMessageTypeEnum.TEXT == messageType) { + if (ImContentTypeEnum.TEXT == messageType) { validateTextContent(map); - } else if (ImMessageTypeEnum.IMAGE == messageType || ImMessageTypeEnum.FACE == messageType) { + } else if (ImContentTypeEnum.IMAGE == messageType || ImContentTypeEnum.FACE == messageType) { validateUrl(getString(map, "url")); - } else if (ImMessageTypeEnum.VOICE == messageType) { + } else if (ImContentTypeEnum.VOICE == messageType) { validateUrl(getString(map, "url")); validatePositiveNumber(map.get("duration")); - } else if (ImMessageTypeEnum.VIDEO == messageType) { + } else if (ImContentTypeEnum.VIDEO == messageType) { validateUrl(getString(map, "url")); - } else if (ImMessageTypeEnum.FILE == messageType) { + } else if (ImContentTypeEnum.FILE == messageType) { validateUrl(getString(map, "url")); validateNotBlank(getString(map, "name")); - } else if (ImMessageTypeEnum.CARD == messageType) { + } else if (ImContentTypeEnum.CARD == messageType) { validateCardContent(map); - } else if (ImMessageTypeEnum.MERGE == messageType) { + } else if (ImContentTypeEnum.MERGE == messageType) { validateMergeContent(map); - } else if (ImMessageTypeEnum.MATERIAL == messageType) { + } else if (ImContentTypeEnum.MATERIAL == messageType) { validateMaterialContent(map); } } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java new file mode 100644 index 0000000..b56845f --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/conversation/ImConversationReadMapperTest.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.im.dal.mysql.conversation; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.conversation.ImConversationReadDO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link ImConversationReadMapper} 的单元测试 + * + * @author 芋道源码 + */ +public class ImConversationReadMapperTest extends BaseDbUnitTest { + + @Resource + private ImConversationReadMapper mapper; + + @Test + public void testUpdateReadMessageIdToLarger_onlyAdvancesWhenLarger() { + // 准备:用户 1 在群 10 的读位置 = 50 + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L); + mapper.insert(read); + + // 1. 新位置更大 → 前进 + int advanced = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now()); + assertEquals(1, advanced); + assertEquals(100L, mapper.selectById(read.getId()).getMessageId()); + + // 2. 新位置相等 → 不更新 + int equalUpdate = mapper.updateReadMessageIdToLarger(read.getId(), 100L, LocalDateTime.now()); + assertEquals(0, equalUpdate); + + // 3. 新位置更小(乱序上报)→ 不回退 + int smaller = mapper.updateReadMessageIdToLarger(read.getId(), 80L, LocalDateTime.now()); + assertEquals(0, smaller); + assertEquals(100L, mapper.selectById(read.getId()).getMessageId()); + } + + @Test + public void testSelectByUserIdAndConversation() { + ImConversationReadDO read = buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L); + mapper.insert(read); + // 同用户不同会话类型,不应命中 + mapper.insert(buildRead(1L, ImConversationTypeEnum.PRIVATE.getType(), 10L, 99L)); + + ImConversationReadDO result = mapper.selectByUserIdAndConversation( + 1L, ImConversationTypeEnum.GROUP.getType(), 10L); + + assertNotNull(result); + assertEquals(50L, result.getMessageId()); + } + + @Test + public void testSelectListByConversation_aggregatesAllUsers() { + // 群 10 内三个用户的读位置,用于群回执人数聚合 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)); + mapper.insert(buildRead(2L, ImConversationTypeEnum.GROUP.getType(), 10L, 80L)); + mapper.insert(buildRead(3L, ImConversationTypeEnum.GROUP.getType(), 10L, 50L)); + // 别的群,不应混入 + mapper.insert(buildRead(1L, ImConversationTypeEnum.GROUP.getType(), 20L, 999L)); + + List list = mapper.selectListByConversation( + ImConversationTypeEnum.GROUP.getType(), 10L); + Map positions = convertMap(list, + ImConversationReadDO::getUserId, ImConversationReadDO::getMessageId); + + assertEquals(3, positions.size()); + assertEquals(100L, positions.get(1L)); + assertEquals(80L, positions.get(2L)); + assertEquals(50L, positions.get(3L)); + } + + @Test + public void testSelectListByUserIdAndConversations_batch() { + // 用户 1 在频道 10 / 20 / 30 的读位置 + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 10L, 5L)); + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 20L, 8L)); + mapper.insert(buildRead(1L, ImConversationTypeEnum.CHANNEL.getType(), 30L, 3L)); + + List list = mapper.selectListByUserIdAndConversations( + 1L, ImConversationTypeEnum.CHANNEL.getType(), List.of(10L, 20L)); + + assertEquals(2, list.size()); + } + + private ImConversationReadDO buildRead(Long userId, Integer conversationType, Long conversationId, + Long readMessageId) { + return ImConversationReadDO.builder() + .userId(userId) + .conversationType(conversationType) + .targetId(conversationId) + .messageId(readMessageId) + .readTime(LocalDateTime.now()) + .build(); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java index ed354df..ef7fb54 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendRequestMapperTest.java @@ -11,7 +11,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; /** - * IM 好友申请记录 Mapper 单元测试 + * {@link ImFriendRequestMapper} 的单元测试 * * @author 芋道源码 */ diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java index 2f1d093..5dcee93 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapperTest.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.im.dal.mysql.message; -import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; @@ -14,7 +13,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; /** - * IM 群聊消息 Mapper 单元测试 + * {@link ImGroupMessageMapper} 的单元测试 * * @author 芋道源码 */ @@ -28,46 +27,76 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { // ========== selectListByMinId ========== @Test + public void testSelectListByMinId_filterByReceiverSnapshot() { + // 准备:群 10 三条消息,快照分别命中 / 不命中用户 1 + ImGroupMessageDO visible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(visible); + ImGroupMessageDO invisible = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L)); + mapper.insert(invisible); + ImGroupMessageDO selfSend = buildMessage(10L, 1L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); + mapper.insert(selfSend); + + // 调用:用户 1 拉取群 10 + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + // 断言:仅返回快照包含用户 1 的消息 + assertEquals(2, result.size()); + assertEquals(visible.getId(), result.get(0).getId()); + assertEquals(selfSend.getId(), result.get(1).getId()); + } + + @Test + public void testSelectListByMinId_excludeNullAndEmptySnapshot() { + // 准备:快照为 null / 空字符串的老数据,不应被任何用户拉到 + ImGroupMessageDO nullSnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, null); + mapper.insert(nullSnapshot); + ImGroupMessageDO emptySnapshot = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of()); + mapper.insert(emptySnapshot); + + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); + + assertTrue(result.isEmpty()); + } + + @Test public void testSelectListByMinId_onlyReturnsGroupsInIdList() { - // 准备:群 10 的消息 + 群 20 的消息 - ImGroupMessageDO msg10 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + // 准备:群 10 + 群 20 各一条,用户 1 都在快照内 + ImGroupMessageDO msg10 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); mapper.insert(msg10); - ImGroupMessageDO msg20 = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO msg20 = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); mapper.insert(msg20); - // 调用:只查群 10 - List result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 100); + // 调用:候选群只含群 10 + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); - // 断言:只有 msg10 assertEquals(1, result.size()); assertEquals(msg10.getId(), result.get(0).getId()); } @Test public void testSelectListByMinId_includesRecall() { - // 准备:一条正常 + 一条撤回;客户端拉离线消息需要拿到撤回状态以渲染「此消息已撤回」占位 - ImGroupMessageDO normal = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + // 准备:撤回消息也要返回,由客户端按 status 渲染「此消息已撤回」占位 + ImGroupMessageDO normal = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(normal); - ImGroupMessageDO recalled = buildMessage(10L, 1L, ImMessageStatusEnum.RECALL); + ImGroupMessageDO recalled = buildMessage(10L, 2L, ImMessageStatusEnum.RECALL, List.of(1L)); mapper.insert(recalled); - List result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 100); + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 100); - // 断言:撤回消息一并返回,由客户端按 status 切换渲染 assertEquals(2, result.size()); } @Test public void testSelectListByMinId_sendTimeWindow() { // 准备:一条在窗口内,一条在窗口外 - ImGroupMessageDO inWindow = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO inWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); inWindow.setSendTime(LocalDateTime.now().minusDays(1)); mapper.insert(inWindow); - ImGroupMessageDO outWindow = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO outWindow = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); outWindow.setSendTime(LocalDateTime.now().minusDays(40)); mapper.insert(outWindow); - List result = mapper.selectListByMinId(ListUtil.of(10L), 0L, + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, LocalDateTime.now().minusDays(30), 100); assertEquals(1, result.size()); @@ -76,16 +105,16 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectListByMinId_sortAscLimit() { - // 准备:插入 3 条 - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + // 准备:插入 3 条可见消息 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m1); - ImGroupMessageDO m2 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m2); - ImGroupMessageDO m3 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m3); // 调用:size=2,返回 id 最小的 2 条 - List result = mapper.selectListByMinId(ListUtil.of(10L), 0L, FAR_PAST, 2); + List result = mapper.selectListByMinId(1L, List.of(10L), 0L, FAR_PAST, 2); assertEquals(2, result.size()); assertTrue(result.get(0).getId() < result.get(1).getId()); @@ -93,129 +122,63 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { assertEquals(m2.getId(), result.get(1).getId()); } - // ========== selectListByGroupIdAndMinIdAndQuitTimeBefore ========== - - @Test - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_onlyReturnsBeforeQuit() { - // 准备:退群时间 = now-1 day,插入一条"退群前",一条"退群后" - LocalDateTime quitTime = LocalDateTime.now().minusDays(1); - ImGroupMessageDO before = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - before.setSendTime(LocalDateTime.now().minusDays(2)); - mapper.insert(before); - ImGroupMessageDO after = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - after.setSendTime(LocalDateTime.now().minusHours(1)); - mapper.insert(after); - - List result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore( - 10L, 0L, FAR_PAST, quitTime, 100); - - // 断言:只返回退群前消息 - assertEquals(1, result.size()); - assertEquals(before.getId(), result.get(0).getId()); - } - @Test - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_boundaryEqualsQuitTime() { - // 准备:消息发送时间恰好等于退群时间(le 语义应包含该条) - LocalDateTime quitTime = LocalDateTime.now().minusDays(1).withNano(0); - ImGroupMessageDO boundary = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - boundary.setSendTime(quitTime); - mapper.insert(boundary); - - List result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore( - 10L, 0L, FAR_PAST, quitTime, 100); - - assertEquals(1, result.size()); - } + public void testSelectListByMinId_minIdExclusive() { + // 准备:游标之前的消息不返回 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m1); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); + mapper.insert(m2); - @Test - public void testSelectListByGroupIdAndMinIdAndQuitTimeBefore_otherGroupExcluded() { - // 准备:群 10 和群 20 各一条消息 - LocalDateTime quitTime = LocalDateTime.now(); - ImGroupMessageDO in10 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - in10.setSendTime(LocalDateTime.now().minusHours(2)); - mapper.insert(in10); - ImGroupMessageDO in20 = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD); - in20.setSendTime(LocalDateTime.now().minusHours(2)); - mapper.insert(in20); - - List result = mapper.selectListByGroupIdAndMinIdAndQuitTimeBefore( - 10L, 0L, FAR_PAST, quitTime, 100); + List result = mapper.selectListByMinId(1L, List.of(10L), m1.getId(), FAR_PAST, 100); assertEquals(1, result.size()); - assertEquals(in10.getId(), result.get(0).getId()); + assertEquals(m2.getId(), result.get(0).getId()); } - // ========== selectHistoryList ========== + // ========== selectHistoryListByUser ========== @Test - public void testSelectHistoryList_basic() { - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + public void testSelectHistoryListByUser_filterByReceiverSnapshot() { + // 准备:群 10 三条,用户 1 在前两条快照内、第三条定向给别人 + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); mapper.insert(m1); - ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L, 2L)); mapper.insert(m2); + ImGroupMessageDO directedOther = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(2L, 3L)); + mapper.insert(directedOther); // 别的群 - ImGroupMessageDO other = buildMessage(20L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO other = buildMessage(20L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(other); - List result = mapper.selectHistoryList(10L, null, 100, null); - - // 断言:只返回 10 群的,按 id 倒序 - assertEquals(2, result.size()); - assertTrue(result.get(0).getId() > result.get(1).getId()); - } - - @Test - public void testSelectHistoryList_includeRecall() { - ImGroupMessageDO normal = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - mapper.insert(normal); - ImGroupMessageDO recalled = buildMessage(10L, 1L, ImMessageStatusEnum.RECALL); - mapper.insert(recalled); - - List result = mapper.selectHistoryList(10L, null, 100, null); + List result = mapper.selectHistoryListByUser(1L, 10L, null, 100); + // 断言:只返回群 10 中用户 1 可见的,按 id 倒序 assertEquals(2, result.size()); - assertEquals(recalled.getId(), result.get(0).getId()); - assertEquals(normal.getId(), result.get(1).getId()); + assertEquals(m2.getId(), result.get(0).getId()); + assertEquals(m1.getId(), result.get(1).getId()); } @Test - public void testSelectHistoryList_maxIdCursor() { - ImGroupMessageDO m1 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + public void testSelectHistoryListByUser_maxIdCursor() { + ImGroupMessageDO m1 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m1); - ImGroupMessageDO m2 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO m2 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m2); - ImGroupMessageDO m3 = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); + ImGroupMessageDO m3 = buildMessage(10L, 2L, ImMessageStatusEnum.NORMAL, List.of(1L)); mapper.insert(m3); - List result = mapper.selectHistoryList(10L, m3.getId(), 100, null); + List result = mapper.selectHistoryListByUser(1L, 10L, m3.getId(), 100); - // 断言:只返回 id < m3.id 的,即 m1、m2 + // 断言:只返回 id < m3.id 的 m1、m2 assertEquals(2, result.size()); assertTrue(result.stream().allMatch(m -> m.getId() < m3.getId())); } - @Test - public void testSelectHistoryList_filterByJoinTime() { - // 准备:一条在入群前,一条在入群后 - LocalDateTime joinTime = LocalDateTime.now().minusHours(1); - ImGroupMessageDO before = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - before.setSendTime(LocalDateTime.now().minusHours(2)); - mapper.insert(before); - ImGroupMessageDO after = buildMessage(10L, 1L, ImMessageStatusEnum.UNREAD); - after.setSendTime(LocalDateTime.now().minusMinutes(30)); - mapper.insert(after); - - List result = mapper.selectHistoryList(10L, null, 100, joinTime); - - // 断言:入群前消息不可见 - assertEquals(1, result.size()); - assertEquals(after.getId(), result.get(0).getId()); - } - // ========== 工具方法 ========== - private ImGroupMessageDO buildMessage(Long groupId, Long senderId, ImMessageStatusEnum status) { + private ImGroupMessageDO buildMessage(Long groupId, Long senderId, ImMessageStatusEnum status, + List receiverUserIds) { return ImGroupMessageDO.builder() .clientMessageId("uuid-" + System.nanoTime()) .senderId(senderId) @@ -224,7 +187,8 @@ public class ImGroupMessageMapperTest extends BaseDbUnitTest { .content("{\"content\":\"test\"}") .status(status.getStatus()) .sendTime(LocalDateTime.now()) - .receiptStatus(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) + .receiverUserIds(receiverUserIds) + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) .build(); } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java index e67b3e5..f3b5ee3 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java @@ -12,7 +12,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; /** - * IM 私聊消息 Mapper 单元测试 + * {@link ImPrivateMessageMapper} 的单元测试 * * @author 芋道源码 */ @@ -21,9 +21,6 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Resource private ImPrivateMessageMapper mapper; - /** - * 一个足够早的时间作为 minSendTime,避免把测试数据过滤掉 - */ private static final LocalDateTime FAR_PAST = LocalDateTime.of(2000, 1, 1, 0, 0, 0); // ========== selectListByMinId ========== @@ -31,13 +28,13 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectListByMinId() { // 准备:用户 1 发给用户 2 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg1); // 用户 2 发给用户 1 - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); mapper.insert(msg2); // 用户 3 发给用户 4(不相关) - ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg3 = buildMessage(3L, 4L, ImMessageStatusEnum.NORMAL); mapper.insert(msg3); // 调用:用户 1 从 id=0 拉取 @@ -52,9 +49,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectListByMinId_withMinId() { // 准备 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg1); - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); mapper.insert(msg2); // 调用:从 msg1.id 之后拉取 @@ -68,9 +65,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectListByMinId_limitSize() { // 准备:插入 3 条 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); - mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD)); - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); // 调用:limit 2 List result = mapper.selectListByMinId(1L, 0L, FAR_PAST, 2); @@ -82,10 +79,10 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectListByMinId_sendTimeFilter() { // 准备:一条落在窗口内,一条落在窗口外 - ImPrivateMessageDO newMsg = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO newMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); newMsg.setSendTime(LocalDateTime.now().minusDays(1)); mapper.insert(newMsg); - ImPrivateMessageDO oldMsg = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO oldMsg = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); oldMsg.setSendTime(LocalDateTime.now().minusDays(40)); mapper.insert(oldMsg); @@ -103,12 +100,12 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectHistoryList_basic() { // 准备:用户 1 <-> 用户 2 的消息 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg1); - ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg2 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); mapper.insert(msg2); // 用户 1 <-> 用户 3 的消息(不相关) - ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg3 = buildMessage(1L, 3L, ImMessageStatusEnum.NORMAL); mapper.insert(msg3); // 调用 @@ -122,7 +119,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectHistoryList_includeRecall() { // 准备 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg1); ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.RECALL); mapper.insert(msg2); @@ -139,11 +136,11 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectHistoryList_withMaxId() { // 准备 - ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg1 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg1); - ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg2 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg2); - ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD); + ImPrivateMessageDO msg3 = buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL); mapper.insert(msg3); // 调用:从 msg3 往前拉 @@ -157,9 +154,9 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectHistoryList_limit() { // 准备 - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); - mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD)); - mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.UNREAD)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL)); + mapper.insert(buildMessage(1L, 2L, ImMessageStatusEnum.NORMAL)); // 调用:limit 2 List result = mapper.selectHistoryList(1L, 2L, null, 2); @@ -171,7 +168,7 @@ public class ImPrivateMessageMapperTest extends BaseDbUnitTest { @Test public void testSelectHistoryList_bidirectional() { // 准备:验证双向查询 - ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.UNREAD); // 对方发的 + ImPrivateMessageDO msg1 = buildMessage(2L, 1L, ImMessageStatusEnum.NORMAL); // 对方发的 mapper.insert(msg1); // 调用:以用户 1 的视角查 diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java index 4808da4..7c50033 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java +++ b/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; import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,7 +25,7 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.*; /** - * IM 数据看板 Mapper 单元测试 + * {@link ImStatisticsManagerMapper} 的单元测试 * * @author 芋道源码 */ @@ -194,10 +194,10 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @Test public void testSelectMessageTypeDistribution_mergePrivateAndGroup() { // 准备:私聊 type=0 ×1,群聊 type=0 ×2、type=1 ×1 - privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType())); - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType())); - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.TEXT.getType())); - groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImMessageTypeEnum.IMAGE.getType())); + privateMessageMapper.insert(buildPrivate(1L, 2L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.TEXT.getType())); + groupMessageMapper.insert(buildGroupMessage(1L, 100L, WINDOW_BEGIN.plusDays(1)).setType(ImContentTypeEnum.IMAGE.getType())); // 调用 List> dist = mapper.selectMessageTypeDistribution(WINDOW_BEGIN, WINDOW_END); @@ -205,8 +205,8 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { // 断言:type=TEXT(0) → 3;type=IMAGE(1) → 1 Map byType = dist.stream().collect(java.util.stream.Collectors.toMap( m -> ((Number) m.get("type")).intValue(), m -> ((Number) m.get("count")).longValue())); - assertEquals(3L, byType.get(ImMessageTypeEnum.TEXT.getType())); - assertEquals(1L, byType.get(ImMessageTypeEnum.IMAGE.getType())); + assertEquals(3L, byType.get(ImContentTypeEnum.TEXT.getType())); + assertEquals(1L, byType.get(ImContentTypeEnum.IMAGE.getType())); } @Test @@ -243,9 +243,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { return ImPrivateMessageDO.builder() .clientMessageId("uuid-" + System.nanoTime()) .senderId(senderId).receiverId(receiverId) - .type(ImMessageTypeEnum.TEXT.getType()) + .type(ImContentTypeEnum.TEXT.getType()) .content("{}") - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(sendTime).build(); } @@ -253,9 +253,9 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { return new ImGroupMessageDO() .setClientMessageId("uuid-" + System.nanoTime()) .setSenderId(senderId).setGroupId(groupId) - .setType(ImMessageTypeEnum.TEXT.getType()) + .setType(ImContentTypeEnum.TEXT.getType()) .setContent("{}") - .setStatus(ImMessageStatusEnum.UNREAD.getStatus()) + .setStatus(ImMessageStatusEnum.NORMAL.getStatus()) .setSendTime(sendTime); } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java index d585074..b14f4ea 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackItemServiceImplTest.java +++ b/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; import static org.mockito.Mockito.when; /** - * IM 表情包项 Service 单元测试 + * {@link ImFacePackItemServiceImpl} 的单元测试 * * @author 芋道源码 */ diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java index 2cf1f01..1b7c8ab 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFacePackServiceImplTest.java +++ b/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; import static org.mockito.Mockito.when; /** - * IM 表情包 Service 单元测试 + * {@link ImFacePackServiceImpl} 的单元测试 * * @author 芋道源码 */ diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java index 3cf7f33..94ca142 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/face/ImFaceUserItemServiceImplTest.java +++ b/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; import static org.mockito.Mockito.when; /** - * IM 用户私有表情 Service 单元测试 + * {@link ImFaceUserItemServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -141,7 +141,7 @@ public class ImFaceUserItemServiceImplTest extends BaseMockitoUnitTest { verify(faceUserItemMapper, never()).deleteById(anyLong()); } - // ========== deleteFaceUserItem(管理后台 by id) ========== + // ========== deleteFaceUserItem(管理后台) ========== @Test public void testDeleteFaceUserItemAdmin_success() { diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java index 0bbf441..2697e58 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.im.service.friend; -import cn.hutool.core.collection.ListUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; @@ -12,7 +11,6 @@ import cn.iocoder.yudao.module.im.enums.friend.ImFriendRequestHandleResultEnum; import cn.iocoder.yudao.module.im.enums.friend.ImFriendStateEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.junit.jupiter.api.Test; @@ -36,7 +34,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * IM 好友申请 Service 单元测试 + * {@link ImFriendRequestServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -113,7 +111,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { assertNull(result); verify(friendService).silentReAddFriend(eq(1L), eq(2L), eq("老张"), eq(1)); verify(friendRequestMapper, never()).insert(any(ImFriendRequestDO.class)); - verify(websocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -158,7 +156,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), saved.getHandleResult()); assertSame(saved, result); // 断言:推送给接收方 - verify(websocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); } @Test @@ -211,7 +209,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { any(LocalDateTime.class)); assertEquals(100L, result.getId()); assertEquals(ImFriendRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); - verify(websocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); } // ========== agreeFriendRequest ========== @@ -229,9 +227,9 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { friendRequestService.agreeFriendRequest(2L, 100L); // 断言:双向建立好友 + 推 APPROVED 给发起方 - verify(adminUserApi).validateUserList(ListUtil.of(1L, 2L)); + verify(adminUserApi, never()).validateUserList(any()); verify(friendService).becomeFriends(request); - verify(websocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); } @Test @@ -306,7 +304,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { eq(ImFriendRequestHandleResultEnum.UNHANDLED.getResult()), captor.capture()); assertEquals(ImFriendRequestHandleResultEnum.REFUSED.getResult(), captor.getValue().getHandleResult()); assertEquals("不认识", captor.getValue().getHandleContent()); - verify(websocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); } @Test @@ -321,7 +319,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { ServiceException exception = assertThrows(ServiceException.class, () -> friendRequestService.refuseFriendRequest(2L, 100L, "x")); assertEquals(FRIEND_REQUEST_HANDLED.getCode(), exception.getCode()); - verify(websocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } // ========== getMyFriendRequestList ========== diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java index 1d8aafc..b0d4d18 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -30,7 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * IM 好友关系 Service 单元测试 + * {@link ImFriendServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -69,7 +68,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { assertEquals(100L, captor.getValue().getId()); assertTrue(captor.getValue().getSilent()); // 断言:推送了好友更新通知 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); } @Test @@ -101,7 +100,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { () -> friendService.updateFriend(1L, reqVO)); assertEquals(FRIEND_NOT_FRIEND.getCode(), exception.getCode()); verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); - verify(imWebSocketService, never()).sendPrivateMessageAsync(any(Long.class), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any()); } @Test @@ -124,7 +123,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { assertEquals("老张", captor.getValue().getDisplayName()); assertNull(captor.getValue().getSilent()); // 断言:推送好友更新通知 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); } @Test @@ -139,10 +138,10 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { // 断言:没查记录、没触发 SQL 更新 / 没发 WebSocket 推送 verify(imFriendMapper, never()).selectByUserIdAndFriendUserId(anyLong(), anyLong()); verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); - verify(imWebSocketService, never()).sendPrivateMessageAsync(any(Long.class), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(any(Long.class), anyInt(), anyInt(), any()); } - // ========== addFriend0(内部方法,被 becomeFriends / silentReAddFriend 调用) ========== + // ========== 建立好友 ========== @Test public void testAddFriend0_existingEnabledSkip() { @@ -158,7 +157,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); verify(imFriendMapper, never()).updateById(any(ImFriendDO.class)); verify(imFriendMapper, never()).updateReAddFields(anyLong(), anyInt(), any(LocalDateTime.class), - anyBoolean(), anyBoolean(), anyBoolean(), any(), any()); + any(LocalDateTime.class), anyBoolean(), anyBoolean(), anyBoolean(), any(), any()); } @Test @@ -173,7 +172,7 @@ public class ImFriendServiceImplTest extends BaseMockitoUnitTest { // 断言:恢复 ENABLE,并清空 deleteTime verify(imFriendMapper).updateReAddFields(eq(10L), eq(CommonStatusEnum.ENABLE.getStatus()), - any(LocalDateTime.class), eq(false), eq(false), eq(false), isNull(), isNull()); + any(LocalDateTime.class), any(LocalDateTime.class), eq(false), eq(false), eq(false), isNull(), isNull()); verify(imFriendMapper, never()).insert(any(ImFriendDO.class)); } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java index 7d2dc75..ba4e0a8 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -24,7 +24,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** - * IM 群成员 Service 单元测试 + * {@link ImGroupMemberServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -242,7 +242,7 @@ public class ImGroupMemberServiceImplTest extends BaseMockitoUnitTest { verify(groupMemberMapper).updateById(captor.capture()); assertEquals(50L, captor.getValue().getId()); assertTrue(captor.getValue().getSilent()); - // 公开字段昵称变化 → 全员广播 GROUP_MEMBER_NICKNAME_UPDATE + // 公开字段昵称变化 → 全员在线同步 GROUP_MEMBER_NICKNAME_UPDATE verify(groupMessageService).sendGroupMessage(eq(1L), any(cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO.class)); // 个人字段 silent 变化 → 仅自己多端同步 GROUP_MEMBER_SETTING_UPDATE verify(groupMessageService).sendGroupMessage(eq(1L), eq(ListUtil.of(1L)), diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java index c4f41a7..0d05e10 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupRequestHandleResultEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.junit.jupiter.api.Test; @@ -35,7 +34,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** - * IM 加群申请 Service 单元测试 + * {@link ImGroupRequestServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -84,7 +83,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { // 推 1510 自由进群 ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); } @Test @@ -117,7 +116,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), anyInt(), anyLong()); verify(groupRequestMapper).insert(any(ImGroupRequestDO.class)); // 1503 推送给 owner(99) + admin(98),去重后两条 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -150,7 +149,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { eq(ImGroupAddSourceEnum.SEARCH.getSource()), any()); assertEquals(50L, result.getId()); assertEquals(ImGroupRequestHandleResultEnum.UNHANDLED.getResult(), result.getHandleResult()); - verify(websocketService).sendPrivateMessageAsync(eq(99L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any()); } @Test @@ -207,9 +206,9 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { eq(ImGroupAddSourceEnum.SEARCH.getSource()), isNull()); ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_ENTER.getType(), dtoCaptor.getValue().getType()); // 1505 推送给申请人 + owner,去重后两条 - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -279,7 +278,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { // 不写群成员;推 1506 给申请人 + 群主(同一人 1L 时去重为 1 + 申请人 2L = 2 条) verify(groupMemberService, never()).addGroupMember(anyLong(), anyLong(), anyInt(), any(), any()); - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } // ==================== createInviteRequestList ==================== @@ -306,7 +305,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { // 断言:插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧 ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupRequestDO.class); verify(groupRequestMapper, times(2)).insert(captor.capture()); - verify(websocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(websocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); // 断言:每条记录 inviterUserId=1 + addSource=INVITE,避免审批通过后回写群成员留痕的来源为空 / 脏带旧值 Collection inserted = captor.getAllValues(); assertEquals(2, inserted.size()); @@ -338,7 +337,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { // 断言:复用并重置旧邀请申请 verify(groupRequestMapper).updateInviteByIdReset(eq(50L), eq(1L), eq(ImGroupAddSourceEnum.INVITE.getSource()), any()); - verify(websocketService).sendPrivateMessageAsync(eq(99L), any(ImPrivateMessageDTO.class)); + verify(websocketService).sendNotificationAsync(eq(99L), anyInt(), anyInt(), any()); } private AdminUserRespDTO buildUser(Long id, String nickname) { diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java index 044a91d..d75e363 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImplTest.java @@ -1,8 +1,5 @@ package cn.iocoder.yudao.module.im.service.group; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.map.MapUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; @@ -22,13 +19,13 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; import cn.iocoder.yudao.module.im.enums.group.ImGroupAddSourceEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.junit.jupiter.api.Test; @@ -51,7 +48,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** - * IM 群 Service 单元测试 + * {@link ImGroupServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -103,7 +100,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 验证:推送 GROUP_CREATE 通知(payload memberUserIds 含创建者自己) ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); } @Test @@ -111,13 +108,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 准备:创建者 + 2 个初始成员,都是好友 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); reqVO.setName("测试群"); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); when(groupMapper.insert(any(ImGroupDO.class))).thenAnswer(invocation -> { ImGroupDO group = invocation.getArgument(0); group.setId(100L); return 1; }); - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImFriendDO.builder().userId(1L).friendUserId(3L) @@ -135,7 +132,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 验证:推送 GROUP_CREATE 通知,payload memberUserIds 含全员(创建者 + 邀请) ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_CREATE.getType(), dtoCaptor.getValue().getType()); } @Test @@ -143,15 +140,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 准备:初始成员里有非好友 ImGroupCreateReqVO reqVO = new ImGroupCreateReqVO(); reqVO.setName("测试群"); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); // 只有 2 是好友,3 不是 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build())); AdminUserRespDTO u3 = new AdminUserRespDTO(); u3.setId(3L); u3.setNickname("李四"); - when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(3L, u3)); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3)); // 调用并断言 ServiceException exception = assertThrows(ServiceException.class, @@ -208,7 +205,59 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 推送 GROUP_NAME_UPDATE 通知给全员 ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_NAME_UPDATE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_NAME_UPDATE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testUpdateGroup_joinApprovalChanged() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .joinApproval(false).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setJoinApproval(true); + + // 调用 + ImGroupDO result = groupService.updateGroup(reqVO, 1L); + + // 断言 + verify(groupMapper).updateById(any(ImGroupDO.class)); + assertTrue(result.getJoinApproval()); + ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); + verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); + assertEquals(ImContentTypeEnum.GROUP_INFO_UPDATE.getType(), dtoCaptor.getValue().getType()); + } + } + + @Test + public void testUpdateGroup_joinApprovalSame() { + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupServiceImpl.class))) + .thenReturn(groupService); + + // 准备 + ImGroupDO group = ImGroupDO.builder().id(10L).name("群").ownerUserId(1L) + .joinApproval(true).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMapper.selectById(10L)).thenReturn(group); + + ImGroupUpdateReqVO reqVO = new ImGroupUpdateReqVO(); + reqVO.setId(10L); + reqVO.setJoinApproval(true); + + // 调用 + ImGroupDO result = groupService.updateGroup(reqVO, 1L); + + // 断言 + verify(groupMapper).updateById(any(ImGroupDO.class)); + assertTrue(result.getJoinApproval()); + verify(groupMessageService, never()).sendGroupMessage(anyLong(), anyCollection(), any()); } } @@ -234,11 +283,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { assertEquals(CommonStatusEnum.DISABLE.getStatus(), captor.getValue().getStatus()); assertNotNull(captor.getValue().getDissolvedTime()); verify(groupMemberService).removeGroupMembersByGroupId(10L); - verify(groupMessageService).deleteReadMaxMessageIdMap(10L); // 推送 GROUP_DISSOLVE 通知(send-before-remove,sendGroupMessage 内部查 active 自动覆盖全员,含群主多端同步) ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); } } @@ -280,9 +328,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 断言:使用管理员编号发通知并完成清理 ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(99L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_DISSOLVE.getType(), dtoCaptor.getValue().getType()); verify(groupMemberService).removeGroupMembersByGroupId(10L); - verify(groupMessageService).deleteReadMaxMessageIdMap(10L); } } @@ -330,8 +377,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { // 被邀请人 2 和 3 都是好友 ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); - List friends = ListUtil.of( + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + List friends = List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImFriendDO.builder().userId(1L).friendUserId(3L) @@ -347,10 +394,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { verify(groupMemberService).addGroupMembers(eq(10L), anyCollection(), eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); verify(groupRequestService, never()).createInviteRequestList(anyLong(), anyLong(), anyCollection()); - verify(webSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); } } @@ -369,7 +416,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.NORMAL.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build()); - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build(), @@ -379,8 +426,8 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImFriendDO.builder().userId(1L).friendUserId(3L) @@ -411,15 +458,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build()); - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImFriendDO.builder().userId(1L).friendUserId(3L) @@ -434,7 +481,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { eq(ImGroupAddSourceEnum.INVITE.getSource()), eq(1L)); ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), anyCollection(), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_INVITE.getType(), dtoCaptor.getValue().getType()); } } @@ -448,18 +495,18 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectById(10L)).thenReturn(group); when(groupMemberService.getActiveGroupMemberListByGroupId(10L)) - .thenReturn(ListUtil.of(ImGroupMemberDO.builder().groupId(10L).userId(1L) + .thenReturn(List.of(ImGroupMemberDO.builder().groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build())); ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L, 3L))); + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L, 3L))); // 只有 2 是好友,3 不是 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(2L) .status(CommonStatusEnum.ENABLE.getStatus()).build())); AdminUserRespDTO u3 = new AdminUserRespDTO(); u3.setId(3L); u3.setNickname("李四"); - when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(3L, u3)); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(Map.of(3L, u3)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.inviteGroupMember(1L, reqVO)); @@ -487,9 +534,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(600L, 601L))); + reqVO.setMemberUserIds(new ArrayList<>(List.of(600L, 601L))); // 被邀请人都是好友 - when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(ListUtil.of( + when(friendService.getActiveFriendList(eq(1L), anyCollection())).thenReturn(List.of( ImFriendDO.builder().userId(1L).friendUserId(600L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImFriendDO.builder().userId(1L).friendUserId(601L) @@ -515,7 +562,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectById(10L)).thenReturn(group); // 用户 2 已在群中 - when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(ListUtil.of( + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImGroupMemberDO.builder().groupId(10L).userId(2L) @@ -524,14 +571,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberInviteReqVO reqVO = new ImGroupMemberInviteReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(new ArrayList<>(ListUtil.of(2L))); // 只邀请 2,他已在群中 + reqVO.setMemberUserIds(new ArrayList<>(List.of(2L))); // 只邀请 2,他已在群中 // 调用 groupService.inviteGroupMember(1L, reqVO); // 断言:不会触发添加、不会推送 verify(groupMemberService, never()).addGroupMembers(anyLong(), anyCollection()); - verify(webSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); + verify(webSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); } } @@ -566,11 +613,10 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { groupService.quitGroup(10L, 1L); verify(groupMemberService).removeGroupMember(10L, 1L); - verify(groupMessageService).deleteReadMaxMessageId(10L, 1L); // 推送 GROUP_MEMBER_QUIT 通知给全员(含 quitter,前端自判清群) ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_QUIT.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_QUIT.getType(), dtoCaptor.getValue().getType()); } } @@ -587,7 +633,6 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { groupService.quitGroup(10L, 1L); verify(groupMemberService).removeGroupMember(10L, 1L); - verify(groupMessageService).deleteReadMaxMessageId(10L, 1L); verify(groupMessageService).sendGroupMessage(eq(1L), any(ImGroupMessageSendDTO.class)); } } @@ -609,7 +654,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(ListUtil.of(1L, 2L)); + reqVO.setMemberUserIds(List.of(1L, 2L)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.removeGroupMember(1L, reqVO)); @@ -631,7 +676,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); // 目标:两个普通成员 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(2L) .role(ImGroupMemberRoleEnum.NORMAL.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build(), @@ -641,16 +686,15 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(ListUtil.of(2L, 3L)); + reqVO.setMemberUserIds(List.of(2L, 3L)); groupService.removeGroupMember(1L, reqVO); verify(groupMemberService).removeGroupMembers(eq(10L), anyCollection()); - verify(groupMessageService).deleteReadMaxMessageIds(eq(10L), anyCollection()); // 推送 GROUP_MEMBER_KICK 通知给全员(含被踢者,前端自判清群) ArgumentCaptor dtoCaptor = ArgumentCaptor.forClass(ImGroupMessageSendDTO.class); verify(groupMessageService).sendGroupMessage(eq(1L), dtoCaptor.capture()); - assertEquals(ImMessageTypeEnum.GROUP_MEMBER_KICK.getType(), dtoCaptor.getValue().getType()); + assertEquals(ImContentTypeEnum.GROUP_MEMBER_KICK.getType(), dtoCaptor.getValue().getType()); } } @@ -668,14 +712,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build()); // 目标:另一个管理员 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(2L) .role(ImGroupMemberRoleEnum.ADMIN.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(ListUtil.of(2L)); + reqVO.setMemberUserIds(List.of(2L)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.removeGroupMember(1L, reqVO)); @@ -697,14 +741,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(ListUtil.of(99L)); + reqVO.setMemberUserIds(List.of(99L)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.removeGroupMember(1L, reqVO)); @@ -726,7 +770,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()).build()); // 目标:2L 有效普通成员;3L 已退群(DISABLE)的历史管理员,应被跳过而非拦截整批 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().groupId(10L).userId(2L) .role(ImGroupMemberRoleEnum.NORMAL.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build(), @@ -736,17 +780,14 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberRemoveReqVO reqVO = new ImGroupMemberRemoveReqVO(); reqVO.setGroupId(10L); - reqVO.setMemberUserIds(ListUtil.of(2L, 3L)); + reqVO.setMemberUserIds(List.of(2L, 3L)); groupService.removeGroupMember(1L, reqVO); - // 仅有效成员 2L 进入移除 / 已读清理,已退群的 3L 被跳过 + // 仅有效成员 2L 进入移除,已退群的 3L 被跳过 ArgumentCaptor removeCaptor = ArgumentCaptor.forClass(Collection.class); verify(groupMemberService).removeGroupMembers(eq(10L), removeCaptor.capture()); - assertEquals(CollUtil.newHashSet(2L), CollUtil.newHashSet(removeCaptor.getValue())); - ArgumentCaptor readCaptor = ArgumentCaptor.forClass(Collection.class); - verify(groupMessageService).deleteReadMaxMessageIds(eq(10L), readCaptor.capture()); - assertEquals(CollUtil.newHashSet(2L), CollUtil.newHashSet(readCaptor.getValue())); + assertEquals(Set.of(2L), Set.copyOf(removeCaptor.getValue())); } } @@ -762,7 +803,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); // 目标 3 是普通成员 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(3L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); // 群里已有 1 个 ADMIN,1 + 1 ≤ 3 不超上限 @@ -773,7 +814,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(3L)); + reqVO.setUserIds(List.of(3L)); groupService.addGroupAdmin(1L, reqVO); @@ -792,7 +833,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(5L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); // 群里已有 3 个 ADMIN(达到上限),再加 1 会超 @@ -801,7 +842,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(5L)); + reqVO.setUserIds(List.of(5L)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.addGroupAdmin(1L, reqVO)); @@ -819,13 +860,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(1L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.OWNER.getRole()).build())); ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(1L)); + reqVO.setUserIds(List.of(1L)); ServiceException exception = assertThrows(ServiceException.class, () -> groupService.addGroupAdmin(1L, reqVO)); @@ -843,13 +884,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); // 目标已是 ADMIN:再加无需操作 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); ImGroupAdminAddReqVO reqVO = new ImGroupAdminAddReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(2L)); + reqVO.setUserIds(List.of(2L)); groupService.addGroupAdmin(1L, reqVO); @@ -869,7 +910,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.ADMIN.getRole()).build())); when(groupMemberService.updateGroupMemberRole(eq(10L), anyCollection(), @@ -877,7 +918,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(2L)); + reqVO.setUserIds(List.of(2L)); groupService.removeGroupAdmin(1L, reqVO); @@ -897,13 +938,13 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMapper.selectByIdForUpdate(10L)).thenReturn(group); // 目标已是 MEMBER:撤销无需操作 - when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(ListUtil.of( + when(groupMemberService.getGroupMembers(eq(10L), anyCollection())).thenReturn(List.of( ImGroupMemberDO.builder().userId(2L).status(CommonStatusEnum.ENABLE.getStatus()) .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build())); ImGroupAdminRemoveReqVO reqVO = new ImGroupAdminRemoveReqVO(); reqVO.setId(10L); - reqVO.setUserIds(ListUtil.of(2L)); + reqVO.setUserIds(List.of(2L)); groupService.removeGroupAdmin(1L, reqVO); @@ -925,9 +966,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { when(groupMemberService.validateMemberInGroup(10L, 2L)).thenReturn( ImGroupMemberDO.builder().groupId(10L).userId(2L) .role(ImGroupMemberRoleEnum.NORMAL.getRole()).build()); - when(groupMemberService.updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(2L)), + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(2L)), eq(ImGroupMemberRoleEnum.OWNER.getRole()))).thenReturn(1); - when(groupMemberService.updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(1L)), + when(groupMemberService.updateGroupMemberRole(eq(10L), eq(Set.of(1L)), eq(ImGroupMemberRoleEnum.NORMAL.getRole()))).thenReturn(1); ImGroupTransferOwnerReqVO reqVO = new ImGroupTransferOwnerReqVO(); @@ -941,9 +982,9 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { verify(groupMapper).updateById(groupCaptor.capture()); assertEquals(2L, groupCaptor.getValue().getOwnerUserId()); // 旧群主 → MEMBER;新群主 → OWNER - verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(1L)), + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(1L)), eq(ImGroupMemberRoleEnum.NORMAL.getRole())); - verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(CollUtil.newHashSet(2L)), + verify(groupMemberService).updateGroupMemberRole(eq(10L), eq(Set.of(2L)), eq(ImGroupMemberRoleEnum.OWNER.getRole())); } } @@ -1111,9 +1152,7 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @Test public void testGetMyGroupList_noMembers() { - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>()); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(new ArrayList<>()); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>()); List result = groupService.getMyGroupList(1L); assertTrue(result.isEmpty()); @@ -1122,20 +1161,16 @@ public class ImGroupServiceImplTest extends BaseMockitoUnitTest { @Test public void testGetMyGroupList_success() { - // 活跃群成员 - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>(ListUtil.of( + // 曾经加入的所有群(含退群) + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(new ArrayList<>(List.of( ImGroupMemberDO.builder().groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImGroupMemberDO.builder().groupId(20L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()).build() + .status(CommonStatusEnum.ENABLE.getStatus()).build(), + ImGroupMemberDO.builder().groupId(30L).userId(1L) + .status(CommonStatusEnum.DISABLE.getStatus()).build() ))); - // 最近退群成员(最近 30 天内) - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(new ArrayList<>(ListUtil.of( - ImGroupMemberDO.builder().groupId(30L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()).build() - ))); - List groups = ListUtil.of( + List groups = List.of( ImGroupDO.builder().id(10L).status(CommonStatusEnum.ENABLE.getStatus()).build(), ImGroupDO.builder().id(20L).status(CommonStatusEnum.ENABLE.getStatus()).build(), ImGroupDO.builder().id(30L).status(CommonStatusEnum.ENABLE.getStatus()).build() diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java new file mode 100644 index 0000000..bbe516a --- /dev/null +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImplTest.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.im.service.message; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImChannelMessageDO; +import cn.iocoder.yudao.module.im.dal.mysql.message.ImChannelMessageMapper; +import cn.iocoder.yudao.module.im.service.channel.ImChannelMaterialService; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * {@link ImChannelMessageServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +public class ImChannelMessageServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private ImChannelMessageServiceImpl channelMessageService; + + @Mock + private ImChannelMessageMapper channelMessageMapper; + @Mock + private ImChannelMaterialService channelMaterialService; + @Mock + private ImWebSocketService webSocketService; + @Mock + private ImConversationReadService conversationReadService; + + @Test + public void testReadChannelMessages_messageNotExists() { + // 准备:messageId 不存在(伪造 / 未来 id) + when(channelMessageMapper.selectById(999L)).thenReturn(null); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 999L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_wrongChannel() { + // 准备:消息属于别的频道 + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(20L).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + + // 调用:声称读的是频道 10 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_notVisible() { + // 准备:定向消息,接收人不含当前用户 + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(10L).receiverUserIds(List.of(2L, 3L)).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:不推进读位置 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + } + + @Test + public void testReadChannelMessages_notAdvanced() { + // 准备:消息真实可见,但读位置未前进(updateConversationReadPosition 返回 false) + ImChannelMessageDO message = ImChannelMessageDO.builder() + .id(100L).channelId(10L).sendTime(LocalDateTime.now()).build(); + when(channelMessageMapper.selectById(100L)).thenReturn(message); + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(false); + + // 调用 + channelMessageService.readChannelMessages(1L, 10L, 100L); + + // 断言:读位置未前进 → 不推 READ 事件 + verify(webSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); + } + +} diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java index dc5aba0..ffa1bb5 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java @@ -1,7 +1,5 @@ package cn.iocoder.yudao.module.im.service.message; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ListUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; @@ -12,19 +10,20 @@ import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; -import cn.iocoder.yudao.module.im.dal.redis.message.ImGroupMessageReadRedisDAO; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; import cn.iocoder.yudao.module.im.enums.group.ImGroupMemberRoleEnum; -import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.message.dto.ImGroupMessageSendDTO; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -41,7 +40,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** - * IM 群聊消息 Service 单元测试 + * {@link ImGroupMessageServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -59,11 +58,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Mock private ImSensitiveWordService sensitiveWordService; @Mock - private ImGroupMessageReadRedisDAO groupMessageReadRedisDAO; + private ImConversationReadService conversationReadService; @Mock private ImWebSocketService imWebSocketService; - /** 用真实实例避免 NPE;默认值与生产保持一致(recallTimeoutMinutes=5、private/group read enabled=true、maxPullSize=1000);个别用例可改字段测分支 */ @Spy private ImProperties imProperties = new ImProperties(); @@ -71,7 +69,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ImGroupMessageSendReqVO reqVO = new ImGroupMessageSendReqVO(); reqVO.setClientMessageId("test-uuid-group-001"); reqVO.setGroupId(10L); - reqVO.setType(ImMessageTypeEnum.TEXT.getType()); + reqVO.setType(ImContentTypeEnum.TEXT.getType()); reqVO.setContent("{\"content\":\"群聊你好\"}"); return reqVO; } @@ -86,6 +84,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备 ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setReceipt(true); when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) .thenReturn(null); ImGroupDO group = new ImGroupDO(); @@ -98,7 +97,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) - .thenReturn(ListUtil.of(1L, 2L, 3L)); + .thenReturn(List.of(1L, 2L, 3L)); when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { ImGroupMessageDO msg = invocation.getArgument(0); msg.setId(99L); @@ -112,13 +111,56 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { assertNotNull(result); assertEquals(1L, result.getSenderId()); assertEquals(10L, result.getGroupId()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.getStatus()); - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); // 验证推送给 3 个群成员(含发送者自己,用于多端同步) - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection ids) -> + ArgumentCaptor payloadCaptor = + ArgumentCaptor.forClass(ImGroupMessageNotification.class); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)), - any(ImGroupMessageDTO.class)); + eq(ImConversationTypeEnum.GROUP.getType()), eq(ImContentTypeEnum.TEXT.getType()), + payloadCaptor.capture()); + ImGroupMessageNotification payload = payloadCaptor.getValue(); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), payload.getReceiptStatus()); + assertEquals(0, payload.getReadCount()); + assertEquals(List.of(1L, 2L, 3L), payload.getReceiverUserIds()); + } + } + + @Test + public void testSendMessage_senderAlwaysVisible() { + // 边界:成员缓存漏掉发送者(理论上 validateMemberInGroup 已挡,这里纯防御);发送者仍必须固化进 receiver_user_ids + try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) + .thenReturn(groupMessageService); + + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")) + .thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + group.setName("测试群"); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + // 成员缓存只返回 {2,3},漏掉发送者 1 + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) + .thenReturn(List.of(2L, 3L)); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { + ImGroupMessageDO msg = invocation.getArgument(0); + msg.setId(99L); + return 1; + }); + + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + + // 断言:固化快照必含发送者,推送目标补回发送者 = {1,2,3} + assertTrue(result.getReceiverUserIds().contains(1L), "发送者必须在 receiver_user_ids 快照内"); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L)), + anyInt(), anyInt(), any()); } } @@ -159,375 +201,122 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); } - // ========== pull 测试 ========== - @Test - public void testPullMessages_joinTimeFilter() { - // 准备:消息时间用"最近 1 天内"避免被 30 天窗口过滤 - LocalDateTime now = LocalDateTime.now(); - LocalDateTime joinTime = now.minusHours(2); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(joinTime).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); - - // 入群前 1 条 + 入群后 1 条 - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder() - .id(1L).groupId(10L).senderId(2L) - .sendTime(now.minusHours(3)).build(); - ImGroupMessageDO afterJoin = ImGroupMessageDO.builder() - .id(2L).groupId(10L).senderId(2L) - .sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(beforeJoin, afterJoin)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); - - // 断言:入群前消息不可见 - assertEquals(1, result.size()); - assertEquals(2L, result.get(0).getId()); - } - - @Test - public void testPullMessages_activeQueryRetriesWhenFirstBatchInvisible() { - // 准备:首批 2 条(入群前 + 定向给别人)全部不可见,第二批命中可见消息 - LocalDateTime now = LocalDateTime.now(); - LocalDateTime joinTime = now.minusHours(1); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(joinTime).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); - - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder() - .id(1L).groupId(10L).senderId(2L) - .sendTime(now.minusHours(3)).build(); - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder() - .id(2L).groupId(10L).senderId(2L) - .receiverUserIds(ListUtil.of(2L, 3L)) - .sendTime(now.minusHours(2)).build(); - ImGroupMessageDO visibleMsg = ImGroupMessageDO.builder() - .id(3L).groupId(10L).senderId(2L) - .sendTime(now.minusMinutes(30)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(2))) - .thenReturn(ListUtil.of(beforeJoin, directedMsg)); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(2L), - any(LocalDateTime.class), eq(2))) - .thenReturn(ListUtil.of(visibleMsg)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 0L, 2); - - // 断言:仅返回第二批的可见消息;两批 selectListByMinId 各被调用一次 - assertEquals(1, result.size()); - assertEquals(3L, result.get(0).getId()); - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(2)); - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(2L), - any(LocalDateTime.class), eq(2)); - } - - @Test - public void testPullMessages_quitMemberLoadsPreQuitMessages() { - // 准备:当前群(在群)+ 一个最近退群 - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO activeMember = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember)); - - ImGroupMessageDO activeMsg = ImGroupMessageDO.builder() - .id(5L).groupId(10L).senderId(2L) - .sendTime(now.minusHours(2)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(activeMsg)); - - // 已退群:退群时间在窗口内(minId=0,minQuitTime 直接用 minSendTime) - LocalDateTime quitTime = now.minusDays(3); - ImGroupMemberDO quitMember = ImGroupMemberDO.builder() - .groupId(20L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()) - .joinTime(now.minusDays(20)) - .quitTime(quitTime).build(); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of(quitMember)); - - ImGroupMessageDO quitGroupMsg = ImGroupMessageDO.builder() - .id(3L).groupId(20L).senderId(99L) - .sendTime(now.minusDays(5)).build(); - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(0L), - any(LocalDateTime.class), eq(quitTime), eq(100))) - .thenReturn(ListUtil.of(quitGroupMsg)); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); - - // 断言:两条消息均返回,并按 id 升序 - assertEquals(2, result.size()); - assertEquals(3L, result.get(0).getId()); - assertEquals(5L, result.get(1).getId()); - } - - @Test - public void testPullMessages_quitMemberFilteredByBoundarySendTime() { - // 准备:minId > 0,边界消息 sendTime 晚于窗口起点,应当被用作 minQuitTime - LocalDateTime now = LocalDateTime.now(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of()); - - LocalDateTime boundarySendTime = now.minusDays(2); - ImGroupMessageDO boundary = ImGroupMessageDO.builder() - .id(8L).groupId(10L).senderId(2L).sendTime(boundarySendTime).build(); - when(groupMessageMapper.selectById(8L)).thenReturn(boundary); - when(groupMemberService.getQuitGroupMemberListByUserId(1L, boundarySendTime)) - .thenReturn(ListUtil.of()); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 8L, 100); - - // 断言:使用边界消息 sendTime 抬升 minQuitTime - assertEquals(0, result.size()); - verify(groupMemberService).getQuitGroupMemberListByUserId(1L, boundarySendTime); - } - - @Test - public void testPullMessages_quitQueryKeepsOriginalMinIdWhenActiveQueryRetries() { - // 准备:主查询首批不可见触发重试(游标从 8→101),退群补齐仍使用原始 minId=8 - LocalDateTime now = LocalDateTime.now(); - LocalDateTime joinTime = now.minusHours(1); - ImGroupMemberDO activeMember = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(joinTime).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember)); - - ImGroupMessageDO beforeJoin = ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L) - .sendTime(now.minusHours(3)).build(); - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder() - .id(101L).groupId(10L).senderId(2L) - .receiverUserIds(ListUtil.of(2L, 3L)) - .sendTime(now.minusMinutes(50)).build(); - ImGroupMessageDO visibleActiveMsg = ImGroupMessageDO.builder() - .id(102L).groupId(10L).senderId(2L) - .sendTime(now.minusMinutes(30)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(8L), - any(LocalDateTime.class), eq(2))) - .thenReturn(ListUtil.of(beforeJoin, directedMsg)); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(101L), - any(LocalDateTime.class), eq(2))) - .thenReturn(ListUtil.of(visibleActiveMsg)); - - LocalDateTime boundarySendTime = now.minusDays(2); - ImGroupMessageDO boundary = ImGroupMessageDO.builder() - .id(8L).groupId(99L).senderId(2L).sendTime(boundarySendTime).build(); - when(groupMessageMapper.selectById(8L)).thenReturn(boundary); - - LocalDateTime quitTime = now.minusHours(12); - ImGroupMemberDO quitMember = ImGroupMemberDO.builder() - .groupId(20L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()) - .joinTime(now.minusDays(10)) - .quitTime(quitTime).build(); - when(groupMemberService.getQuitGroupMemberListByUserId(1L, boundarySendTime)) - .thenReturn(ListUtil.of(quitMember)); - - ImGroupMessageDO quitGroupMsg = ImGroupMessageDO.builder() - .id(50L).groupId(20L).senderId(99L) - .sendTime(now.minusDays(1)).build(); - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(8L), - any(LocalDateTime.class), eq(quitTime), eq(2))) - .thenReturn(ListUtil.of(quitGroupMsg)); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 8L, 2); - - // 断言:退群补齐使用原始 minId=8 而非主查询重试后的 101 - assertEquals(2, result.size()); - assertEquals(50L, result.get(0).getId()); - assertEquals(102L, result.get(1).getId()); - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(8L), - any(LocalDateTime.class), eq(2)); - verify(groupMessageMapper).selectListByMinId(eq(ListUtil.of(10L)), eq(101L), - any(LocalDateTime.class), eq(2)); - verify(groupMemberService).getQuitGroupMemberListByUserId(1L, boundarySendTime); - verify(groupMessageMapper).selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(8L), - any(LocalDateTime.class), eq(quitTime), eq(2)); - verify(groupMessageMapper, never()).selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(101L), - any(LocalDateTime.class), eq(quitTime), eq(2)); - } - - @Test - public void testPullMessages_receiverUserIdsFilter() { - // 准备 - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); - - // 定向接收消息:只给用户 2 和 3 - ImGroupMessageDO directedMsg = ImGroupMessageDO.builder() - .id(1L).groupId(10L).senderId(5L) - .receiverUserIds(ListUtil.of(2L, 3L)) - .sendTime(now.minusHours(2)).build(); - // 全员消息 - ImGroupMessageDO allMsg = ImGroupMessageDO.builder() - .id(2L).groupId(10L).senderId(5L) - .sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(directedMsg, allMsg)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + public void testSendMessage_quoteDirectedMessageRejected() { + // 准备:发送人 1 在群 10,当前有效成员(广播受众)= {1,2,3} + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}"); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L)); + // 被引用原消息:定向给 {1,2}(对发送人 1 可见,但不覆盖广播受众 {1,2,3}) + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L)) + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"密\"}") + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(500L)).thenReturn(original); - // 断言:定向接收的消息用户 1 看不到,只能看到全员消息 - assertEquals(1, result.size()); - assertEquals(2L, result.get(0).getId()); + // 调用并断言:引用定向消息被拒绝,避免原文随广播泄漏给全群 + ServiceException exception = assertThrows(ServiceException.class, + () -> groupMessageService.sendGroupMessage(1L, reqVO)); + assertEquals(MESSAGE_QUOTE_INVALID.getCode(), exception.getCode()); + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); } @Test - public void testPullMessages_atFieldCorrectReturn() { - // 准备 - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); - - ImGroupMessageDO atMsg = ImGroupMessageDO.builder() - .id(1L).groupId(10L).senderId(2L) - .atUserIds(ListUtil.of(1L, 3L)) - .sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(atMsg)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); - - // 调用 - List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + public void testSendMessage_quoteBroadcastAccepted() { + // 准备:广播受众 {1,2,3},被引用原消息快照覆盖全部受众 → 允许引用 + ImGroupMessageSendReqVO reqVO = buildSendReqVO(); + reqVO.setContent("{\"content\":\"引用\",\"quote\":{\"messageId\":500}}"); + when(groupMessageMapper.selectBySenderIdAndClientMessageId(1L, "test-uuid-group-001")).thenReturn(null); + ImGroupDO group = new ImGroupDO(); + group.setId(10L); + when(groupService.validateGroupExists(10L)).thenReturn(group); + ImGroupMemberDO member = ImGroupMemberDO.builder().groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()).build(); + when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L, 3L)); + ImGroupMessageDO original = ImGroupMessageDO.builder().id(500L).groupId(10L).senderId(2L) + .status(ImMessageStatusEnum.NORMAL.getStatus()).receiverUserIds(List.of(1L, 2L, 3L)) + .type(ImContentTypeEnum.TEXT.getType()).content("{\"content\":\"公开\"}") + .sendTime(LocalDateTime.now()).build(); + when(groupMessageMapper.selectById(500L)).thenReturn(original); + when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenAnswer(invocation -> { + invocation.getArgument(0).setId(99L); + return 1; + }); - // 断言:@ 字段正确返回 - assertEquals(1, result.size()); - assertEquals(ListUtil.of(1L, 3L), result.get(0).getAtUserIds()); + // 调用并断言:正常入库 + ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); + assertEquals(99L, result.getId()); + verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); } - @Test - public void testPullMessages_pageTrimmedToSize() { - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO activeMember = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(activeMember)); - - ImGroupMessageDO activeMsg1 = ImGroupMessageDO.builder() - .id(1000L).groupId(10L).senderId(2L).sendTime(now.minusHours(2)).build(); - ImGroupMessageDO activeMsg2 = ImGroupMessageDO.builder() - .id(2000L).groupId(10L).senderId(2L).sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), - any(LocalDateTime.class), eq(2))) - .thenReturn(ListUtil.of(activeMsg1, activeMsg2)); - - LocalDateTime quitTime = now.minusDays(1); - ImGroupMemberDO quitMember = ImGroupMemberDO.builder() - .groupId(20L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()) - .joinTime(now.minusDays(20)) - .quitTime(quitTime).build(); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of(quitMember)); - - ImGroupMessageDO quit1 = ImGroupMessageDO.builder() - .id(150L).groupId(20L).senderId(99L).sendTime(now.minusDays(5)).build(); - ImGroupMessageDO quit2 = ImGroupMessageDO.builder() - .id(160L).groupId(20L).senderId(99L).sendTime(now.minusDays(4)).build(); - when(groupMessageMapper.selectListByGroupIdAndMinIdAndQuitTimeBefore(eq(20L), eq(0L), - any(LocalDateTime.class), eq(quitTime), eq(2))) - .thenReturn(ListUtil.of(quit1, quit2)); - - when(groupMessageReadRedisDAO.getReadMaxMessageId(anyLong(), eq(1L))).thenReturn(null); - - List result = groupMessageService.pullGroupMessageList(1L, 0L, 2); - - assertEquals(2, result.size()); - assertEquals(150L, result.get(0).getId()); - assertEquals(160L, result.get(1).getId()); - } + // ========== pull 测试 ========== @Test - public void testPullMessages_pullSetsReadFromRedisCursor() { + public void testPullMessages_doesNotOverwriteStatus() { + // 准备:拉到两条消息;Phase 2 起 pull 不再按读位置覆盖 status,已读由前端按读位置判断 LocalDateTime now = LocalDateTime.now(); ImGroupMemberDO member = ImGroupMemberDO.builder() .groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); ImGroupMessageDO low = ImGroupMessageDO.builder() - .id(5L).groupId(10L).senderId(2L).status(ImMessageStatusEnum.UNREAD.getStatus()) + .id(5L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(now.minusHours(2)).build(); ImGroupMessageDO high = ImGroupMessageDO.builder() - .id(10L).groupId(10L).senderId(2L).status(ImMessageStatusEnum.UNREAD.getStatus()) + .id(10L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(low, high)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); - - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(7L); + .thenReturn(List.of(low, high)); List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + // 断言:status 保持 DB 原值(都 NORMAL),pull 不覆盖 assertEquals(2, result.size()); - assertEquals(ImMessageStatusEnum.READ.getStatus(), result.get(0).getStatus()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.get(1).getStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(1).getStatus()); } @Test public void testPullMessages_receiptReadCountForSender() { + // 准备:本人发送的回执消息,可见成员 {1,2,3},分母排除发送者后为 {2,3} LocalDateTime now = LocalDateTime.now(); ImGroupMemberDO member = ImGroupMemberDO.builder() .groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(now.minusDays(10)).build(); - when(groupMemberService.getActiveGroupMemberListByUserId(1L)).thenReturn(ListUtil.of(member)); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); ImGroupMessageDO receiptMsg = ImGroupMessageDO.builder() .id(100L).groupId(10L).senderId(1L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) + .receiverUserIds(List.of(1L, 2L, 3L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) .sendTime(now.minusHours(1)).build(); - when(groupMessageMapper.selectListByMinId(eq(ListUtil.of(10L)), eq(0L), + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), any(LocalDateTime.class), eq(100))) - .thenReturn(ListUtil.of(receiptMsg)); - when(groupMemberService.getQuitGroupMemberListByUserId(eq(1L), any(LocalDateTime.class))) - .thenReturn(ListUtil.of()); + .thenReturn(List.of(receiptMsg)); - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(100L); Map positions = new HashMap<>(); positions.put(1L, 100L); positions.put(2L, 100L); positions.put(3L, 50L); - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L))) + .thenReturn(positions); - List allMembers = ListUtil.of( + List allMembers = List.of( member, ImGroupMemberDO.builder().groupId(10L).userId(2L) .status(CommonStatusEnum.ENABLE.getStatus()) @@ -536,14 +325,44 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(now.minusDays(20)).build() ); - when(groupMemberService.getGroupMemberListByGroupId(10L)).thenReturn(allMembers); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers); List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + // 断言:分母 {2,3},仅用户 2 读到 >=100 → readCount==1 assertEquals(1, result.size()); assertEquals(1, result.get(0).getReadCount()); } + @Test + public void testPullMessages_noReceiptMessageSkipsReadCount() { + // 准备:本人发送但不需要回执(NO_RECEIPT)的消息——不补 readCount,status 也不被覆盖 + LocalDateTime now = LocalDateTime.now(); + ImGroupMemberDO member = ImGroupMemberDO.builder() + .groupId(10L).userId(1L) + .status(CommonStatusEnum.ENABLE.getStatus()) + .joinTime(now.minusDays(10)).build(); + when(groupMemberService.getGroupMemberListByUserId(1L)).thenReturn(List.of(member)); + + ImGroupMessageDO noReceiptMsg = ImGroupMessageDO.builder() + .id(100L).groupId(10L).senderId(1L) + .receiverUserIds(List.of(1L, 2L, 3L)) + .status(ImMessageStatusEnum.NORMAL.getStatus()) + .receiptStatus(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus()) + .sendTime(now.minusHours(1)).build(); + when(groupMessageMapper.selectListByMinId(eq(1L), anyCollection(), eq(0L), + any(LocalDateTime.class), eq(100))) + .thenReturn(List.of(noReceiptMsg)); + + List result = groupMessageService.pullGroupMessageList(1L, 0L, 100); + + // 断言:status 保持 NORMAL(pull 不覆盖),NO_RECEIPT 不补 readCount,也不查 readCount 相关的读位置映射 + assertEquals(1, result.size()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.get(0).getStatus()); + assertNull(result.get(0).getReadCount()); + verify(conversationReadService, never()).getUserReadMessageIdMap(anyInt(), anyLong()); + } + // ========== 撤回测试 ========== @Test @@ -555,7 +374,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:消息由用户 1 发送,刚发送 5 分钟内 ImGroupMessageDO message = ImGroupMessageDO.builder() .id(50L).senderId(1L).groupId(10L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now()).build(); when(groupMessageMapper.selectById(50L)).thenReturn(message); // 撤回前需要校验用户仍在群中 @@ -566,7 +385,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) - .thenReturn(ListUtil.of(1L, 2L)); + .thenReturn(List.of(1L, 2L)); // 调用 ImGroupMessageDO result = groupMessageService.recallGroupMessage(1L, 50L); @@ -577,9 +396,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { verify(groupMessageMapper).updateById(any(ImGroupMessageDO.class)); verify(groupMessageMapper).insert(any(ImGroupMessageDO.class)); // 验证:给 2 个活跃成员推送撤回提示 - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection ids) -> + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> ids.size() == 2 && ids.contains(1L) && ids.contains(2L)), - any(ImGroupMessageDTO.class)); + anyInt(), anyInt(), any()); } } @@ -588,7 +407,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备 ImGroupMessageDO message = ImGroupMessageDO.builder() .id(50L).senderId(2L).groupId(10L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now()).build(); when(groupMessageMapper.selectById(50L)).thenReturn(message); when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( @@ -624,7 +443,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) ImGroupMessageDO message = ImGroupMessageDO.builder() .id(50L).senderId(1L).groupId(10L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now().minusMinutes(10)).build(); when(groupMessageMapper.selectById(50L)).thenReturn(message); when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn( @@ -641,16 +460,16 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadMessages_messageInvisible() { - // 准备:用户不是群成员 - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(null); + // 准备:消息不在用户接收快照内 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build()); + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) + .sendTime(LocalDateTime.now()).build()); // 调用并断言 ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); } @Test @@ -662,18 +481,19 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(); when(groupMemberService.validateMemberInGroup(10L, 5L)).thenReturn(currentMember); - // 准备:消息由用户 5 发,发送时间在 2026-04-12 10:00 + // 准备:消息由用户 5 发,快照可见范围为 {1,2,5}(用户 3 不在快照内 → 不可见) ImGroupMessageDO message = ImGroupMessageDO.builder() .id(80L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(1L, 2L, 5L)) .sendTime(LocalDateTime.of(2026, 4, 12, 10, 0, 0)).build(); when(groupMessageMapper.selectById(80L)).thenReturn(message); // 准备:群成员一览 - // 用户 1: 正常,入群在消息之前 - // 用户 2: 正常,入群在消息之前 - // 用户 3: 正常,但入群在消息之后 → 不可见 + // 用户 1: 在快照内 + // 用户 2: 在快照内 + // 用户 3: 不在快照内 → 不可见 // 用户 5: 发送者,不计入回执 - List allMembers = ListUtil.of( + List allMembers = List.of( currentMember, ImGroupMemberDO.builder().groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()) @@ -683,19 +503,20 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), ImGroupMemberDO.builder().groupId(10L).userId(3L) .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(LocalDateTime.of(2026, 4, 13, 0, 0, 0)).build(), // 消息之后才入群 + .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build(), ImGroupMemberDO.builder().groupId(10L).userId(5L) .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0)).build() // 发送者 ); - when(groupMemberService.getGroupMemberListByGroupId(10L)).thenReturn(allMembers); + when(groupMemberService.getActiveGroupMemberListByGroupId(10L)).thenReturn(allMembers); - // 准备:Redis 已读位置 — 用户 1 读到 100, 用户 2 读到 50, 用户 3 读到 200 + // 准备:已读位置 — 用户 1 读到 100, 用户 2 读到 50, 用户 3 读到 200 Map positions = new HashMap<>(); positions.put(1L, 100L); positions.put(2L, 50L); positions.put(3L, 200L); - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); + when(conversationReadService.getUserReadMessageIdMap(eq(ImConversationTypeEnum.GROUP.getType()), eq(10L))) + .thenReturn(positions); // 调用:查询 messageId=80 的已读用户 List readUsers = groupMessageService.getGroupReadUserIds(5L, 10L, 80L); @@ -703,7 +524,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 断言: // 用户 1: 可见 + readMaxId=100>=80 → 已读 ✓ // 用户 2: 可见 + readMaxId=50<80 → 未读 ✗ - // 用户 3: 入群在消息之后 → 不可见 → 不算 + // 用户 3: 不在快照内 → 不可见 → 不算 // 用户 5: 发送者 → 排除 assertEquals(1, readUsers.size()); assertTrue(readUsers.contains(1L)); @@ -724,7 +545,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); } - // ========== 发送:补充边界 ========== + // ========== 发送边界 ========== @Test public void testSendMessage_receiptPending() { @@ -743,14 +564,14 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO member = ImGroupMemberDO.builder() .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); when(groupMessageMapper.insert(any(ImGroupMessageDO.class))).thenReturn(1); // 调用 ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); // 断言:receipt=true → 回执状态为 PENDING - assertEquals(ImGroupMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus()); } } @@ -775,10 +596,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); // 断言:不入库、不推送 verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); - verify(imWebSocketService, never()).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); } - // ========== 撤回:补充边界 ========== + // ========== 撤回边界 ========== @Test public void testRecallMessage_notExists() { @@ -794,7 +615,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:消息合法、可撤回时间内,但发送人已退群 ImGroupMessageDO message = ImGroupMessageDO.builder() .id(50L).senderId(1L).groupId(10L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now()).build(); when(groupMessageMapper.selectById(50L)).thenReturn(message); when(groupMemberService.validateMemberInGroup(10L, 1L)) @@ -808,7 +629,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); } - // ========== 已读:happy path + 跳过 ========== + // ========== 已读事件 ========== @Test public void testReadGroupMessages_success() { @@ -816,24 +637,24 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) .thenReturn(groupMessageService); - // 准备:用户在群;Redis 游标从 5 前进到 100 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(LocalDateTime.now().minusDays(1)).build(); - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); + // 准备:消息对用户可见;已读位置从 5 前进到 100 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build()); - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(5L); + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .sendTime(LocalDateTime.now()).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(5L); + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(true); // readGroupMessageEvent 内部会调 selectListByGroupIdAndPendingReceipt → 返回空简化流程 - when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 5L, 100L)).thenReturn(ListUtil.of()); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 5L, 100L)).thenReturn(List.of()); // 调用 groupMessageService.readGroupMessages(1L, 10L, 100L); - // 断言:Redis 游标更新 + READ 事件 - verify(groupMessageReadRedisDAO).updateReadMaxMessageId(10L, 1L, 100L); - verify(imWebSocketService).sendGroupMessageAsync(eq(1L), any(ImGroupMessageDTO.class)); + // 断言:已读位置更新 + READ 事件 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); } } @@ -846,9 +667,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); assertEquals(MESSAGE_GROUP_READ_DISABLED.getCode(), exception.getCode()); - // 断言:Redis 不写、不推送 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); - verify(imWebSocketService, never()).sendGroupMessageAsync(anyLong(), any(ImGroupMessageDTO.class)); + // 断言:已读位置不写、不推送 + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -880,11 +701,11 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ImGroupMemberDO member = ImGroupMemberDO.builder() .groupId(10L).userId(1L).status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L, 2L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); ImGroupMessageDO result = groupMessageService.sendGroupMessage(1L, reqVO); - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus(), + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), result.getReceiptStatus(), "群已读关闭时即使发送方传 receipt=true 也强制落 NO_RECEIPT"); } } @@ -892,31 +713,26 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadGroupMessages_cursorAlreadyAhead() { // 准备:已读游标已 >= 目标,直接返回 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(LocalDateTime.now().minusDays(1)).build(); - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L).sendTime(LocalDateTime.now()).build()); - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(200L); + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L)) + .sendTime(LocalDateTime.now()).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(200L); + // CAS 命中旧位置 200 > 100,单调更新不前进,返回 false + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(false); // 调用 groupMessageService.readGroupMessages(1L, 10L, 100L); - // 断言:不更新、不推送 - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); - verify(imWebSocketService, never()).sendGroupMessageAsync(anyLong(), any(ImGroupMessageDTO.class)); + // 断言:调用了 CAS 更新但读位置未前进 → 不推送 + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test public void testReadGroupMessages_messageNotInGroup() { - // 准备:用户在群,但 messageId 属于其它群 - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(LocalDateTime.now().minusDays(1)).build(); - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); + // 准备:messageId 属于其它群 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() .id(100L).groupId(20L).senderId(2L).sendTime(LocalDateTime.now()).build()); @@ -924,7 +740,7 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); } @Test @@ -933,62 +749,54 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImGroupMessageServiceImpl.class))) .thenReturn(groupMessageService); - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()) - .joinTime(now.minusDays(1)) - .quitTime(now.plusMinutes(1)).build(); - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); + // 准备:历史消息的接收快照包含用户 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L).sendTime(now) - .receiverUserIds(ListUtil.of(1L, 2L)).build()); - when(groupMessageReadRedisDAO.getReadMaxMessageId(10L, 1L)).thenReturn(0L); - when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(ListUtil.of()); + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(1L, 2L)) + .sendTime(LocalDateTime.now().minusMinutes(1)).build()); + when(conversationReadService.getConversationReadMessageId(1L, ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(0L); + when(conversationReadService.updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L)) + .thenReturn(true); + when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)).thenReturn(List.of()); // 调用 groupMessageService.readGroupMessages(1L, 10L, 100L); // 断言:不校验当前在群,直接推进已读位置 verify(groupMemberService, never()).validateMemberInGroup(10L, 1L); - verify(groupMessageReadRedisDAO).updateReadMaxMessageId(10L, 1L, 100L); + verify(conversationReadService).updateConversationReadPosition(1L, ImConversationTypeEnum.GROUP.getType(), 10L, 100L); } } @Test public void testReadGroupMessages_quitMemberInvisibleMessage() { - LocalDateTime now = LocalDateTime.now(); - ImGroupMemberDO member = ImGroupMemberDO.builder() - .groupId(10L).userId(1L) - .status(CommonStatusEnum.DISABLE.getStatus()) - .joinTime(now.minusDays(2)) - .quitTime(now.minusDays(1)).build(); - when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); + // 准备:消息不在历史接收快照内 when(groupMessageMapper.selectById(100L)).thenReturn(ImGroupMessageDO.builder() - .id(100L).groupId(10L).senderId(2L).sendTime(now) - .receiverUserIds(ListUtil.of(1L, 2L)).build()); + .id(100L).groupId(10L).senderId(2L).receiverUserIds(List.of(2L)) + .sendTime(LocalDateTime.now()).build()); // 调用并断言 ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.readGroupMessages(1L, 10L, 100L)); assertEquals(MESSAGE_NOT_IN_GROUP.getCode(), exception.getCode()); - verify(groupMessageReadRedisDAO, never()).updateReadMaxMessageId(anyLong(), anyLong(), anyLong()); + verify(conversationReadService, never()).updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong()); } - // ========== 回执:DONE 状态迁移 ========== + // ========== 回执状态迁移 ========== @Test public void testReadGroupMessageEvent_receiptDoneTransition() { // 准备:消息 100 发送者 5,群 10 活跃成员 {5,1,2},可见用户中除发送者外 {1,2} - // Redis 位置 1→100、2→100 → 全部已读 → 迁移为 DONE + // 已读位置 1→100、2→100 → 全部已读 → 迁移为 DONE ImGroupMessageDO pending = ImGroupMessageDO.builder() .id(100L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(5L, 1L, 2L)) .sendTime(LocalDateTime.now().minusMinutes(1)) - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()).build(); when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) - .thenReturn(ListUtil.of(pending)); - List activeMembers = ListUtil.of( + .thenReturn(List.of(pending)); + List activeMembers = List.of( ImGroupMemberDO.builder().groupId(10L).userId(5L) .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(LocalDateTime.now().minusDays(10)).build(), @@ -1003,7 +811,8 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { Map positions = new HashMap<>(); positions.put(1L, 100L); positions.put(2L, 100L); - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(positions); // 调用 groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); @@ -1012,9 +821,9 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); verify(groupMessageMapper).updateById(captor.capture()); assertEquals(100L, captor.getValue().getId()); - assertEquals(ImGroupMessageReceiptStatusEnum.DONE.getStatus(), captor.getValue().getReceiptStatus()); + assertEquals(ImMessageReceiptStatusEnum.DONE.getStatus(), captor.getValue().getReceiptStatus()); // 断言:给消息发送方(5)推送 RECEIPT 事件 - verify(imWebSocketService).sendGroupMessageAsync(eq(5L), any(ImGroupMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any()); } @Test @@ -1022,12 +831,13 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:用户 2 还没读到 100 → 仍为 PENDING,不更新 DB ImGroupMessageDO pending = ImGroupMessageDO.builder() .id(100L).groupId(10L).senderId(5L) + .receiverUserIds(List.of(5L, 1L, 2L)) .sendTime(LocalDateTime.now().minusMinutes(1)) - .receiptStatus(ImGroupMessageReceiptStatusEnum.PENDING.getStatus()) - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); + .receiptStatus(ImMessageReceiptStatusEnum.PENDING.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()).build(); when(groupMessageMapper.selectListByGroupIdAndPendingReceipt(10L, 0L, 100L)) - .thenReturn(ListUtil.of(pending)); - List activeMembers = ListUtil.of( + .thenReturn(List.of(pending)); + List activeMembers = List.of( ImGroupMemberDO.builder().groupId(10L).userId(5L) .status(CommonStatusEnum.ENABLE.getStatus()) .joinTime(LocalDateTime.now().minusDays(10)).build(), @@ -1042,33 +852,34 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { Map positions = new HashMap<>(); positions.put(1L, 100L); positions.put(2L, 50L); // 还没读到 100 - when(groupMessageReadRedisDAO.getReadMaxMessageIdMap(10L)).thenReturn(positions); + when(conversationReadService.getUserReadMessageIdMap(ImConversationTypeEnum.GROUP.getType(), 10L)) + .thenReturn(positions); groupMessageService.readGroupMessageEvent(1L, 10L, 0L, 100L); // 断言:回执状态保持 PENDING,不更新 DB verify(groupMessageMapper, never()).updateById(any(ImGroupMessageDO.class)); // 仍然推送 RECEIPT 事件给发送方(携带已读人数) - verify(imWebSocketService).sendGroupMessageAsync(eq(5L), any(ImGroupMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(5L), anyInt(), anyInt(), any()); } - // ========== sendGroupMessage(senderId, dto):receiverUserIds 定向过滤 ========== + // ========== 定向群消息 ========== @Test public void testSendGroupMessage_noReceiverUserIds_broadcastsToAllMembers() { // 准备:无定向 → 应发给所有 ENABLE 成员(含发送者自己,多端同步) ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) .setContent("{\"messageId\":1}"); when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); + .thenReturn(List.of(1L, 2L, 3L, 4L)); groupMessageService.sendGroupMessage(1L, dto); - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection ids) -> + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> ids.size() == 4 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L) && ids.contains(4L)), - any(ImGroupMessageDTO.class)); + anyInt(), anyInt(), any()); } @Test @@ -1076,47 +887,48 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:定向给 {2,3},发送者 1;群成员 {1,2,3,4} // 预期推送目标:{1,2,3}(发送者自己多端同步 + 定向列表);4 被过滤 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) .setContent("{\"messageId\":1}") - .setReceiverUserIds(ListUtil.of(2L, 3L)); + .setReceiverUserIds(List.of(2L, 3L)); when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); + .thenReturn(List.of(1L, 2L, 3L, 4L)); groupMessageService.sendGroupMessage(1L, dto); - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection ids) -> + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> ids.size() == 3 && ids.contains(1L) && ids.contains(2L) && ids.contains(3L) && !ids.contains(4L)), - any(ImGroupMessageDTO.class)); + anyInt(), anyInt(), any()); } @Test - public void testSendGroupMessage_withReceiverUserIds_senderNotInGroup_senderStillExcluded() { - // 边界:发送者不在群的 userId 列表里(理论上应先被 validateMemberInGroup 挡住,这里纯防御) - // 预期:仅定向用户可见 + public void testSendGroupMessage_withReceiverUserIds_senderAlwaysIncluded() { + // 边界:发送者不在群成员缓存里(理论上应先被 validateMemberInGroup 挡住,这里纯防御) + // 预期:定向用户 + 发送者自己(始终纳入快照保证多端同步);非定向成员 4 被过滤 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) .setContent("{\"messageId\":1}") - .setReceiverUserIds(ListUtil.of(2L, 3L)); + .setReceiverUserIds(List.of(2L, 3L)); when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)) - .thenReturn(ListUtil.of(1L, 2L, 3L, 4L)); + .thenReturn(List.of(1L, 2L, 3L, 4L)); groupMessageService.sendGroupMessage(99L, dto); - verify(imWebSocketService).sendGroupMessageAsync(argThat((Collection ids) -> - ids.size() == 2 && ids.contains(2L) && ids.contains(3L)), - any(ImGroupMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 3 && ids.contains(2L) && ids.contains(3L) + && ids.contains(99L) && !ids.contains(4L)), + anyInt(), anyInt(), any()); } - // ========== sendGroupMessage(senderId, dto):helper 行为 ========== + // ========== DTO 群消息 ========== @Test public void testSendGroupMessage_dto_persistsAndSerializesPojoContent() { // 准备:persistent=true 类型 + POJO content ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) .setContent(new RecallMessage().setMessageId(50L)); - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); groupMessageService.sendGroupMessage(1L, dto); @@ -1126,10 +938,10 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { ImGroupMessageDO message = captor.getValue(); assertEquals(1L, message.getSenderId()); assertEquals(10L, message.getGroupId()); - assertEquals(ImMessageTypeEnum.RECALL.getType(), message.getType()); + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType()); assertEquals("{\"messageId\":50}", message.getContent()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), message.getStatus()); - assertEquals(ImGroupMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), message.getReceiptStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.NO_RECEIPT.getStatus(), message.getReceiptStatus()); assertNotNull(message.getClientMessageId()); assertNotNull(message.getSendTime()); } @@ -1138,114 +950,103 @@ public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { public void testSendGroupMessage_dto_receiptPending() { // 准备:dto.receipt=true → receiptStatus=PENDING ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECALL.getType()) + .setGroupId(10L).setType(ImContentTypeEnum.RECALL.getType()) .setContent("{\"messageId\":50}").setReceipt(true); - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L)); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L)); groupMessageService.sendGroupMessage(1L, dto); ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupMessageDO.class); verify(groupMessageMapper).insert(captor.capture()); - assertEquals(ImGroupMessageReceiptStatusEnum.PENDING.getStatus(), captor.getValue().getReceiptStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), captor.getValue().getReceiptStatus()); } @Test public void testSendGroupMessage_dto_nonPersistentTypeNotInserted() { // 准备:persistent=false 类型(RECEIPT 回执)→ 不入库,仅推送 ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.RECEIPT.getType()); - when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(ListUtil.of(1L, 2L)); + .setGroupId(10L).setType(ImContentTypeEnum.RECEIPT.getType()); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); groupMessageService.sendGroupMessage(1L, dto); verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); - verify(imWebSocketService).sendGroupMessageAsync(anyCollection(), any(ImGroupMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(anyCollection(), anyInt(), anyInt(), any()); + } + + @Test + public void testSendGroupMessage_dto_groupMemberNicknameUpdateNotInserted() { + // 准备:成员昵称变更只做在线同步,不入库 + ImGroupMessageSendDTO dto = ImGroupMessageSendDTO.ofGroupMemberNicknameUpdate(10L, 1L, "群昵称"); + when(groupMemberService.getActiveGroupMemberUserIdsByGroupId(10L)).thenReturn(List.of(1L, 2L)); + + groupMessageService.sendGroupMessage(1L, dto); + + verify(groupMessageMapper, never()).insert(any(ImGroupMessageDO.class)); + verify(imWebSocketService).sendNotificationAsync(argThat((Collection ids) -> + ids.size() == 2 && ids.contains(1L) && ids.contains(2L)), + anyInt(), eq(ImContentTypeEnum.GROUP_MEMBER_NICKNAME_UPDATE.getType()), any()); } @Test public void testSendGroupMessage_threeArg_explicitTargetsBypassActiveMembers() { // 准备:调用方传入显式 targets(解散场景成员已被批量 DISABLE,必须按移除前快照推送) - Set targets = CollUtil.newHashSet(1L, 2L, 3L); + Set targets = Set.of(1L, 2L, 3L); ImGroupMessageSendDTO dto = new ImGroupMessageSendDTO() - .setGroupId(10L).setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()).setContent("{}"); + .setGroupId(10L).setType(ImContentTypeEnum.GROUP_DISSOLVE.getType()).setContent("{}"); groupMessageService.sendGroupMessage(1L, targets, dto); // 断言:不读取 active members,按调用方 targets 推送 verify(groupMemberService, never()).getActiveGroupMemberUserIdsByGroupId(anyLong()); - verify(imWebSocketService).sendGroupMessageAsync(eq(targets), any(ImGroupMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(targets), anyInt(), anyInt(), any()); } // ========== getGroupMessageList ========== @Test - public void testGetGroupMessageList_delegatesToMapperWithJoinTime() { - // 准备 - LocalDateTime joinTime = LocalDateTime.now().minusDays(5); + public void testGetGroupMessageList_delegatesToMapper() { + // 准备:可见性由 SQL 按 receiver_user_ids 快照过滤,Service 直接透传 mapper 结果 ImGroupMemberDO member = ImGroupMemberDO.builder() .id(50L).groupId(10L).userId(1L) .status(CommonStatusEnum.ENABLE.getStatus()) - .joinTime(joinTime).build(); - when(groupMemberService.validateMemberInGroup(10L, 1L)).thenReturn(member); + .joinTime(LocalDateTime.now().minusDays(5)).build(); + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(member); ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); reqVO.setGroupId(10L); reqVO.setMaxId(100L); reqVO.setLimit(20); - List mockList = ListUtil.of( + List mockList = List.of( ImGroupMessageDO.builder().id(99L).groupId(10L).senderId(2L) - .sendTime(joinTime.plusMinutes(1)).build() + .receiverUserIds(List.of(1L)).sendTime(LocalDateTime.now()).build() ); - when(groupMessageMapper.selectHistoryList(10L, 100L, 20, joinTime)).thenReturn(mockList); + when(groupMessageMapper.selectHistoryListByUser(1L, 10L, 100L, 20)).thenReturn(mockList); // 调用 List result = groupMessageService.getGroupMessageList(1L, reqVO); - // 断言:使用成员的 joinTime 作为历史消息的下限 + // 断言:透传到 mapper,参数一致 assertEquals(1, result.size()); - verify(groupMessageMapper).selectHistoryList(10L, 100L, 20, joinTime); + assertEquals(99L, result.get(0).getId()); + verify(groupMessageMapper).selectHistoryListByUser(1L, 10L, 100L, 20); } @Test public void testGetGroupMessageList_notInGroup() { - // 准备:用户不在群中 + // 准备:用户从未在群中(getGroupMember 返回 null) ImGroupMessageListReqVO reqVO = new ImGroupMessageListReqVO(); reqVO.setGroupId(10L); reqVO.setLimit(20); - when(groupMemberService.validateMemberInGroup(10L, 1L)) - .thenThrow(new ServiceException(GROUP_MEMBER_NOT_IN_GROUP.getCode(), - GROUP_MEMBER_NOT_IN_GROUP.getMsg())); + when(groupMemberService.getGroupMember(10L, 1L)).thenReturn(null); // 调用并断言:鉴权失败 ServiceException exception = assertThrows(ServiceException.class, () -> groupMessageService.getGroupMessageList(1L, reqVO)); assertEquals(GROUP_MEMBER_NOT_IN_GROUP.getCode(), exception.getCode()); // 断言:不查询 DB - verify(groupMessageMapper, never()).selectHistoryList(anyLong(), anyLong(), anyInt(), any()); - } - - // ========== delete read cursor 委托 ========== - - @Test - public void testDeleteReadMaxMessageId_delegatesToRedis() { - groupMessageService.deleteReadMaxMessageId(10L, 1L); - - verify(groupMessageReadRedisDAO).deleteReadMaxMessageId(10L, 1L); - } - - @Test - public void testDeleteReadMaxMessageIds_delegatesToRedis() { - groupMessageService.deleteReadMaxMessageIds(10L, ListUtil.of(1L, 2L)); - - verify(groupMessageReadRedisDAO).deleteReadMaxMessageIds(10L, ListUtil.of(1L, 2L)); - } - - @Test - public void testDeleteReadMaxMessageIdMap_delegatesToRedis() { - groupMessageService.deleteReadMaxMessageIdMap(10L); - - verify(groupMessageReadRedisDAO).deleteReadMaxMessageIdMap(10L); + verify(groupMessageMapper, never()).selectHistoryListByUser(anyLong(), anyLong(), anyLong(), anyInt()); } } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java index dd64cac..91436cf 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java +++ b/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 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.enums.message.ImMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; -import cn.iocoder.yudao.module.im.enums.message.ImMessageTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; import cn.iocoder.yudao.module.im.framework.config.ImProperties; +import cn.iocoder.yudao.module.im.service.conversation.ImConversationReadService; import cn.iocoder.yudao.module.im.service.friend.ImFriendService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.message.dto.ImPrivateMessageSendDTO; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReadNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImMessageReceiptNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.RecallMessage; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -31,7 +36,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** - * IM 私聊消息 Service 单元测试 + * {@link ImPrivateMessageServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -47,9 +52,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @Mock private ImSensitiveWordService sensitiveWordService; @Mock + private ImConversationReadService conversationReadService; + @Mock private ImWebSocketService imWebSocketService; - /** 用真实实例避免 NPE;默认值与生产保持一致(recallTimeoutMinutes=5、private/group read enabled=true);个别用例可改字段测分支 */ @Spy private ImProperties imProperties = new ImProperties(); @@ -57,7 +63,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO(); reqVO.setClientMessageId("test-uuid-001"); reqVO.setReceiverId(2L); - reqVO.setType(ImMessageTypeEnum.TEXT.getType()); + reqVO.setType(ImContentTypeEnum.TEXT.getType()); reqVO.setContent("{\"content\":\"你好\"}"); return reqVO; } @@ -83,8 +89,10 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { assertNotNull(result); assertEquals(1L, result.getSenderId()); assertEquals(2L, result.getReceiverId()); - assertEquals(ImMessageTypeEnum.TEXT.getType(), result.getType()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), result.getStatus()); + assertEquals(ImContentTypeEnum.TEXT.getType(), result.getType()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), result.getStatus()); + assertEquals(ImMessageReceiptStatusEnum.PENDING.getStatus(), result.getReceiptStatus(), + "用户私聊消息默认需要回执(PENDING)"); assertNotNull(result.getSendTime()); // 验证调用 @@ -92,8 +100,8 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { verify(sensitiveWordService).validateText(reqVO.getContent()); verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); // 验证推送给接收方和发送方 - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); } @Test @@ -162,38 +170,44 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testReadMessages_success() { - // 准备:前端上报已读到 messageId=5;mapper 返回更新行数 2 表示有未读被翻转 - when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus( + // 准备:前端上报已读到 messageId=5;mapper 返回更新行数 2 表示有待回执消息被标记完成 + when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( eq(2L), eq(1L), eq(5L), - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class))) + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class))) .thenReturn(2); + // 读位置前进 → 才下发事件 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(true); // 调用 privateMessageService.readPrivateMessages(1L, 2L, 5L); - // 断言:更新了消息状态 - verify(privateMessageMapper).updateBySenderIdAndReceiverIdAndIdLeAndStatus( + // 断言:把待回执(PENDING)消息标记为已完成(DONE);status 不再写 READ + verify(privateMessageMapper).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( eq(2L), eq(1L), eq(5L), - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class)); + eq(ImMessageReceiptStatusEnum.PENDING.getStatus()), any(ImPrivateMessageDO.class)); // 断言:发送了 READ + RECEIPT 事件,payload 字段正确 ArgumentCaptor userCaptor = ArgumentCaptor.forClass(Long.class); - ArgumentCaptor contentCaptor = ArgumentCaptor.forClass(ImPrivateMessageDTO.class); - verify(imWebSocketService, times(2)).sendPrivateMessageAsync( - userCaptor.capture(), contentCaptor.capture()); + ArgumentCaptor contentTypeCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(Object.class); + verify(imWebSocketService, times(2)).sendNotificationAsync( + userCaptor.capture(), eq(ImConversationTypeEnum.PRIVATE.getType()), + contentTypeCaptor.capture(), payloadCaptor.capture()); // 第一次:发给自己的 READ 事件 assertEquals(1L, userCaptor.getAllValues().get(0)); - ImPrivateMessageDTO readPayload = contentCaptor.getAllValues().get(0); - assertEquals(ImMessageTypeEnum.READ.getType(), readPayload.getType()); + assertEquals(ImContentTypeEnum.READ.getType(), contentTypeCaptor.getAllValues().get(0)); + ImMessageReadNotification readPayload = (ImMessageReadNotification) payloadCaptor.getAllValues().get(0); assertEquals(1L, readPayload.getSenderId()); assertEquals(2L, readPayload.getReceiverId()); assertEquals(5L, readPayload.getId(), "READ id 应为前端上报的 messageId"); // 第二次:发给对方的 RECEIPT 事件 assertEquals(2L, userCaptor.getAllValues().get(1)); - ImPrivateMessageDTO receiptPayload = contentCaptor.getAllValues().get(1); - assertEquals(ImMessageTypeEnum.RECEIPT.getType(), receiptPayload.getType()); + assertEquals(ImContentTypeEnum.RECEIPT.getType(), contentTypeCaptor.getAllValues().get(1)); + ImMessageReceiptNotification receiptPayload = + (ImMessageReceiptNotification) payloadCaptor.getAllValues().get(1); assertEquals(5L, receiptPayload.getId(), "RECEIPT id 应为前端上报的 messageId"); } @@ -204,7 +218,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { // 准备 ImPrivateMessageDO message = ImPrivateMessageDO.builder() .id(10L).senderId(1L).receiverId(2L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now()).build(); // 刚发送,5 分钟内 when(privateMessageMapper.selectById(10L)).thenReturn(message); when(privateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); @@ -219,7 +233,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { verify(privateMessageMapper).updateById(any(ImPrivateMessageDO.class)); verify(privateMessageMapper).insert(any(ImPrivateMessageDO.class)); // 验证推送了消息(给接收方和发送方) - verify(imWebSocketService, times(2)).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService, times(2)).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -227,7 +241,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { // 准备 ImPrivateMessageDO message = ImPrivateMessageDO.builder() .id(10L).senderId(2L).receiverId(1L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now()).build(); when(privateMessageMapper.selectById(10L)).thenReturn(message); @@ -268,7 +282,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { // 准备:消息发送于 10 分钟前(超过 5 分钟窗口) ImPrivateMessageDO message = ImPrivateMessageDO.builder() .id(10L).senderId(1L).receiverId(2L) - .status(ImMessageStatusEnum.UNREAD.getStatus()) + .status(ImMessageStatusEnum.NORMAL.getStatus()) .sendTime(LocalDateTime.now().minusMinutes(10)).build(); when(privateMessageMapper.selectById(10L)).thenReturn(message); @@ -295,7 +309,7 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { assertEquals(MESSAGE_SENSITIVE_WORD_BLOCKED.getCode(), exception.getCode()); // 断言:不入库、不推送 verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test @@ -308,33 +322,31 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { () -> privateMessageService.readPrivateMessages(1L, 2L, 5L)); assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); // 断言:不更新消息状态、不推送 - verify(privateMessageMapper, never()).updateBySenderIdAndReceiverIdAndIdLeAndStatus( + verify(privateMessageMapper, never()).updateBySenderIdAndReceiverIdAndIdLeAndReceiptStatus( anyLong(), anyLong(), anyLong(), anyInt(), any(ImPrivateMessageDO.class)); - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } @Test - public void testReadMessages_noUnread() { - // 准备:mapper 返回 0 表示 id <= messageId 范围内没有未读消息 - when(privateMessageMapper.updateBySenderIdAndReceiverIdAndIdLeAndStatus( - eq(2L), eq(1L), eq(5L), - eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class))) - .thenReturn(0); + public void testReadMessages_notAdvanced() { + // 准备:读位置未前进(已读过 / CAS 失败),不下发事件 + when(conversationReadService.updateConversationReadPosition(anyLong(), anyInt(), anyLong(), anyLong())) + .thenReturn(false); // 调用 privateMessageService.readPrivateMessages(1L, 2L, 5L); - // 断言:不推送 READ / RECEIPT - verify(imWebSocketService, never()).sendPrivateMessageAsync(anyLong(), any(ImPrivateMessageDTO.class)); + // 断言:读位置没前进,不推送 READ / RECEIPT + verify(imWebSocketService, never()).sendNotificationAsync(anyLong(), anyInt(), anyInt(), any()); } // ========== getMaxReadMessageId 测试 ========== @Test public void testGetMaxReadMessageId_hit() { - // 准备:对方读到我发的最大 id=10 - when(privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus( - 1L, 2L, ImMessageStatusEnum.READ.getStatus())).thenReturn(10L); + // 准备:对方(2) 在与我(1) 的会话里读位置=10 + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L))) + .thenReturn(10L); // 调用 Long result = privateMessageService.getMaxReadMessageId(1L, 2L); @@ -345,9 +357,9 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { @Test public void testGetMaxReadMessageId_miss() { - // 准备:对方一条都没读过 - when(privateMessageMapper.selectMaxIdBySenderIdAndReceiverIdAndStatus( - 1L, 2L, ImMessageStatusEnum.READ.getStatus())).thenReturn(null); + // 准备:对方一条都没读过(读位置为 null) + when(conversationReadService.getConversationReadMessageId(eq(2L), anyInt(), eq(1L))) + .thenReturn(null); // 调用 Long result = privateMessageService.getMaxReadMessageId(1L, 2L); @@ -367,13 +379,13 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { assertEquals(MESSAGE_PRIVATE_READ_DISABLED.getCode(), exception.getCode()); } - // ========== sendPrivateMessage(senderId, dto):helper 行为 ========== + // ========== DTO 私聊消息 ========== @Test public void testSendPrivateMessage_dto_persistsAndSerializesPojoContent() { // 准备:persistent=true 类型 + POJO content ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() - .setReceiverId(2L).setType(ImMessageTypeEnum.RECALL.getType()) + .setReceiverId(2L).setType(ImContentTypeEnum.RECALL.getType()) .setContent(new RecallMessage().setMessageId(50L)); privateMessageService.sendPrivateMessage(1L, dto); @@ -384,27 +396,27 @@ public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { ImPrivateMessageDO message = captor.getValue(); assertEquals(1L, message.getSenderId()); assertEquals(2L, message.getReceiverId()); - assertEquals(ImMessageTypeEnum.RECALL.getType(), message.getType()); + assertEquals(ImContentTypeEnum.RECALL.getType(), message.getType()); assertEquals("{\"messageId\":50}", message.getContent()); - assertEquals(ImMessageStatusEnum.UNREAD.getStatus(), message.getStatus()); + assertEquals(ImMessageStatusEnum.NORMAL.getStatus(), message.getStatus()); assertNotNull(message.getClientMessageId()); assertNotNull(message.getSendTime()); // 断言:sender + receiver 双端推送 - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(imWebSocketService).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); } @Test public void testSendPrivateMessage_dto_nonPersistentTypeNotInserted() { // 准备:persistent=false 类型(FRIEND_DELETE 通知)→ 不入库;仅推 sender 多端,receiver 不感知 ImPrivateMessageSendDTO dto = new ImPrivateMessageSendDTO() - .setReceiverId(2L).setType(ImMessageTypeEnum.FRIEND_DELETE.getType()); + .setReceiverId(2L).setType(ImContentTypeEnum.FRIEND_DELETE.getType()); privateMessageService.sendPrivateMessage(1L, dto); verify(privateMessageMapper, never()).insert(any(ImPrivateMessageDO.class)); - verify(imWebSocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); - verify(imWebSocketService, never()).sendPrivateMessageAsync(eq(2L), any(ImPrivateMessageDTO.class)); + verify(imWebSocketService).sendNotificationAsync(eq(1L), anyInt(), anyInt(), any()); + verify(imWebSocketService, never()).sendNotificationAsync(eq(2L), anyInt(), anyInt(), any()); } // ========== getPrivateMessageList ========== diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java index 9173b87..b178022 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImplTest.java +++ b/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; import cn.iocoder.yudao.module.im.framework.config.ImProperties; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import org.junit.jupiter.api.Test; @@ -44,6 +43,11 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +/** + * {@link ImRtcCallServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @InjectMocks @@ -64,7 +68,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { @Mock private ImGroupMemberService groupMemberService; - // ========== timeoutInvitingParticipants(Job 入口)========== + // ========== timeoutInvitingParticipants ========== @Test public void testTimeoutInvitingParticipants_emptyCandidates_returnsZeroAndNoDownstream() { @@ -144,7 +148,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { // 断言:成功 1 个;NO_ANSWER 信令推到主叫;不触发 endSession assertEquals(1, result); - verify(webSocketService).sendPrivateMessageAsync(eq(200L), any(ImPrivateMessageDTO.class)); + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any()); verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); } @@ -167,7 +171,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { verifyNoInteractions(webSocketService); } - // ========== noAnswerCallCheck(前端 timer 入口)========== + // ========== noAnswerCallCheck ========== @Test public void testNoAnswerCallCheck_authFails_silentNoOp() { @@ -239,7 +243,7 @@ public class ImRtcCallServiceImplTest extends BaseMockitoUnitTest { rtcCallService.noAnswerCallCheck(100L, "r1"); // 断言:NO_ANSWER 信令推到主叫 200L;不触发 endSession - verify(webSocketService).sendPrivateMessageAsync(eq(200L), any(ImPrivateMessageDTO.class)); + verify(webSocketService).sendNotificationAsync(eq(200L), anyInt(), anyInt(), any()); verify(rtcCallMapper, never()).updateByIdAndStatusIn(any(), anyCollection(), any()); } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java index 548a63e..5e7711d 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImplTest.java +++ b/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.*; import static org.mockito.Mockito.*; /** - * IM 敏感词 Service 单元测试 + * {@link ImSensitiveWordServiceImpl} 的单元测试 * * @author 芋道源码 */ diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java index f5b5786..a55037d 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java +++ b/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; import static org.mockito.Mockito.when; /** - * IM 数据看板 Service 单元测试 + * {@link ImStatisticsManagerServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -72,7 +72,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { assertEquals(456L, service.getGroupMessageCount(BEGIN, END)); } - // ========== 每日序列 toDailyCountMap ========== + // ========== 每日序列 ========== @Test public void testGetNewUserDailyCountMap_dateConvert() { diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java index df2125a..898aa1e 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/websocket/ImWebSocketServiceImplTest.java +++ b/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; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; -import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; +import cn.iocoder.yudao.module.im.enums.ImContentTypeEnum; +import cn.iocoder.yudao.module.im.enums.ImConversationTypeEnum; +import cn.iocoder.yudao.module.im.service.websocket.notification.ImNotificationWebSocketDTO; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImGroupMessageNotification; +import cn.iocoder.yudao.module.im.service.websocket.notification.message.ImPrivateMessageNotification; import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -21,10 +24,11 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.*; /** - * IM WebSocket 推送 Service 单元测试 + * {@link ImWebSocketServiceImpl} 的单元测试 * * @author 芋道源码 */ @@ -44,28 +48,32 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { } } - // ========== sendPrivateMessageAsync ========== + // ========== 私聊推送 ========== @Test - public void testSendPrivateMessageAsync_noTransactionSendsImmediately() { + public void testSendNotificationAsync_private_noTransactionSendsImmediately() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); // 准备 - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); // 调用:无事务,应立即发送 - imWebSocketService.sendPrivateMessageAsync(2L, dto); + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); // 断言 verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImPrivateMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE, + ImContentTypeEnum.TEXT, dto))); } } @Test - public void testSendPrivateMessageAsync_inTransactionDeferredUntilCommit() { + public void testSendNotificationAsync_private_inTransactionDeferredUntilCommit() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); @@ -73,10 +81,12 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { // 准备:开启事务同步 TransactionSynchronizationManager.initSynchronization(); try { - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); // 调用 - imWebSocketService.sendPrivateMessageAsync(2L, dto); + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); // 断言:事务未提交,未推送 verify(webSocketSenderApi, never()).sendObject(anyInt(), anyLong(), anyString(), any()); @@ -89,49 +99,60 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { // 断言:提交后推送 verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImPrivateMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.PRIVATE, + ImContentTypeEnum.TEXT, dto))); } finally { TransactionSynchronizationManager.clear(); } } } - // ========== sendGroupMessageAsync ========== + // ========== 群聊推送 ========== @Test - public void testSendGroupMessageAsync_fanOutToAllUsers() { + public void testSendNotificationAsync_group_fanOutToAllUsers() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); - ImGroupMessageDTO dto = new ImGroupMessageDTO(); + ImGroupMessageNotification dto = new ImGroupMessageNotification(); dto.setGroupId(10L); dto.setSenderId(1L); + dto.setType(ImContentTypeEnum.TEXT.getType()); - imWebSocketService.sendGroupMessageAsync(ListUtil.of(1L, 2L, 3L), dto); + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L), + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(3L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(3L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); } } @Test - public void testSendGroupMessageAsync_senderExceptionDoesNotBreakOthers() { + public void testSendNotificationAsync_group_senderExceptionDoesNotBreakOthers() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); - ImGroupMessageDTO dto = new ImGroupMessageDTO(); + ImGroupMessageNotification dto = new ImGroupMessageNotification(); dto.setGroupId(10L); // 给 1 号用户推送时抛异常,不能影响 2/3 号 doThrow(new RuntimeException("user offline")) .when(webSocketSenderApi).sendObject(anyInt(), eq(1L), anyString(), any()); - imWebSocketService.sendGroupMessageAsync(ListUtil.of(1L, 2L, 3L), dto); + imWebSocketService.sendNotificationAsync(ListUtil.of(1L, 2L, 3L), + ImConversationTypeEnum.GROUP.getType(), ImContentTypeEnum.TEXT.getType(), dto); // 2L 和 3L 也都被推送 verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); @@ -140,60 +161,96 @@ public class ImWebSocketServiceImplTest extends BaseMockitoUnitTest { } @Test - public void testDoSendGroupMessage_emptyUserIds_noSend() { - ImGroupMessageDTO dto = new ImGroupMessageDTO().setGroupId(10L); + public void testDoSendNotification_emptyUserIds_noSend() { + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto); - imWebSocketService.doSendGroupMessage(Collections.emptyList(), dto); - imWebSocketService.doSendGroupMessage(null, dto); + imWebSocketService.doSendNotification(Collections.emptyList(), notification); + imWebSocketService.doSendNotification(null, notification); verifyNoInteractions(webSocketSenderApi); } @Test - public void testDoSendGroupMessage_distinctUserIds() { - ImGroupMessageDTO dto = new ImGroupMessageDTO().setGroupId(10L); + public void testDoSendNotification_distinctUserIds() { + ImGroupMessageNotification dto = new ImGroupMessageNotification(); + dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); + ImNotificationWebSocketDTO notification = buildNotification(ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto); - imWebSocketService.doSendGroupMessage(Arrays.asList(1L, 2L, 1L, null), dto); + imWebSocketService.doSendNotification(Arrays.asList(1L, 2L, 1L, null), notification); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(1L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(2L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); verifyNoMoreInteractions(webSocketSenderApi); } @Test - public void testSendPrivateMessageAsync_exceptionSwallowed() { + public void testSendNotificationAsync_private_exceptionSwallowed() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); // 准备:sender 抛异常 - ImPrivateMessageDTO dto = new ImPrivateMessageDTO().setSenderId(1L).setReceiverId(2L); + ImPrivateMessageNotification dto = new ImPrivateMessageNotification().setSenderId(1L).setReceiverId(2L) + .setType(ImContentTypeEnum.TEXT.getType()); doThrow(new RuntimeException("user offline")) .when(webSocketSenderApi).sendObject(anyInt(), anyLong(), anyString(), any()); // 调用:异常应被吞掉,不向上抛 - imWebSocketService.sendPrivateMessageAsync(2L, dto); + imWebSocketService.sendNotificationAsync(2L, ImConversationTypeEnum.PRIVATE.getType(), + ImContentTypeEnum.TEXT.getType(), dto); verify(webSocketSenderApi).sendObject(anyInt(), eq(2L), anyString(), any()); } } @Test - public void testSendGroupMessageAsync_singleUserDefaultOverload() { + public void testSendNotificationAsync_group_singleUserDefaultOverload() { try (MockedStatic springUtilMockedStatic = mockStatic(SpringUtil.class)) { springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImWebSocketServiceImpl.class))) .thenReturn(imWebSocketService); - ImGroupMessageDTO dto = new ImGroupMessageDTO(); + ImGroupMessageNotification dto = new ImGroupMessageNotification(); dto.setGroupId(10L); + dto.setType(ImContentTypeEnum.TEXT.getType()); - imWebSocketService.sendGroupMessageAsync(42L, dto); + imWebSocketService.sendNotificationAsync(42L, ImConversationTypeEnum.GROUP.getType(), + ImContentTypeEnum.TEXT.getType(), dto); verify(webSocketSenderApi).sendObject( - eq(UserTypeEnum.ADMIN.getValue()), eq(42L), eq(ImGroupMessageDTO.TYPE), eq(dto)); + eq(UserTypeEnum.ADMIN.getValue()), eq(42L), eq(ImNotificationWebSocketDTO.TYPE), + argThat(actual -> isNotification(actual, ImConversationTypeEnum.GROUP, + ImContentTypeEnum.TEXT, dto))); } } + private static boolean isNotification(Object object, ImConversationTypeEnum conversationType, + ImContentTypeEnum contentType, Object payload) { + if (!(object instanceof ImNotificationWebSocketDTO notification)) { + return false; + } + return conversationType.getType().equals(notification.getConversationType()) + && contentType.getType().equals(notification.getContentType()) + && payload == notification.getPayload(); + } + + private static ImNotificationWebSocketDTO buildNotification(ImConversationTypeEnum conversationType, + ImContentTypeEnum contentType, Object payload) { + return new ImNotificationWebSocketDTO() + .setConversationType(conversationType.getType()) + .setContentType(contentType.getType()) + .setPayload(payload); + } + } diff --git a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java index 03f6a14..a821bd0 100644 --- a/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java +++ b/yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/util/ImMessageUtilsTest.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.im.util; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.QuoteMessage; +import cn.iocoder.yudao.module.im.dal.dataobject.message.content.QuoteMessage; import org.junit.jupiter.api.Test; import java.util.Map; @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** - * IM 消息内容工具类单元测试 + * {@link ImMessageUtils} 的单元测试 * * @author 芋道源码 */ diff --git a/yudao-module-im/src/test/resources/sql/clean.sql b/yudao-module-im/src/test/resources/sql/clean.sql index 64ff0dd..c25a453 100644 --- a/yudao-module-im/src/test/resources/sql/clean.sql +++ b/yudao-module-im/src/test/resources/sql/clean.sql @@ -1,5 +1,6 @@ DELETE FROM "im_private_message"; DELETE FROM "im_group_message"; +DELETE FROM "im_conversation_read"; DELETE FROM "im_group"; DELETE FROM "im_group_member"; DELETE FROM "im_friend"; diff --git a/yudao-module-im/src/test/resources/sql/create_tables.sql b/yudao-module-im/src/test/resources/sql/create_tables.sql index 2505fbb..86579c4 100644 --- a/yudao-module-im/src/test/resources/sql/create_tables.sql +++ b/yudao-module-im/src/test/resources/sql/create_tables.sql @@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS "im_private_message" ( "type" smallint NOT NULL COMMENT '消息类型', "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', "status" tinyint NOT NULL COMMENT '消息状态', + "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态', "send_time" timestamp NOT NULL COMMENT '发送时间', "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -26,7 +27,7 @@ CREATE TABLE IF NOT EXISTS "im_group_message" ( "content" varchar(8192) DEFAULT NULL COMMENT '消息内容', "status" tinyint NOT NULL COMMENT '消息状态', "send_time" timestamp NOT NULL COMMENT '发送时间', - "receiver_user_ids" varchar(1024) DEFAULT NULL COMMENT '定向接收用户编号列表', + "receiver_user_ids" text DEFAULT NULL COMMENT '定向接收用户编号列表', "at_user_ids" varchar(1024) DEFAULT NULL COMMENT '@ 目标用户编号列表', "receipt_status" tinyint NOT NULL DEFAULT 0 COMMENT '回执状态', "creator" varchar(64) DEFAULT '', @@ -281,7 +282,7 @@ CREATE TABLE IF NOT EXISTS "im_channel_message" ( "material_id" bigint NOT NULL COMMENT '关联素材编号', "type" smallint NOT NULL COMMENT '消息类型', "content" varchar(8192) DEFAULT NULL COMMENT '消息内容;推送时 payload JSON 快照;不含富文本正文', - "receiver_user_ids" varchar(1024) DEFAULT NULL COMMENT '接收人编号列表;逗号分隔;为空表示全员', + "receiver_user_ids" text DEFAULT NULL COMMENT '接收人编号列表;逗号分隔;为空表示全员', "send_time" timestamp NOT NULL COMMENT '发送时间', "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -292,6 +293,23 @@ CREATE TABLE IF NOT EXISTS "im_channel_message" ( PRIMARY KEY ("id") ) COMMENT 'IM 频道消息表'; +CREATE TABLE IF NOT EXISTS "im_conversation_read" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "user_id" bigint NOT NULL COMMENT '用户编号', + "conversation_type" tinyint NOT NULL COMMENT '会话类型', + "target_id" bigint NOT NULL COMMENT '目标编号', + "message_id" bigint NOT NULL COMMENT '最大已读消息编号', + "read_time" timestamp NOT NULL COMMENT '最近已读时间', + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id"), + CONSTRAINT "uk_im_conversation_read_user_target" UNIQUE ("user_id", "conversation_type", "target_id", "tenant_id") +) COMMENT 'IM 会话读位置表'; + CREATE TABLE IF NOT EXISTS "im_sensitive_word" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', "word" varchar(128) NOT NULL COMMENT '敏感词',