diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 3f498b3f6..7b8a573d6 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: 16/05/2026 06:24:32 + Date: 31/05/2026 22:29:18 */ SET NAMES utf8mb4; @@ -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 = 23883 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 = 2216 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 @@ -284,17 +284,17 @@ CREATE TABLE `infra_file_config` ( -- Records of infra_file_config -- ---------------------------- BEGIN; -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-01-13 22:11:12', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:03:22', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:47:08', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:56:42', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:18:41', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:43:10', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (29, '本地存储(示例)', 10, 'mac/linux 使用 /,windows 使用 \\', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/tmp/file\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2025-05-02 11:25:45', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (30, 'SFTP 存储(示例)', 12, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig\",\"basePath\":\"/upload\",\"domain\":\"http://127.0.0.1:48080\",\"host\":\"127.0.0.1\",\"port\":2222,\"username\":\"foo\",\"password\":\"pass\"}', '1', '2025-05-02 16:34:10', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (34, '七牛云存储【私有】(示例)', 20, '请换成你自己的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://t151glocd.hn-bkt.clouddn.com\",\"bucket\":\"ruoyi-vue-pro-private\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false}', '1', '2025-08-17 21:22:00', '1', '2025-11-24 20:57:14', b'0'); -INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (35, '1', 20, '1', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://www.baidu.com\",\"domain\":\"http://www.xxx.com\",\"bucket\":\"1\",\"accessKey\":\"2\",\"accessSecret\":\"3\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false,\"region\":\"1\"}', '1', '2025-10-02 14:32:12', '1', '2025-11-29 15:59:39', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (4, '数据库(示例)', 1, '我是数据库', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.db.DBFileClientConfig\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2022-03-15 23:56:24', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (22, '七牛存储器(示例)', 20, '请换成你自己的密钥!!!', b'1', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://test.yudao.iocoder.cn\",\"bucket\":\"ruoyi-vue-pro\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-01-13 22:11:12', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (24, '腾讯云存储(示例)', 20, '请换成你的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"https://cos.ap-shanghai.myqcloud.com\",\"domain\":\"http://tengxun-oss.iocoder.cn\",\"bucket\":\"aoteman-1255880240\",\"accessKey\":\"AKIDAF6WSh1uiIjwqtrOsGSN3WryqTM6cTMt\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:03:22', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (25, '阿里云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"oss-cn-beijing.aliyuncs.com\",\"domain\":\"http://ali-oss.iocoder.cn\",\"bucket\":\"yunai-aoteman\",\"accessKey\":\"LTAI5tEQLgnDyjh3WpNcdMKA\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:47:08', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (26, '火山云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"tos-s3-cn-beijing.volces.com\",\"domain\":null,\"bucket\":\"yunai\",\"accessKey\":\"AKLTZjc3Zjc4MzZmMjU3NDk0ZTgxYmIyMmFkNTIwMDI1ZGE\",\"accessSecret\":\"X==\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 16:56:42', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (27, '华为云存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"obs.cn-east-3.myhuaweicloud.com\",\"domain\":\"\",\"bucket\":\"yudao\",\"accessKey\":\"PVDONDEIOTW88LF8DC4U\",\"accessSecret\":\"X\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:18:41', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (28, 'MinIO 存储(示例)', 20, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://127.0.0.1:9000\",\"domain\":\"http://127.0.0.1:9000/yudao\",\"bucket\":\"yudao\",\"accessKey\":\"admin\",\"accessSecret\":\"password\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":true}', '1', '2024-11-09 17:43:10', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (29, '本地存储(示例)', 10, 'mac/linux 使用 /,windows 使用 \\', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.local.LocalFileClientConfig\",\"basePath\":\"/Users/yunai/tmp/file\",\"domain\":\"http://127.0.0.1:48080\"}', '1', '2025-05-02 11:25:45', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (30, 'SFTP 存储(示例)', 12, '', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileClientConfig\",\"basePath\":\"/upload\",\"domain\":\"http://127.0.0.1:48080\",\"host\":\"127.0.0.1\",\"port\":2222,\"username\":\"foo\",\"password\":\"pass\"}', '1', '2025-05-02 16:34:10', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (34, '七牛云存储【私有】(示例)', 20, '请换成你自己的密钥!!!', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"s3.cn-south-1.qiniucs.com\",\"domain\":\"http://t151glocd.hn-bkt.clouddn.com\",\"bucket\":\"ruoyi-vue-pro-private\",\"accessKey\":\"3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS\",\"accessSecret\":\"wd0tbVBYlp0S-ihA8Qg2hPLncoP83wyrIq24OZuY\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false}', '1', '2025-08-17 21:22:00', '1', '2026-05-17 14:05:15', b'0'); +INSERT INTO `infra_file_config` (`id`, `name`, `storage`, `remark`, `master`, `config`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (35, '1', 20, '1', b'0', '{\"@class\":\"cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig\",\"endpoint\":\"http://www.baidu.com\",\"domain\":\"http://www.xxx.com\",\"bucket\":\"1\",\"accessKey\":\"2\",\"accessSecret\":\"3\",\"enablePathStyleAccess\":false,\"enablePublicAccess\":false,\"region\":\"1\"}', '1', '2025-10-02 14:32:12', '1', '2026-05-17 14:05:15', b'0'); COMMIT; -- ---------------------------- @@ -313,7 +313,7 @@ CREATE TABLE `infra_file_content` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_config_id_path`(`config_id` ASC, `path` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 286 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; +) ENGINE = InnoDB AUTO_INCREMENT = 288 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; -- ---------------------------- -- Records of infra_file_content @@ -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 = 1061096 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 @@ -1095,6 +1095,8 @@ 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 (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', 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 (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', 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 (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', 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 (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52: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 (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', '2026-05-23 13:52:25', '1', '2026-05-23 13:52: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 (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 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 (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32: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 (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', b'0'); @@ -1454,8 +1456,32 @@ 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 (1061091, 1, '退货出库', '200', 'wms_shipment_order_type', 0, 'warning', '', '退货出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', 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 (1061092, 2, '销售出库', '201', 'wms_shipment_order_type', 0, 'primary', '', '销售出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', 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 (1061093, 3, '生产出库', '202', 'wms_shipment_order_type', 0, 'success', '', '生产出库', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', 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 (1061094, 1, '语音', '1', 'im_rtc_call_media_type', 0, '', '', '语音通话', 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', 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 (1061095, 2, '视频', '2', 'im_rtc_call_media_type', 0, '', '', '视频通话', 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', 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 (1061096, 1, '语音', '1', 'im_rtc_call_media_type', 0, '', '', '语音通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', 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 (1061097, 2, '视频', '2', 'im_rtc_call_media_type', 0, '', '', '视频通话', 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', 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 (1061098, 1, '私聊', '1', 'im_rtc_call_conversation_type', 0, 'primary', '', '一对一私聊通话', '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 (1061099, 2, '群聊', '2', 'im_rtc_call_conversation_type', 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 (1061100, 1, '创建', '10', 'im_rtc_call_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 (1061101, 2, '进行中', '20', 'im_rtc_call_status', 0, 'primary', '', '已有人接通,通话中', '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 (1061102, 3, '已结束', '30', 'im_rtc_call_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 (1061103, 1, '通话结束', '1', 'im_rtc_call_end_reason', 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 (1061104, 2, '已拒绝', '2', 'im_rtc_call_end_reason', 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 (1061105, 3, '已取消', '3', 'im_rtc_call_end_reason', 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 (1061106, 4, '无人接听', '4', 'im_rtc_call_end_reason', 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 (1061107, 5, '对方正忙', '5', 'im_rtc_call_end_reason', 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 (1061108, 6, '通话异常', '9', 'im_rtc_call_end_reason', 0, 'danger', '', '网络中断、设备失败等', '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 (1061109, 1, '发起人', '1', 'im_rtc_participant_role', 0, 'primary', '', '通话发起者', '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 (1061110, 2, '被邀请者', '2', 'im_rtc_participant_role', 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 (1061111, 3, '主动加入者', '3', 'im_rtc_participant_role', 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 (1061112, 1, '邀请中', '10', 'im_rtc_participant_status', 0, 'info', '', '已发出 invite,等待响应', '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 (1061113, 2, '已加入', '20', 'im_rtc_participant_status', 0, 'primary', '', '已接通并进入房间', '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 (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_message_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_message_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_message_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'); COMMIT; -- ---------------------------- @@ -1475,7 +1501,7 @@ CREATE TABLE `system_dict_type` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1061092 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1061099 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; -- ---------------------------- -- Records of system_dict_type @@ -1682,7 +1708,13 @@ 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 (1061070, 'WMS 单据状态', 'wms_order_status', 0, 'WMS 单据状态', '1', '2026-05-12 13:40:29', '1', '2026-05-12 13:40:29', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061080, '入库单类型', 'wms_receipt_order_type', 0, 'WMS 入库单类型', '1', '2026-05-11 11:21:49', '1', '2026-05-12 13:40:29', b'0', NULL); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061090, '出库单类型', 'wms_shipment_order_type', 0, 'WMS 出库单类型', '1', '2026-05-12 17:48:35', '1', '2026-05-12 17:48:35', b'0', NULL); -INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061091, 'IM 通话媒体类型', 'im_rtc_call_media_type', 0, NULL, 'admin', '2026-05-14 13:48:31', 'admin', '2026-05-14 13:48:31', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061092, 'IM 通话媒体类型', 'im_rtc_call_media_type', 0, NULL, 'admin', '2026-05-16 11:34:50', 'admin', '2026-05-16 11:34:50', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061093, 'IM 通话会话类型', 'im_rtc_call_conversation_type', 0, '1=私聊;2=群聊', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061094, 'IM 通话状态', 'im_rtc_call_status', 0, '10=创建;20=进行中;30=已结束', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061095, 'IM 通话结束原因', 'im_rtc_call_end_reason', 0, '1=通话结束;2=已拒绝;3=已取消;4=无人接听;5=对方正忙;9=通话异常', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061096, 'IM 通话参与角色', 'im_rtc_participant_role', 0, '1=发起人;2=被邀请者;3=主动加入者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061097, 'IM 通话参与状态', 'im_rtc_participant_status', 0, '10=邀请中;20=已加入;30=已拒绝;40=未应答;50=已离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061098, 'IM 频道素材内容类型', 'im_channel_material_type', 0, '1=站内富文本 / 2=外链', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0', NULL); COMMIT; -- ---------------------------- @@ -1708,7 +1740,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 = 4557 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 @@ -1841,7 +1873,7 @@ CREATE TABLE `system_menu` ( `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 = 6611 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; +) ENGINE = InnoDB AUTO_INCREMENT = 6735 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; -- ---------------------------- -- Records of system_menu @@ -2773,7 +2805,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 (5034, 'OTA 固件创建', 'iot:ota-firmware:create', 3, 2, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38:21', 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 (5035, 'OTA 固件更新', 'iot:ota-firmware:update', 3, 3, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38: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 (5036, 'OTA 固件删除', 'iot:ota-firmware:delete', 3, 4, 5032, '', '', '', '', 0, b'1', b'1', b'1', '', '2025-06-30 07:50:29', '\"1\"', '2025-06-30 17:38:37', 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 (5037, 'OTA 升级任务查询', 'iot:ota-task:create', 3, 11, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:56:56', '1', '2025-07-02 23:56:56', 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 (5037, 'OTA 升级任务查询', 'iot:ota-task:query', 3, 11, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:56:56', '1', '2026-05-19 08:48: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 (5038, 'OTA 升级任务取消', 'iot:ota-task:cancel', 3, 13, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:57:26', '1', '2025-07-02 23:57:26', 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 (5039, 'OTA 升级任务创建', 'iot:ota-task:create', 3, 12, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:57:52', '1', '2025-07-02 23:57:52', 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 (5040, 'OTA 升级记录查询', 'iot:ota-task-record:query', 3, 21, 5032, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-07-02 23:58:30', '1', '2025-07-02 23:58:30', b'0'); @@ -3270,6 +3302,24 @@ 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 (6582, '用户表情删除', 'im:manager:face-user-item:delete', 3, 20, 6580, '', '', '', '', 0, b'1', b'1', b'1', '', '2026-05-06 13:56:08', '', '2026-05-06 13:56:08', 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 (6600, '私聊管理', '', 1, 20, 6500, 'private', 'ep:chat-round', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40: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 (6610, '群聊管理', '', 1, 30, 6500, 'group', 'ep:chat-line-round', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-07 00:40:35', '1', '2026-05-07 00:40: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 (6611, '通话记录', '', 2, 40, 6500, 'rtc', 'ep:phone', 'im/manager/rtc/index', 'ImRtcCall', 0, b'1', b'1', b'1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', 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 (6612, '通话记录查询', 'im:manager:rtc:query', 3, 1, 6611, '', '', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', 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 (6700, '频道管理', '', 1, 90, 6500, 'channel', 'ep:promotion', NULL, NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6710, '频道列表', '', 2, 1, 6700, 'list', 'ep:promotion', 'im/manager/channel/list/index', 'ImChannel', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6711, '频道查询', 'im:manager:channel:query', 3, 1, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6712, '频道创建', 'im:manager:channel:create', 3, 2, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6713, '频道修改', 'im:manager:channel:update', 3, 3, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6714, '频道删除', 'im:manager:channel:delete', 3, 4, 6710, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6720, '频道素材', '', 2, 2, 6700, 'material', 'ep:document', 'im/manager/channel/material/index', 'ImChannelMaterial', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6721, '素材查询', 'im:manager:channel-material:query', 3, 1, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6722, '素材创建', 'im:manager:channel-material:create', 3, 2, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6723, '素材修改', 'im:manager:channel-material:update', 3, 3, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6724, '素材删除', 'im:manager:channel-material:delete', 3, 4, 6720, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6730, '频道消息', '', 2, 3, 6700, 'message', 'ep:message', 'im/manager/channel/message/index', 'ImChannelMessage', 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-19 09:44:34', 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 (6731, '消息查询', 'im:manager:channel-message:query', 3, 1, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6732, '立即推送', 'im:manager:channel-message:send', 3, 2, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6733, '消息删除', 'im:manager:channel-message:delete', 3, 3, 6730, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-18 13:14:34', '1', '2026-05-18 13:14:34', 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 (6734, '解散群', 'im:manager:group:dissolve', 3, 21, 6540, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2026-05-24 12:02:38', '1', '2026-05-24 12:02:38', b'0'); COMMIT; -- ---------------------------- @@ -3392,7 +3442,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 = 54457 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 @@ -3518,7 +3568,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 = 2590 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 @@ -3554,7 +3604,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 = 9194 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 @@ -4952,16 +5002,16 @@ CREATE TABLE `system_users` ( INDEX `idx_mobile`(`mobile` ASC) USING BTREE, INDEX `idx_email`(`email` ASC) USING BTREE, INDEX `idx_dept_id`(`dept_id` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 145 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表'; +) ENGINE = InnoDB AUTO_INCREMENT = 225 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表'; -- ---------------------------- -- 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/user/avatar/20251220/blob_1766215463801.jpg', 0, '0:0:0:0:0:0:0:1', '2026-05-15 22:24:39', 'admin', '2021-01-05 17:03:47', NULL, '2026-05-15 22:24:39', 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-05-09 09:57:13', '', '2021-01-21 02:13:53', NULL, '2026-05-09 09:57:13', 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); @@ -4977,7 +5027,7 @@ INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, 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', 'aoteman001', NULL, 116, 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', '2025-12-15 15:55:54', 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-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java index ffc36c60b..b2fd55f63 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java @@ -238,6 +238,11 @@ public class LocalDateTimeUtils { /** * 获取最近 N 天的 0 点时刻序列(升序,含今天) + *

+ * 例:getLatestDays(3) 返回 [前天 00:00, 昨天 00:00, 今天 00:00] + * + * @param days 天数(含今天) + * @return 升序的 LocalDateTime 列表 */ public static List getLatestDays(int days) { LocalDateTime today = getToday(); diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 04eb7cb92..95220a9fb 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -9,6 +9,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import java.net.URI; import java.net.URLDecoder; @@ -55,11 +56,61 @@ public class HttpUtils { * @return 解码后的路径 */ public static String decodeUrlPath(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } // 先将 + 替换为 %2B,避免被 URLDecoder 解码为空格 String encoded = path.replace("+", "%2B"); return URLDecoder.decode(encoded, StandardCharsets.UTF_8); } + /** + * 编码 URL 路径,按路径段编码,保留 / 分隔符 + * + * @param path URL 路径,例如 20250602/xxx.pdf + * @return 编码后的路径 + */ + public static String encodeUrlPath(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } + String[] segments = path.split(StrUtil.SLASH, -1); + StringBuilder result = new StringBuilder(path.length()); + for (int i = 0; i < segments.length; i++) { + if (i > 0) { + result.append(StrUtil.SLASH); + } + result.append(encodeUrlPathSegment(segments[i])); + } + return result.toString(); + } + + /** + * 编码 URL 路径段 + * + * @param segment URL 路径段 + * @return 编码后的路径段 + */ + public static String encodeUrlPathSegment(String segment) { + return UriUtils.encodePathSegment(segment, StandardCharsets.UTF_8); + } + + public static String removeUrlPathQueryAndFragment(String path) { + if (StrUtil.isEmpty(path)) { + return path; + } + int endIndex = path.length(); + int queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + endIndex = queryIndex; + } + int fragmentIndex = path.indexOf('#'); + if (fragmentIndex >= 0 && fragmentIndex < endIndex) { + endIndex = fragmentIndex; + } + return path.substring(0, endIndex); + } + public static String replaceUrlQuery(String url, String key, String value) { UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); // 先移除;再添加 @@ -202,6 +253,9 @@ public class HttpUtils { /** * WebSocket URL 切换成 HTTP URL:ws:// → http://;wss:// → https://;其它格式原样保留 + * + * @param url 原始 URL + * @return 切换协议后的 URL */ public static String wsUrlToHttp(String url) { return StrUtil.startWithIgnoreCase(url, "ws") ? "http" + url.substring(2) : url; diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java index fc17917c5..6bb1608d8 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/JsonUtils.java @@ -176,6 +176,9 @@ public class JsonUtils { /** * 解析 JSON 字符串成 Map,空字符串或解析失败返回 null + * + * @param text JSON 字符串 + * @return Map 对象 */ public static Map parseMap(String text) { if (StrUtil.isEmpty(text)) { diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java index 7332a57a7..f0e423e8c 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/string/StrUtils.java @@ -81,6 +81,15 @@ public class StrUtils { /** * 转小写拼音,字之间以空格分隔,便于调用方按需拼接 / 取首字母 / 拼音搜索 + * + * 例:「老张」→ "lao zhang"、「ZhangSan」→ "zhangsan" + * 英文 / 数字 / 符号原样返回,空值返回 null + * + * 注意:底层依赖 hutool-extra 的 {@link PinyinUtil},需要业务模块自行引入拼音引擎依赖 + * (pinyin4j / TinyPinyin / Bopomofo4j 任选其一),否则运行时会抛 NoClassDefFoundError + * + * @param str 字符串 + * @return 拼音串(保留空格分隔) */ public static String toPinyin(String str) { if (StrUtil.isBlank(str)) { diff --git a/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java index a42874f04..e2b612007 100644 --- a/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java +++ b/yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/http/HttpUtilsTest.java @@ -9,6 +9,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ public class HttpUtilsTest { + @Test + public void testEncodeUrlPath() { + // 准备参数 + String path = "avatar/中文 100%+文件.jpg"; + + // 调用 + String result = HttpUtils.encodeUrlPath(path); + + // 断言 + assertEquals("avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg", result); + } + + @Test + public void testDecodeUrlPath() { + // 准备参数:+ 是路径字符,不应该按 query parameter 语义解码为空格 + String path = "avatar/%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.jpg"; + + // 调用 + String result = HttpUtils.decodeUrlPath(path); + + // 断言 + assertEquals("avatar/中文 100%+文件.jpg", result); + } + + @Test + public void testRemoveUrlPathQueryAndFragment() { + assertEquals("avatar/test.jpg", HttpUtils.removeUrlPathQueryAndFragment("avatar/test.jpg?token=1#preview")); + assertEquals("avatar/test.jpg", HttpUtils.removeUrlPathQueryAndFragment("avatar/test.jpg#preview?token=1")); + } + @Test public void testReplaceUrlQuery_replace() { // 准备参数 diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java index f9ca67adb..485e1aa7d 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.tenant.config; -import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.biz.system.tenant.TenantCommonApi; import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; @@ -31,7 +30,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.BatchStrategies; import org.springframework.data.redis.cache.RedisCacheConfiguration; @@ -46,10 +44,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.util.pattern.PathPattern; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -63,13 +58,6 @@ public class YudaoTenantAutoConfiguration { @Bean public TenantFrameworkService tenantFrameworkService(TenantCommonApi tenantApi) { - // 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC6YZF - try { - TenantCommonApi tenantApiImpl = SpringUtil.getBean("tenantApiImpl", TenantCommonApi.class); - if (tenantApiImpl != null) { - tenantApi = tenantApiImpl; - } - } catch (Exception ignored) {} return new TenantFrameworkServiceImpl(tenantApi); } @@ -167,20 +155,9 @@ public class YudaoTenantAutoConfiguration { // ========== MQ ========== - /** - * 多租户 Redis 消息队列的配置类 - * - * 为什么要单独一个配置类呢?如果直接把 TenantRedisMessageInterceptor Bean 的初始化放外面,会报 RedisMessageInterceptor 类不存在的错误 - */ - @Configuration - @ConditionalOnClass(name = "cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate") - public static class TenantRedisMQAutoConfiguration { - - @Bean - public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { - return new TenantRedisMessageInterceptor(); - } - + @Bean + public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { + return new TenantRedisMessageInterceptor(); } @Bean @@ -198,7 +175,6 @@ public class YudaoTenantAutoConfiguration { // ========== Job ========== @Bean - @ConditionalOnClass(name = "com.xxl.job.core.handler.annotation.XxlJob") public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) { return new TenantJobAspect(tenantFrameworkService); } @@ -216,7 +192,12 @@ public class YudaoTenantAutoConfiguration { RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); // 创建 TenantRedisCacheManager 对象 - return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches()); + TenantRedisCacheManager cacheManager = new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, + tenantProperties.getIgnoreCaches()); + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit, + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变 + cacheManager.setTransactionAware(true); + return cacheManager; } } diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java index b80244456..5b0d8866c 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessag import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RedissonClient; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; @@ -70,7 +69,8 @@ public class YudaoRedisMQConsumerAutoConfiguration { public RedisPendingMessageResendJob redisPendingMessageResendJob(List> listeners, RedisMQTemplate redisTemplate, RedissonClient redissonClient) { - return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient); + return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient, + RedisPendingMessageResendJob.DEFAULT_RESEND_LOCK_KEY); } /** @@ -81,7 +81,8 @@ public class YudaoRedisMQConsumerAutoConfiguration { public RedisStreamMessageCleanupJob redisStreamMessageCleanupJob(List> listeners, RedisMQTemplate redisTemplate, RedissonClient redissonClient) { - return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient); + return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient, + RedisStreamMessageCleanupJob.DEFAULT_CLEANUP_LOCK_KEY); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java index bb16be0ee..d0b7ae309 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java @@ -23,7 +23,9 @@ import java.util.Objects; @AllArgsConstructor public class RedisPendingMessageResendJob { - private static final String LOCK_KEY = "redis:stream:pending-message-resend:lock"; + public static final String DEFAULT_RESEND_LOCK_KEY = "redis:stream:pending-message-resend:lock"; + + public static final String IOT_RESEND_LOCK_KEY = "redis:stream:pending-message-resend:lock:iot"; /** * 消息超时时间,默认 5 分钟 @@ -36,22 +38,26 @@ public class RedisPendingMessageResendJob { private final List> listeners; private final RedisMQTemplate redisTemplate; private final RedissonClient redissonClient; + private final String resendLockKey; /** * 一分钟执行一次,这里选择每分钟的 35 秒执行,是为了避免整点任务过多的问题 */ @Scheduled(cron = "35 * * * * ?") public void messageResend() { - RLock lock = redissonClient.getLock(LOCK_KEY); - // 尝试加锁 + RLock lock = redissonClient.getLock(resendLockKey); if (lock.tryLock()) { try { execute(); } catch (Exception ex) { - log.error("[messageResend][执行异常]", ex); + log.error("[messageResend][执行异常][lockKey={}]", resendLockKey, ex); } finally { - lock.unlock(); + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } } + } else { + log.debug("[messageResend][未获取到锁,跳过本轮][lockKey={}]", resendLockKey); } } diff --git a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java index 19da84594..eff64fd4d 100644 --- a/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java +++ b/yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java @@ -23,7 +23,16 @@ import java.util.List; @AllArgsConstructor public class RedisStreamMessageCleanupJob { - private static final String LOCK_KEY = "redis:stream:message-cleanup:lock"; + /** + * 业务 MQ(Spring 容器内 AbstractRedisStreamMessageListener)清理任务使用的分布式锁 + */ + public static final String DEFAULT_CLEANUP_LOCK_KEY = "redis:stream:message-cleanup:lock"; + + /** + * IoT Redis 总线清理任务使用的分布式锁(须与 {@link #DEFAULT_CLEANUP_LOCK_KEY} 区分,否则会共抢一把锁, + * 同一时刻只有一侧能执行 XTRIM,另一侧 Stream 可能无限积压) + */ + public static final String IOT_CLEANUP_LOCK_KEY = "redis:stream:message-cleanup:lock:iot"; /** * 保留的消息数量,默认保留最近 10000 条消息 @@ -33,22 +42,29 @@ public class RedisStreamMessageCleanupJob { private final List> listeners; private final RedisMQTemplate redisTemplate; private final RedissonClient redissonClient; + /** + * Redisson 锁键(多 Bean 注册清理任务时必须各不相同) + */ + private final String cleanupLockKey; /** * 每小时执行一次清理任务 */ @Scheduled(cron = "0 0 * * * ?") public void cleanup() { - RLock lock = redissonClient.getLock(LOCK_KEY); - // 尝试加锁 + RLock lock = redissonClient.getLock(cleanupLockKey); if (lock.tryLock()) { try { execute(); } catch (Exception ex) { - log.error("[cleanup][执行异常]", ex); + log.error("[cleanup][执行异常][lockKey={}]", cleanupLockKey, ex); } finally { - lock.unlock(); + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } } + } else { + log.debug("[cleanup][未获取到锁,跳过本轮][lockKey={}]", cleanupLockKey); } } @@ -59,8 +75,8 @@ public class RedisStreamMessageCleanupJob { StreamOperations ops = redisTemplate.getRedisTemplate().opsForStream(); listeners.forEach(listener -> { try { - // 使用 XTRIM 命令清理消息,只保留最近的 MAX_LEN 条消息 - Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true); + // 使用 XTRIM MAXLEN 精确裁剪(approximate=false),避免 ~ 模式下长期明显高于上限 + Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, false); if (trimCount != null && trimCount > 0) { log.info("[execute][Stream({}) 清理消息数量({})]", listener.getStreamKey(), trimCount); } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 460ea7811..62a5b0830 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java @@ -20,51 +20,49 @@ public enum DbTypeEnum { /** * H2 - * - * 注意:H2 不支持 find_in_set 函数 */ - H2(DbType.H2, "H2", ""), + H2(DbType.H2, "H2", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * MySQL */ - MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET('#{value}', #{column}) <> 0"), + MY_SQL(DbType.MYSQL, "MySQL", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * Oracle */ - ORACLE(DbType.ORACLE, "Oracle", "FIND_IN_SET('#{value}', #{column}) <> 0"), + ORACLE(DbType.ORACLE, "Oracle", "INSTR(',' || #{column} || ',', ',' || #{value} || ',') > 0"), /** * PostgreSQL * * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同 */ - POSTGRE_SQL(DbType.POSTGRE_SQL,"PostgreSQL", "POSITION('#{value}' IN #{column}) <> 0"), + POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * SQL Server */ - SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER(DbType.SQL_SERVER, "Microsoft SQL Server", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * SQL Server 2005 */ - SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + #{value} + ',', ',' + #{column} + ',') <> 0"), + SQL_SERVER2005(DbType.SQL_SERVER2005, "Microsoft SQL Server 2005", "CHARINDEX(',' + CAST(#{value} AS varchar(255)) + ',', ',' + #{column} + ',') > 0"), /** * 达梦 */ - DM(DbType.DM, "DM DBMS", "FIND_IN_SET('#{value}', #{column}) <> 0"), + DM(DbType.DM, "DM DBMS", "FIND_IN_SET(#{value}, #{column}) <> 0"), /** * 人大金仓 */ - KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION('#{value}' IN #{column}) <> 0"), + KINGBASE_ES(DbType.KINGBASE_ES, "KingbaseES", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), /** * OceanBase */ - OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET('#{value}', #{column}) <> 0") + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0") ; @@ -95,7 +93,9 @@ public enum DbTypeEnum { } public static String getFindInSetTemplate(DbType dbType) { - return Optional.of(MAP_BY_MP.get(dbType).getFindInSetTemplate()) + return Optional.ofNullable(MAP_BY_MP.get(dbType)) + .map(DbTypeEnum::getFindInSetTemplate) + .filter(StrUtil::isNotBlank) .orElseThrow(() -> new IllegalArgumentException("FIND_IN_SET not supported")); } } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index d72ccc7bf..08bdf19c4 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -174,6 +174,9 @@ public interface BaseMapperX extends MPJBaseMapper { * 获取满足条件的最新一条记录 *

* 目的:解决并发场景下,插入多条记录后,使用 selectOne 会报错的问题 + * + * @param queryWrapper 查询条件 + * @return 最新一条;不存在返回 null */ default T selectLastOne(LambdaQueryWrapper queryWrapper) { return CollUtil.getLast(selectList(queryWrapper)); diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java index a23930187..ab4612cb0 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java @@ -34,6 +34,10 @@ public class MyBatisUtils { private static final Pattern SAFE_COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)*$"); + private static final String FIND_IN_SET_VALUE_PLACEHOLDER = "#{value}"; + + private static final String FIND_IN_SET_COLUMN_PLACEHOLDER = "#{column}"; + public static Page buildPage(PageParam pageParam) { return buildPage(pageParam, null); } @@ -157,15 +161,43 @@ public class MyBatisUtils { /** * 跨数据库的 find_in_set 实现 * - * @param column 字段名称 - * @param value 查询值(不带单引号) + * @param columnName 字段名称 * @return sql */ - public static String findInSet(String column, Object value) { + public static String findInSet(String columnName) { + return findInSet(columnName, 0); + } + + /** + * 跨数据库的 find_in_set 实现,适用于同一个 apply 语句中有多个参数的场景 + * + * @param columnName 字段名称 + * @param paramIndex apply 参数序号 + * @return sql + */ + public static String findInSetWithParamIndex(String columnName, int paramIndex) { + return findInSet(columnName, paramIndex); + } + + private static String findInSet(String columnName, int paramIndex) { DbType dbType = JdbcUtils.getDbType(); + return findInSet(dbType, columnName, paramIndex); + } + + static String findInSet(DbType dbType, String columnName, int paramIndex) { + if (!isSafeColumnName(columnName)) { + throw new IllegalArgumentException("Invalid column name: " + columnName); + } + if (paramIndex < 0) { + throw new IllegalArgumentException("Invalid param index: " + paramIndex); + } return DbTypeEnum.getFindInSetTemplate(dbType) - .replace("#{column}", column) - .replace("#{value}", StrUtil.toString(value)); + .replace(FIND_IN_SET_COLUMN_PLACEHOLDER, columnName) + .replace(FIND_IN_SET_VALUE_PLACEHOLDER, "{" + paramIndex + "}"); + } + + private static boolean isSafeColumnName(String columnName) { + return StrUtil.isNotEmpty(columnName) && SAFE_COLUMN_NAME_PATTERN.matcher(columnName).matches(); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java index 43f5611dc..abe889154 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.mybatis.core.util; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.SortingField; +import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.OrderItem; @@ -13,6 +14,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -98,6 +100,71 @@ public class MyBatisUtilsTest { assertEquals("DESC", MyBatisUtils.getOrderDirection(null)); } + @Test + public void testFindInSet() { + assertEquals("FIND_IN_SET({0}, websites) <> 0", + MyBatisUtils.findInSet(DbType.MYSQL, "websites", 0)); + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.H2, "websites", 0)); + assertEquals("INSTR(',' || t.websites || ',', ',' || {0} || ',') > 0", + MyBatisUtils.findInSet(DbType.ORACLE, "t.websites", 0)); + assertEquals("POSITION(',' || CAST({1} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", + MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1)); + assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0", + MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2)); + } + + @Test + public void testFindInSet_invalidColumnName() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites;drop table system_tenant", 0)); + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "FIND_IN_SET(value, websites)", 0)); + } + + @Test + public void testFindInSet_invalidParamIndex() { + assertThrows(IllegalArgumentException.class, + () -> MyBatisUtils.findInSet(DbType.MYSQL, "websites", -1)); + } + + @Test + public void testFindInSet_applyBindsValue() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value = "test' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "to_mails", 0), value); + + // 断言:SQL 片段里只有 MyBatis Plus 参数占位,用户输入不会被直接拼接进去 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, to_mails) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value)); + assertEquals(value, query.getParamNameValuePairs().get("MPGENVAL1")); + } + + @Test + public void testFindInSet_applyBindsMultipleValues() { + // 准备参数 + QueryWrapper query = new QueryWrapper<>(); + String value1 = "1' OR 1 = 1"; + String value2 = "2' OR 1 = 1"; + + // 调用 + query.apply(MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 0) + + " OR " + MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", 1), value1, value2); + + // 断言:多个参数都由 MyBatis Plus 生成占位符,不拼接用户输入 + assertEquals("(FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL1}, tag_ids) <> 0" + + " OR FIND_IN_SET(#{ew.paramNameValuePairs.MPGENVAL2}, tag_ids) <> 0)", + query.getSqlSegment()); + assertFalse(query.getSqlSegment().contains(value1)); + assertFalse(query.getSqlSegment().contains(value2)); + assertEquals(value1, query.getParamNameValuePairs().get("MPGENVAL1")); + assertEquals(value2, query.getParamNameValuePairs().get("MPGENVAL2")); + } + private void assertOrderItem(OrderItem orderItem, String column, boolean asc) { assertEquals(column, orderItem.getColumn()); assertEquals(asc, orderItem.isAsc()); diff --git a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java index 9e84c4a49..afdb32cc7 100644 --- a/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-redis/src/main/java/cn/iocoder/yudao/framework/redis/config/YudaoCacheAutoConfiguration.java @@ -75,8 +75,12 @@ public class YudaoCacheAutoConfiguration { RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory()); RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(yudaoCacheProperties.getRedisScanBatchSize())); - // 创建 TenantRedisCacheManager 对象 - return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration); + // 创建 TimeoutRedisCacheManager 对象 + TimeoutRedisCacheManager cacheManager = new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration); + // 开启事务感知:@Transactional 方法内的 @CacheEvict / @CachePut 自动延迟到 afterCommit, + // 避免事务未提交就清缓存被并发读穿写脏值;无事务时立即生效,行为不变 + cacheManager.setTransactionAware(true); + return cacheManager; } } diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java index 120f529c2..88ceee627 100644 --- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/handler/JsonWebSocketMessageHandler.java @@ -76,7 +76,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler { Long tenantId = WebSocketFrameworkUtils.getTenantId(session); TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj)); } catch (Throwable ex) { - log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload()); + log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload(), ex); } } diff --git a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java index 3a31825f5..f3392f6b5 100644 --- a/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java +++ b/yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/core/security/LoginUserHandshakeInterceptor.java @@ -27,9 +27,10 @@ public class LoginUserHandshakeInterceptor implements HandshakeInterceptor { public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); - if (loginUser != null) { - WebSocketFrameworkUtils.setLoginUser(loginUser, attributes); + if (loginUser == null) { + return false; } + WebSocketFrameworkUtils.setLoginUser(loginUser, attributes); return true; } diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/tool/function/UserProfileQueryToolFunction.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/tool/function/UserProfileQueryToolFunction.java index 06a2641da..a4e00d644 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/tool/function/UserProfileQueryToolFunction.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/tool/function/UserProfileQueryToolFunction.java @@ -84,7 +84,7 @@ public class UserProfileQueryToolFunction request.setId(loginUser.getId()); } return TenantUtils.execute(tenantId, () -> { - AdminUserRespDTO user = adminUserApi.getUser(request.getId()).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(request.getId()); return BeanUtils.toBean(user, Response.class); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java index bf293c314..09df9b7e6 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -143,10 +143,10 @@ public class BpmProcessInstanceController { processInstance.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); - AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); + AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null && startUser.getDeptId() != null) { - dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); + dept = deptApi.getDept(startUser.getDeptId()); } return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, processDefinition, processDefinitionInfo, startUser, dept)); @@ -211,8 +211,8 @@ public class BpmProcessInstanceController { if (historicProcessInstance == null) { throw exception(PROCESS_INSTANCE_NOT_EXISTS); } - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(historicProcessInstance.getStartUserId())).getCheckedData(); - DeptRespDTO dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(historicProcessInstance.getStartUserId())); + DeptRespDTO dept = deptApi.getDept(startUser.getDeptId()); List tasks = taskService.getFinishedTaskListByProcessInstanceIdWithoutCancel(processInstanceId); Map userMap = adminUserApi.getUserMap( convertSet(tasks, item -> Long.valueOf(item.getAssignee()))); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java index 68593bb4c..e9180c869 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java @@ -57,7 +57,7 @@ public class BpmTaskAssignLeaderExpression { return emptySet(); } } else { - DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData(); + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); if (parentDept == null) { // 找不到父级部门,所以只好结束寻找。原因是:例如说,级别比较高的人,所在部门层级比较少 break; } @@ -68,11 +68,11 @@ public class BpmTaskAssignLeaderExpression { } private DeptRespDTO getStartUserDept(Long startUserId) { - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData(); + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); if (startUser.getDeptId() == null) { // 找不到部门,所以无法使用该规则 return null; } - return deptApi.getDept(startUser.getDeptId()).getCheckedData(); + return deptApi.getDept(startUser.getDeptId()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java index 5cd249252..1a2b067c0 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java @@ -40,7 +40,7 @@ public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmT } DeptRespDTO currentDept = dept; for (int i = 1; i < level; i++) { - DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()).getCheckedData(); + DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()); if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 break; } @@ -63,12 +63,12 @@ public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmT } Set deptLeaderIds = new LinkedHashSet<>(); // 保证有序 for (Long deptId : deptIds) { - DeptRespDTO dept = deptApi.getDept(deptId).getCheckedData(); + DeptRespDTO dept = deptApi.getDept(deptId); for (int i = 0; i < level; i++) { if (dept.getLeaderUserId() != null) { deptLeaderIds.add(dept.getLeaderUserId()); } - DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData(); + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()); if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 break; } @@ -84,11 +84,11 @@ public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmT * @param startUserId 发起人 Id */ protected DeptRespDTO getStartUserDept(Long startUserId) { - AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData(); + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId); if (startUser.getDeptId() == null) { // 找不到部门 return null; } - return deptApi.getDept(startUser.getDeptId()).getCheckedData(); + return deptApi.getDept(startUser.getDeptId()); } } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java index 61767ff89..ce8b2accb 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -30,7 +30,7 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCand List deptIds = StrUtils.splitToLong(params[0], ","); int level = Integer.parseInt(params[1]); // 校验部门存在 - deptApi.validateDeptList(deptIds).checkError(); + deptApi.validateDeptList(deptIds); Assert.isTrue(level > 0, "部门层级必须大于 0"); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java index f5d4a6a26..336f97b7f 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java @@ -32,13 +32,13 @@ public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrat @Override public void validateParam(String param) { Set deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds).checkError(); + deptApi.validateDeptList(deptIds); } @Override public Set calculateUsers(String param) { Set deptIds = StrUtils.splitToLongSet(param); - List depts = deptApi.getDeptList(deptIds).getCheckedData(); + List depts = deptApi.getDeptList(deptIds); return convertSet(depts, DeptRespDTO::getLeaderUserId); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java index aa1059933..703ec7d5e 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java @@ -35,13 +35,13 @@ public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrat @Override public void validateParam(String param) { Set deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds).checkError(); + deptApi.validateDeptList(deptIds); } @Override public Set calculateUsers(String param) { Set deptIds = StrUtils.splitToLongSet(param); - List users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); + List users = adminUserApi.getUserListByDeptIds(deptIds); return convertSet(users, AdminUserRespDTO::getId); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java index 573b2ff08..873c4bad1 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java @@ -41,7 +41,7 @@ public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { @Override public Set calculateUsers(String param) { Set postIds = StrUtils.splitToLongSet(param); - List users = adminUserApi.getUserListByPostIds(postIds).getCheckedData(); + List users = adminUserApi.getUserListByPostIds(postIds); return convertSet(users, AdminUserRespDTO::getId); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java index 44fb1df4c..15002d91f 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java @@ -28,7 +28,7 @@ public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { @Override public void validateParam(String param) { - adminUserApi.validateUserList(StrUtils.splitToLongSet(param)).checkError(); + adminUserApi.validateUserList(StrUtils.splitToLongSet(param)); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 36ee811b4..17051c708 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -456,6 +456,54 @@ public class BpmnModelUtils { return new ArrayList<>(); } + /** + * 根据节点,递归获取上游 source 为 UserTask 的入口连线 + * + * 1. 如果当前节点的直接入口连线 source 就是 UserTask,则直接返回该连线 + * 2. 如果当前节点的直接入口连线 source 不是 UserTask,则继续向上递归查找 + * 3. 如果递归过程中遇到 StartEvent 或 SubProcess,则停止该分支继续向上查找 + * + * @param source 起始节点 + * @return 上游连接 UserTask 的入口连线列表 + */ + public static List getElementIncomingUserTaskFlows(FlowElement source) { + List result = new ArrayList<>(); + collectElementIncomingUserTaskFlows(source, new HashSet<>(), new HashSet<>(), result); + return result; + } + + private static void collectElementIncomingUserTaskFlows(FlowElement source, Set visitedSequenceFlowIds, + Set resultSequenceFlowIds, List result) { + // 如果是开始节点或子流程,则停止该分支向上查找 + if (source == null || source instanceof StartEvent || source instanceof SubProcess) { + return; + } + // 获取入口连线 + List incomingFlows = getElementIncomingFlows(source); + if (CollUtil.isEmpty(incomingFlows)) { + return; + } + + // 循环找到目标元素 + for (SequenceFlow incomingFlow : incomingFlows) { + // 如果发现连线重复,说明连线已经走过。跳过 + if (incomingFlow == null || !visitedSequenceFlowIds.add(incomingFlow.getId())) { + continue; + } + // 如果 source 是 UserTask,则添加到结果中 + FlowElement sourceFlowElement = incomingFlow.getSourceFlowElement(); + if (sourceFlowElement instanceof UserTask) { + if (resultSequenceFlowIds.add(incomingFlow.getId())) { + result.add(incomingFlow); + } + continue; + } + // 递归向上查找 UserTask + collectElementIncomingUserTaskFlows(sourceFlowElement, visitedSequenceFlowIds, + resultSequenceFlowIds, result); + } + } + /** * 根据节点,获取出口连线 * @@ -673,7 +721,7 @@ public class BpmnModelUtils { // 这条线路存在目标节点,直接返回 true FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); if (target.getId().equals(sourceFlowElement.getId())) { - return true; + return true; } // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?) if (sourceFlowElement instanceof ParallelGateway) { @@ -798,9 +846,9 @@ public class BpmnModelUtils { // 情况:StartEvent/EndEvent/UserTask/ServiceTask if (currentElement instanceof StartEvent - || currentElement instanceof EndEvent - || currentElement instanceof UserTask - || currentElement instanceof ServiceTask) { + || currentElement instanceof EndEvent + || currentElement instanceof UserTask + || currentElement instanceof ServiceTask) { // 添加节点 FlowNode flowNode = (FlowNode) currentElement; resultElements.add(flowNode); @@ -982,8 +1030,8 @@ public class BpmnModelUtils { */ private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map variables) { SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) - && (evalConditionExpress(variables, flow.getConditionExpression()))); + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) + && (evalConditionExpress(variables, flow.getConditionExpression()))); if (matchSequenceFlow == null) { matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index 01a09338e..836b5181c 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -101,7 +101,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ // 校验用户是否在允许发起的部门列表中 if (CollUtil.isNotEmpty(processDefinition.getStartDeptIds())) { - AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(userId); return user != null && user.getDeptId() != null && processDefinition.getStartDeptIds().contains(user.getDeptId()); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index c55d8df0c..5344dd7c5 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -872,7 +872,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService if (titleSetting == null || !BooleanUtil.isTrue(titleSetting.getEnable())) { return definition.getName(); } - AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(userId); Map cloneVariables = new HashMap<>(variables); cloneVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, user.getNickname()); cloneVariables.put(BpmnVariableConstants.PROCESS_START_TIME, DateUtil.now()); @@ -920,7 +920,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } // 2. 取消流程 - AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(userId); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 5569f09a6..c178ea270 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -618,7 +618,14 @@ public class BpmTaskServiceImpl implements BpmTaskService { runtimeService.setVariable(task.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskIdsByReturn); } - // 6. 调用 BPM complete 去完成任务 + // 6. 清理退回设置的不自动通过的变量。仅在该标记存在时才删除,避免每次完成任务都产生无谓的 DB delete + String returnFlagKey = String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()); + if (runtimeService.hasVariable(task.getProcessInstanceId(), returnFlagKey)) { + log.info("[approveTask][taskId({}) 清理退回标记变量({})]", task.getId(), returnFlagKey); + runtimeService.removeVariable(task.getProcessInstanceId(), returnFlagKey); + } + + // 7. 调用 BPM complete 去完成任务 taskService.complete(task.getId(), variables, true); // 【加签专属】处理加签任务 @@ -780,8 +787,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { */ private void approveDelegateTask(BpmTaskApproveReqVO reqVO, Task task) { // 1. 添加审批意见 - AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId()).getCheckedData(); - AdminUserRespDTO ownerUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); // 发起委托的用户 + AdminUserRespDTO currentUser = adminUserApi.getUser(WebFrameworkUtils.getLoginUserId()); + AdminUserRespDTO ownerUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())); // 发起委托的用户 Assert.notNull(ownerUser, "委派任务找不到原审批人,需要检查数据"); taskService.addComment(reqVO.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_END.getType(), BpmCommentTypeEnum.DELEGATE_END.formatComment(currentUser.getNickname(), ownerUser.getNickname(), reqVO.getReason())); @@ -914,7 +921,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { * @param reqVO 前端参数封装 */ public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { - // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveExecutionsToSingleActivityId 回撤 // 1.1 获取所有正常进行的任务节点 Key List taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); List runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); @@ -922,14 +929,16 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F List returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); List returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); - + List runExecutionIds = new ArrayList<>(); // 2. 给当前要被退回的 task 数组,设置退回意见 taskList.forEach(task -> { // 需要排除掉,不需要设置退回意见的任务 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { return; } - + if (task.getExecutionId() != null) { + runExecutionIds.add(task.getExecutionId()); + } // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 // 2.1.1 添加评论 @@ -946,18 +955,25 @@ public class BpmTaskServiceImpl implements BpmTaskService { Set needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); // 4. 执行驳回 + // 4.1 校验是否有可回撤的 execution,避免 moveExecutionsToSingleActivityId 传入空集合时 Flowable 内部报错 + if (CollUtil.isEmpty(runExecutionIds)) { + throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); + } + // 4.2 执行驳回 // ① 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId。原因:当多实例任务回退的时候有问题。 // 相关 issue: https://github.com/flowable/flowable-engine/issues/3944 // ② flowable 7.2.0 版本后,继续使用 moveActivityIdsToSingleActivityId 方法。原因:flowable 7.2.0 版本修复了该问题。 // 相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/1018 + // ③ moveActivityIdsToSingleActivityId 使用遇到问题, 相关 issue https://gitee.com/zhijiantianya/yudao-cloud/issues/IJM8MS + // 改成 moveExecutionsToSingleActivityId 好像并没有遇到 ② 提到的超时提醒失效的问题。暂时先改回 moveExecutionsToSingleActivityId + // 目前还有的相关问题 https://t.zsxq.com/z4d9i。 估计需要升级 flowable 8 版本试试 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) - .moveActivityIdsToSingleActivityId(returnTaskKeyList, reqVO.getTargetTaskDefinitionKey()) + .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) // 设置需要预测的任务 ids 的流程变量,用于辅助预测 .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) - // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 - .localVariable(reqVO.getTargetTaskDefinitionKey(), - String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) + // 设置流程变量节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 + .processVariable(String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) .changeState(); } @@ -999,13 +1015,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { throw exception(TASK_DELEGATE_FAIL_USER_REPEAT); } // 1.2 校验目标用户存在 - AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId()).getCheckedData(); + AdminUserRespDTO delegateUser = adminUserApi.getUser(reqVO.getDelegateUserId()); if (delegateUser == null) { throw exception(TASK_DELEGATE_FAIL_USER_NOT_EXISTS); } // 2. 添加委托意见 - AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO currentUser = adminUserApi.getUser(userId); taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_START.getType(), BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason())); @@ -1030,13 +1046,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { throw exception(TASK_TRANSFER_FAIL_USER_REPEAT); } // 1.2 校验目标用户存在 - AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId()).getCheckedData(); + AdminUserRespDTO assigneeUser = adminUserApi.getUser(reqVO.getAssigneeUserId()); if (assigneeUser == null) { throw exception(TASK_TRANSFER_FAIL_USER_NOT_EXISTS); } // 2. 添加委托意见 - AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO currentUser = adminUserApi.getUser(userId); taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.TRANSFER.getType(), BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason())); @@ -1095,7 +1111,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { // 1. 获取和校验任务 TaskEntityImpl taskEntity = validateTaskCanCreateSign(userId, reqVO); - List userList = adminUserApi.getUserList(reqVO.getUserIds()).getCheckedData(); + List userList = adminUserApi.getUserList(reqVO.getUserIds()); if (CollUtil.isEmpty(userList)) { throw exception(TASK_SIGN_CREATE_USER_NOT_EXIST); } @@ -1122,7 +1138,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { createSignTaskList(convertList(reqVO.getUserIds(), String::valueOf), taskEntity); // 4. 记录加签的评论到 task 任务 - AdminUserRespDTO currentUser = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO currentUser = adminUserApi.getUser(userId); String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getComment(), currentUser.getNickname(), BpmTaskSignTypeEnum.nameOfType(reqVO.getType()), String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason()); @@ -1154,7 +1170,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { List currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { - List userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData(); + List userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())); throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); } return taskEntity; @@ -1216,10 +1232,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 1.2 校验取消人存在 AdminUserRespDTO cancelUser = null; if (StrUtil.isNotBlank(task.getAssignee())) { - cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())).getCheckedData(); + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())); } if (cancelUser == null && StrUtil.isNotBlank(task.getOwner())) { - cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())).getCheckedData(); + cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())); } Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误"); @@ -1233,7 +1249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { taskService.deleteTasks(convertList(childTaskList, Task::getId)); // 3. 记录日志到父任务中。先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录 - AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(userId); taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), BpmCommentTypeEnum.SUB_SIGN.getType(), StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getComment(), user.getNickname(), cancelUser.getNickname())); @@ -1464,102 +1480,106 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } - // 自动去重,通过自动审批的方式 - BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(task.getProcessDefinitionId()); - if (processDefinitionInfo == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程定义({})]", task.getId(), task.getProcessDefinitionId()); - return; - } - if (processDefinitionInfo.getAutoApprovalType() != null) { - HistoricTaskInstanceQuery sameAssigneeQuery = historyService.createHistoricTaskInstanceQuery() - .processInstanceId(task.getProcessInstanceId()) - .taskAssignee(task.getAssignee()) // 相同审批人 - .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) - .finished(); - if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) - && sameAssigneeQuery.count() > 0) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); + // 需要基于 instance 设置租户编号,避免 Flowable 内部异步执行时【例如:超时自动通过】 丢失租户编号 + FlowableUtils.execute(processInstance.getTenantId(), () -> { + // 自动去重,通过自动审批的方式 + BpmProcessDefinitionInfoDO processDefinitionInfo = bpmProcessDefinitionService.getProcessDefinitionInfo(task.getProcessDefinitionId()); + if (processDefinitionInfo == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程定义({})]", task.getId(), task.getProcessDefinitionId()); return; } - if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型({})]", task.getId(), task.getProcessDefinitionId()); - return; - } - List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 - BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), - SequenceFlow::getSourceRef); - if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { + if (processDefinitionInfo.getAutoApprovalType() != null) { + HistoricTaskInstanceQuery approvedTaskQuery = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) + .finished(); + if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) + && approvedTaskQuery.taskAssignee(task.getAssignee()).count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getName())); + .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); return; } - } - } - - // 获取发起人节点 - BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); - if (bpmnModel == null) { - log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); - return; - } - FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略(使用 local variable) - Boolean returnTaskFlag = runtimeService.getVariableLocal(task.getExecutionId(), - String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); - Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), - BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); - if (userTaskElement.getId().equals(START_USER_NODE_ID) - && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 - || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 - && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); - return; - } - // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 - if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID) - && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { - if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { - Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); - - // 情况一:自动跳过 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { - getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); - return; - } - // 情况二:转交给部门负责人审批 - if (ObjectUtils.equalsAny(assignStartUserHandlerType, - BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); - Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); - DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()).getCheckedData() : null; - Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); - // 找不到部门负责人的情况下,自动审批通过 - // noinspection DataFlowIssue - if (dept.getLeaderUserId() == null) { + // 连续审批的节点自动通过 + if (BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getType().equals(processDefinitionInfo.getAutoApprovalType())) { + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型({})]", task.getId(), task.getProcessDefinitionId()); + return; + } + List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingUserTaskFlows( // 获取所有的上一个 UserTask 节点连线 + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), + SequenceFlow::getSourceRef); + approvedTaskQuery.taskDefinitionKeys(sourceTaskIds).orderByTaskCreateTime().desc(); // 设置 taskIds, 并按创建时间倒序排序 + HistoricTaskInstance firstHisTask = CollUtil.getFirst(approvedTaskQuery.list()); + if (firstHisTask != null && StrUtil.equals(firstHisTask.getAssignee(), task.getAssignee())) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + .setReason(BpmAutoApproveTypeEnum.APPROVE_SEQUENT.getName())); return; } - // 找得到部门负责人的情况下,修改负责人 - if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { - getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() - .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) - .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); - return; - } - // 如果部门负责人是自己,还是自己审批吧~ } } - } - // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 - FlowableUtils.execute(processInstance.getTenantId(), () -> { - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); + + // 获取发起人节点 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), + String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(), + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); + if (userTaskElement.getId().equals(START_USER_NODE_ID) + && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 + && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); + return; + } + // 当不为发起人节点时,审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 + if (ObjectUtil.notEqual(userTaskElement.getId(), START_USER_NODE_ID) + && StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); + + // 情况一:自动跳过 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); + return; + } + // 情况二:转交给部门负责人审批 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()) : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); + // 找不到部门负责人的情况下,自动审批通过 + // noinspection DataFlowIssue + if (dept.getLeaderUserId() == null) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + return; + } + // 找得到部门负责人的情况下,修改负责人 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); + return; + } + // 如果部门负责人是自己,还是自己审批吧~ + } + } + } + + // 发送消息 + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())); messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java index 251af1d9f..f7eac7ca0 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpressionTest.java @@ -14,7 +14,6 @@ import org.mockito.Mock; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; @@ -41,9 +40,9 @@ public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest { DelegateExecution execution = mockDelegateExecution(1L); // mock 方法(startUser) AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L)); - when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); // mock 方法(getStartUserDept)没有部门 - when(deptApi.getDept(eq(10L))).thenReturn(success(null)); + when(deptApi.getDept(eq(10L))).thenReturn(null); // 调用 Set result = expression.calculateUsers(execution, 1); @@ -57,12 +56,12 @@ public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest { DelegateExecution execution = mockDelegateExecution(1L); // mock 方法(startUser) AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L)); - when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) .setLeaderUserId(20L)); // mock 方法(getDept) - when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept)); - when(deptApi.getDept(eq(100L))).thenReturn(success(null)); + when(deptApi.getDept(eq(10L))).thenReturn(startUserDept); + when(deptApi.getDept(eq(100L))).thenReturn(null); // 调用 Set result = expression.calculateUsers(execution, 2); @@ -76,14 +75,14 @@ public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest { DelegateExecution execution = mockDelegateExecution(1L); // mock 方法(startUser) AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L)); - when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser)); + when(adminUserApi.getUser(eq(1L))).thenReturn(startUser); DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L) .setLeaderUserId(20L)); - when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept)); + when(deptApi.getDept(eq(10L))).thenReturn(startUserDept); // mock 方法(父 dept) DeptRespDTO parentDept = randomPojo(DeptRespDTO.class, o -> o.setId(100L).setParentId(1000L) .setLeaderUserId(200L)); - when(deptApi.getDept(eq(100L))).thenReturn(success(parentDept)); + when(deptApi.getDept(eq(100L))).thenReturn(parentDept); // 调用 Set result = expression.calculateUsers(execution, 2); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java index 4bac14e95..3d671cf32 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategyTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; @@ -12,7 +11,6 @@ import org.mockito.stubbing.Answer; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -31,9 +29,9 @@ public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnit // 准备参数 String param = "10,20|2"; // mock 方法 - when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + when(deptApi.getDept(any())).thenAnswer((Answer) invocationOnMock -> { Long deptId = invocationOnMock.getArgument(0); - return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); }); // 调用 diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java index 8d515e6fb..fc27bcd8e 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategyTest.java @@ -11,7 +11,6 @@ import org.mockito.Mock; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,9 +30,9 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest // 准备参数 String param = "10,20"; // mock 方法 - when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList( + when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList( randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)), - randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L))))); + randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L)))); // 调用 Set userIds = strategy.calculateUsers(param); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java index 809d714e2..57ab22850 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategyTest.java @@ -12,7 +12,6 @@ import org.mockito.Mock; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,9 +33,9 @@ public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest // 准备参数 String param = "10,20"; // mock 方法 - when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList( + when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(asList( randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)), - randomPojo(AdminUserRespDTO.class, o -> o.setId(21L))))); + randomPojo(AdminUserRespDTO.class, o -> o.setId(21L)))); // 调用 Set userIds = strategy.calculateUsers(param); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java index 76a1cf376..2f66a83b5 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -17,7 +16,6 @@ import org.mockito.stubbing.Answer; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -74,10 +72,10 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMo private void mockGetStartUserDept(Long startUserId) { when(adminUserApi.getUser(eq(startUserId))).thenReturn( - success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)))); - when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))); + when(deptApi.getDept(any())).thenAnswer((Answer) invocationOnMock -> { Long deptId = invocationOnMock.getArgument(0); - return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java index 559b017e1..a5f863f24 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategyTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -17,7 +16,6 @@ import org.mockito.stubbing.Answer; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -74,10 +72,10 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockito private void mockGetStartUserDeptLeader(Long startUserId) { when(adminUserApi.getUser(eq(startUserId))).thenReturn( - success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L)))); - when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult>) invocationOnMock -> { + randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))); + when(deptApi.getDept(any())).thenAnswer((Answer) invocationOnMock -> { Long deptId = invocationOnMock.getArgument(0); - return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1))); + return randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)); }); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java index 8d2191eeb..a4442240e 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategyTest.java @@ -12,7 +12,6 @@ import org.mockito.Mock; import java.util.List; import java.util.Set; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -37,7 +36,7 @@ public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest { // mock 方法 List users = convertList(asSet(11L, 22L), id -> new AdminUserRespDTO().setId(id)); - when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(success(users)); + when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users); // 调用 Set userIds = strategy.calculateUsersByTask(null, param); diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java index 1536582de..0663d1f28 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java @@ -110,7 +110,7 @@ public class CrmBusinessStatusController { public CommonResult> getBusinessStatusTypeSimpleList() { List list = businessStatusTypeService.getBusinessStatusTypeList(); // 过滤掉部门不匹配的 - Long deptId = adminUserApi.getUser(getLoginUserId()).getCheckedData().getDeptId(); + Long deptId = adminUserApi.getUser(getLoginUserId()).getDeptId(); list.removeIf(statusType -> CollUtil.isNotEmpty(statusType.getDeptIds()) && !statusType.getDeptIds().contains(deptId)); return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.class)); } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java index a62aabb7a..230ec1f17 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import org.apache.ibatis.annotations.Mapper; @@ -28,9 +29,9 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX query = new LambdaQueryWrapperX() .eq(CrmCustomerLimitConfigDO::getType, type); query.and(w -> { - w.apply("FIND_IN_SET({0}, user_ids) > 0", userId); + w.apply(MyBatisUtils.findInSet("user_ids"), userId); if (deptId != null) { - w.or().apply("FIND_IN_SET({0}, dept_ids) > 0", deptId); + w.or().apply(MyBatisUtils.findInSet("dept_ids"), deptId); } }); return selectList(query); diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java index 224638957..05f96c586 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java @@ -34,7 +34,7 @@ public class SysAdminUserParseFunction implements IParseFunction { } // 获取用户信息 - AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString())).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString())); if (user == null) { log.warn("[apply][获取用户{{}}为空", value); return ""; diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java index 5b002c9ce..6d01f9f97 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java @@ -34,7 +34,7 @@ public class SysDeptParseFunction implements IParseFunction { } // 获取部门信息 - DeptRespDTO dept = deptApi.getDept(Long.parseLong(value.toString())).getCheckedData(); + DeptRespDTO dept = deptApi.getDept(Long.parseLong(value.toString())); if (dept == null) { log.warn("[apply][获取部门{{}}为空", value); return ""; diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java index e642636ba..8e4970117 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java @@ -95,7 +95,7 @@ public class CrmPermissionAspect { } // 3. 考虑下级的权限 - List subordinateUserIds = adminUserApi.getUserListBySubordinate(userId).getCheckedData(); + List subordinateUserIds = adminUserApi.getUserListBySubordinate(userId); for (Long subordinateUserId : convertSet(subordinateUserIds, AdminUserRespDTO::getId)) { CrmPermissionDO subordinatePermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), subordinateUserId)); diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index b5c301a28..e11c34626 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -112,7 +112,7 @@ public class CrmClueServiceImpl implements CrmClueService { private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { // 校验负责人 if (Objects.nonNull(reqVO.getOwnerUserId()) && - Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()).getCheckedData())) { + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { throw exception(USER_NOT_EXISTS); } } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java index 610447f86..6d6a49f46 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java @@ -110,13 +110,13 @@ public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfig * @param deptIds 部门 ids */ private void validateUserAndDept(Collection userIds, Collection deptIds) { - deptApi.validateDeptList(deptIds).checkError(); - adminUserApi.validateUserList(userIds).checkError(); + deptApi.validateDeptList(deptIds); + adminUserApi.validateUserList(userIds); } @Override public List getCustomerLimitConfigListByUserId(Integer type, Long userId) { - AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(userId); Assert.notNull(user, "用户({})不存在", userId); return customerLimitConfigMapper.selectListByTypeAndUserIdAndDeptId(type, userId, user.getDeptId()); } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java index a010e4390..11ac107d9 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java @@ -391,7 +391,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { throw exception(CUSTOMER_NOT_EXISTS); } // 1.2 校验负责人是否存在 - adminUserApi.validateUserList(singletonList(ownerUserId)).checkError(); + adminUserApi.validateUserList(singletonList(ownerUserId)); // 1.3 校验状态 customers.forEach(customer -> { // 校验是否已有负责人 @@ -424,7 +424,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { // 3. 记录操作日志 AdminUserRespDTO user = null; if (!isReceive) { - user = adminUserApi.getUser(ownerUserId).getCheckedData(); + user = adminUserApi.getUser(ownerUserId); } for (CrmCustomerDO customer : customers) { getSelf().receiveCustomerLog(customer, user == null ? null : user.getNickname()); diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java index b98503c57..6e2c37c89 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -136,7 +136,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { private void createBizTypePermissions(CrmPermissionSaveReqVO reqVO, Integer type, Long bizId, String name, List createPermissions) { - AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId()).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(reqVO.getUserId()); // 1. 需要考虑,被添加人,是不是应该有对应的权限了; CrmPermissionDO permission = hasAnyPermission(type, bizId, reqVO.getUserId()); if (ObjUtil.isNotNull(permission)) { @@ -157,7 +157,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { private Long createPermission0(CrmPermissionCreateReqBO createReqBO) { validatePermissionNotExists(Collections.singletonList(createReqBO)); // 1. 校验用户是否存在 - adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId())).checkError(); + adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId())); // 2. 插入权限 CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class); permissionMapper.insert(permission); @@ -168,7 +168,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { public void createPermissionBatch(List createReqBOs) { validatePermissionNotExists(createReqBOs); // 1. 校验用户是否存在 - adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId)).checkError(); + adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId)); // 2. 创建 List permissions = BeanUtils.toBean(createReqBOs, CrmPermissionDO.class); @@ -219,7 +219,7 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName); } // 1.2 校验新负责人是否存在 - adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())).checkError(); + adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); // 2. 修改新负责人的权限 List permissions = permissionMapper.selectByBizTypeAndBizId( diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java index 45ad66d64..e45c4db22 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java @@ -60,7 +60,7 @@ public class CrmProductServiceImpl implements CrmProductService { success = CRM_PRODUCT_CREATE_SUCCESS) public Long createProduct(CrmProductSaveReqVO createReqVO) { // 1. 校验产品 - adminUserApi.validateUserList(Collections.singleton(createReqVO.getOwnerUserId())).checkError(); + adminUserApi.validateUserList(Collections.singleton(createReqVO.getOwnerUserId())); validateProductNoDuplicate(null, createReqVO.getNo()); validateProductCategoryExists(createReqVO.getCategoryId()); diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java index 744279e5a..1df4d5a60 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java @@ -359,10 +359,10 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe } // 情况二:选中某个部门 // 2.1 获得部门列表 - List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId); + List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId); deptIds.add(reqVO.getDeptId()); // 2.2 获得用户编号 - return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); } } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java index 504593189..f4a04ed3f 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java @@ -145,10 +145,10 @@ public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelServic } // 情况二:选中某个部门 // 2.1 获得部门列表 - List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId); + List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId); deptIds.add(reqVO.getDeptId()); // 2.2 获得用户编号 - return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); } } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java index fbe0c99c1..719813e7b 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java @@ -111,10 +111,10 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform // 情况二:选中某个部门 // 2.1 获得部门列表 final Long deptId = reqVO.getDeptId(); - List deptIds = convertList(deptApi.getChildDeptList(deptId).getCheckedData(), DeptRespDTO::getId); + List deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId); deptIds.add(deptId); // 2.2 获得用户编号 - return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); } } \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java index 3febdea08..913ffb08c 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPortraitServiceImpl.java @@ -122,10 +122,10 @@ public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitSe } // 情况二:选中某个部门 // 2.1 获得部门列表 - List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId); + List deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId); deptIds.add(reqVO.getDeptId()); // 2.2 获得用户编号 - return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); } } diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankServiceImpl.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankServiceImpl.java index b0d90a881..e591c35c0 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankServiceImpl.java @@ -125,10 +125,10 @@ public class CrmStatisticsRankServiceImpl implements CrmStatisticsRankService { */ public List getUserIds(Long deptId) { // 1. 获得部门列表 - List deptIds = convertList(deptApi.getChildDeptList(deptId).getCheckedData(), DeptRespDTO::getId); + List deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId); deptIds.add(deptId); // 2. 获得用户编号 - return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId); } } \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java index b3882486c..eb5cddaa4 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java @@ -67,7 +67,7 @@ public class CrmPermissionUtils { // 场景三:下属负责的数据(下属是负责人) if (CrmSceneTypeEnum.isSubordinate(sceneType)) { AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class); - List subordinateUsers = adminUserApi.getUserListBySubordinate(userId).getCheckedData(); + List subordinateUsers = adminUserApi.getUserListBySubordinate(userId); if (CollUtil.isEmpty(subordinateUsers)) { query.eq(ownerUserIdField, -1); // 不返回任何结果 } else { diff --git a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java index 5584af6b4..4c62469eb 100644 --- a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java +++ b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java @@ -79,7 +79,7 @@ public class ImGroupMemberController { // 3. 转化 VO ImGroupMemberRespVO memberVO = BeanUtils.toBean(member, ImGroupMemberRespVO.class); - AdminUserRespDTO user = adminUserApi.getUser(member.getUserId()).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(member.getUserId()); if (user != null) { memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()); } diff --git a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java index 7f7f3c76e..12b5fb159 100644 --- a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java +++ b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImpl.java @@ -97,7 +97,7 @@ public class ImFriendRequestServiceImpl implements ImFriendRequestService { ImFriendRequestDO request = createOrResetRequest(fromUserId, reqVO); // 3. 推送 FRIEND_REQUEST_RECEIVED 给 toUser 多端;payload 携带申请方昵称 / 头像,前端按 requestId 直推 push 进列表 - AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId).getCheckedData(); + AdminUserRespDTO fromUser = adminUserApi.getUser(fromUserId); FriendRequestNotification payload = (FriendRequestNotification) new FriendRequestNotification() .setRequestId(request.getId()).setApplyContent(request.getApplyContent()).setAddSource(request.getAddSource()) .setOperatorUserId(fromUserId).setFriendUserId(fromUserId); diff --git a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java index 4b6093504..71d3d76af 100644 --- a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java +++ b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImpl.java @@ -98,7 +98,7 @@ public class ImGroupRequestServiceImpl implements ImGroupRequestService { ImGroupRequestDO request = createOrResetApplyRequest(groupId, userId, reqVO); // 4. 1503 私聊定向推群主 + 全部管理员(多端同步);payload 携带申请方昵称 / 头像 - AdminUserRespDTO applyUser = adminUserApi.getUser(userId).getCheckedData(); + AdminUserRespDTO applyUser = adminUserApi.getUser(userId); GroupRequestReceivedNotification payload = buildRequestNotification(group, request, applyUser); for (Long receiverUserId : getGroupMemberListByOwnerAndAdminUserIds(group)) { websocketService.sendPrivateMessageAsync(receiverUserId, ImPrivateMessageDTO.ofGroupNotification( diff --git a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java index 695921fbb..28bf9b894 100644 --- a/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java +++ b/yudao-module-im/yudao-module-im-server/src/main/java/cn/iocoder/yudao/module/im/service/rtc/ImRtcCallServiceImpl.java @@ -235,7 +235,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { rtcParticipantMapper.insertBatch(participants); // 3.1 推送通知:RTC_CALL(INVITE) 给每个被邀请人 - AdminUserRespDTO inviterUser = adminUserApi.getUser(inviterId).getCheckedData(); + AdminUserRespDTO inviterUser = adminUserApi.getUser(inviterId); Map inviteeMap = adminUserApi.getUserMap(invitees); for (Long inviteeId : invitees) { pushCallInviteNotification(call, inviterUser, inviteeId, inviteeMap.get(inviteeId), invitees); @@ -303,7 +303,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { rtcParticipantMapper.insertBatch(participants); // 3. 推送通知:RTC_CALL(INVITE) 给每个新邀请人 - AdminUserRespDTO inviter = adminUserApi.getUser(inviterId).getCheckedData(); + AdminUserRespDTO inviter = adminUserApi.getUser(inviterId); Map inviteeMap = adminUserApi.getUserMap(incomingUserIds); for (Long inviteeId : incomingUserIds) { pushCallInviteNotification(call, inviter, inviteeId, inviteeMap.get(inviteeId), incomingUserIds); @@ -491,7 +491,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { if (!ImRtcParticipantStatusEnum.isJoined(participant.getStatus())) { throw exception(RTC_NOT_PARTICIPANT); } - return signToken(userId, resolveDisplayName(adminUserApi.getUser(userId).getCheckedData(), userId), room); + return signToken(userId, resolveDisplayName(adminUserApi.getUser(userId), userId), room); } @Override @@ -1016,7 +1016,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { * @param operatorUserId 拒接者用户编号 */ private void pushCallRejectNotification(ImRtcCallDO call, Long operatorUserId) { - AdminUserRespDTO operator = operatorUserId != null ? adminUserApi.getUser(operatorUserId).getCheckedData() : null; + 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( @@ -1107,7 +1107,7 @@ public class ImRtcCallServiceImpl implements ImRtcCallService { */ private void pushCallEndNotification(ImRtcCallDO call, Long operatorId, ImRtcCallEndReasonEnum reason, Long durationSeconds) { - AdminUserRespDTO operator = operatorId != null ? adminUserApi.getUser(operatorId).getCheckedData() : null; + AdminUserRespDTO operator = operatorId != null ? adminUserApi.getUser(operatorId) : null; ImRtcCallEndNotification payload = ImRtcCallEndNotification.of(call, reason, durationSeconds, operatorId, operator); Long peerUserId = null; if (!ImConversationTypeEnum.isGroup(call.getConversationType())) { diff --git a/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java b/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java index 4eff98bb1..0bbf441bd 100644 --- a/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java +++ b/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/friend/ImFriendRequestServiceImplTest.java @@ -1,5 +1,6 @@ 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; @@ -23,7 +24,6 @@ import org.springframework.dao.DuplicateKeyException; import java.time.LocalDateTime; import java.util.List; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -142,7 +142,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { when(friendService.getFriendState(1L, 2L)).thenReturn(ImFriendStateEnum.NONE.getState()); when(friendService.getFriend(2L, 1L)).thenReturn(null); when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(null); - when(adminUserApi.getUser(1L)).thenReturn(success(new AdminUserRespDTO().setNickname("张三").setAvatar("a.png"))); + when(adminUserApi.getUser(1L)).thenReturn(new AdminUserRespDTO().setNickname("张三").setAvatar("a.png")); when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); // 调用 @@ -172,7 +172,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { .setHandleResult(ImFriendRequestHandleResultEnum.REFUSED.getResult()) .setHandleContent("旧拒绝").setApplyContent("旧内容"); when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(old); - when(adminUserApi.getUser(1L)).thenReturn(success(null)); + when(adminUserApi.getUser(1L)).thenReturn(null); when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); // 调用 @@ -200,7 +200,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { .setHandleResult(ImFriendRequestHandleResultEnum.REFUSED.getResult()); when(friendRequestMapper.selectByFromUserIdAndToUserId(1L, 2L)).thenReturn(null, old); when(friendRequestMapper.insert(any(ImFriendRequestDO.class))).thenThrow(new DuplicateKeyException("dup")); - when(adminUserApi.getUser(1L)).thenReturn(success(null)); + when(adminUserApi.getUser(1L)).thenReturn(null); when(imProperties.getFriend()).thenReturn(new ImProperties.Friend()); // 调用 @@ -229,7 +229,7 @@ public class ImFriendRequestServiceImplTest extends BaseMockitoUnitTest { friendRequestService.agreeFriendRequest(2L, 100L); // 断言:双向建立好友 + 推 APPROVED 给发起方 - verify(adminUserApi).validateUserList(List.of(1L, 2L)); + verify(adminUserApi).validateUserList(ListUtil.of(1L, 2L)); verify(friendService).becomeFriends(request); verify(websocketService).sendPrivateMessageAsync(eq(1L), any(ImPrivateMessageDTO.class)); } diff --git a/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java b/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java index 4207359ab..c4f41a729 100644 --- a/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java +++ b/yudao-module-im/yudao-module-im-server/src/test/java/cn/iocoder/yudao/module/im/service/group/ImGroupRequestServiceImplTest.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.im.service.group; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapUtil; 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; @@ -27,7 +29,6 @@ import org.springframework.dao.DuplicateKeyException; import java.util.Collection; import java.util.List; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -94,14 +95,14 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); // 群里有 owner + 一个 admin,作为 1503 推送目标 - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build(), ImGroupMemberDO.builder().groupId(10L).userId(98L) .role(ImGroupMemberRoleEnum.ADMIN.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); - when(adminUserApi.getUser(1L)).thenReturn(success(buildUser(1L, "申请人"))); + when(adminUserApi.getUser(1L)).thenReturn(buildUser(1L, "申请人")); ImGroupRequestApplyReqVO reqVO = new ImGroupRequestApplyReqVO(); reqVO.setGroupId(10L); @@ -126,11 +127,11 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { .joinApproval(true) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); - when(adminUserApi.getUser(1L)).thenReturn(success(buildUser(1L, "申请人"))); + when(adminUserApi.getUser(1L)).thenReturn(buildUser(1L, "申请人")); ImGroupRequestDO old = new ImGroupRequestDO().setId(50L).setGroupId(10L).setUserId(1L) .setHandleResult(ImGroupRequestHandleResultEnum.REFUSED.getResult()); when(groupRequestMapper.selectByGroupIdAndUserId(10L, 1L)).thenReturn(null, old); @@ -191,7 +192,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(99L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.getGroup(10L)).thenReturn(group); - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); @@ -269,7 +270,7 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { ImGroupDO group = ImGroupDO.builder().id(10L).ownerUserId(1L) .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.getGroup(10L)).thenReturn(group); - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(1L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); @@ -290,16 +291,17 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { .status(CommonStatusEnum.ENABLE.getStatus()).build(); when(groupService.validateGroupExists(10L)).thenReturn(group); when(groupRequestMapper.selectByGroupIdAndUserId(eq(10L), anyLong())).thenReturn(null); - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); - when(adminUserApi.getUserMap(anyCollection())).thenReturn(java.util.Map.of( - 2L, buildUser(2L, "用户A"), - 3L, buildUser(3L, "用户B"))); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.builder() + .put(2L, buildUser(2L, "用户A")) + .put(3L, buildUser(3L, "用户B")) + .build()); // 调用:邀请人 1L 邀请 2L、3L(都没有旧记录) - groupRequestService.createInviteRequestList(10L, 1L, List.of(2L, 3L)); + groupRequestService.createInviteRequestList(10L, 1L, ListUtil.of(2L, 3L)); // 断言:插入 2 条 + 推 1503 给 owner(每条 1 帧)共 2 帧 ArgumentCaptor captor = ArgumentCaptor.forClass(ImGroupRequestDO.class); @@ -324,14 +326,14 @@ public class ImGroupRequestServiceImplTest extends BaseMockitoUnitTest { .setHandleResult(ImGroupRequestHandleResultEnum.REFUSED.getResult()); when(groupRequestMapper.selectByGroupIdAndUserId(10L, 2L)).thenReturn(null, old); when(groupRequestMapper.insert(any(ImGroupRequestDO.class))).thenThrow(new DuplicateKeyException("dup")); - when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(List.of( + when(groupMemberService.getGroupMemberListByOwnerAndAdmin(10L)).thenReturn(ListUtil.of( ImGroupMemberDO.builder().groupId(10L).userId(99L) .role(ImGroupMemberRoleEnum.OWNER.getRole()) .status(CommonStatusEnum.ENABLE.getStatus()).build())); - when(adminUserApi.getUserMap(anyCollection())).thenReturn(java.util.Map.of(2L, buildUser(2L, "用户A"))); + when(adminUserApi.getUserMap(anyCollection())).thenReturn(MapUtil.of(2L, buildUser(2L, "用户A"))); // 调用 - groupRequestService.createInviteRequestList(10L, 1L, List.of(2L)); + groupRequestService.createInviteRequestList(10L, 1L, ListUtil.of(2L)); // 断言:复用并重置旧邀请申请 verify(groupRequestMapper).updateInviteByIdReset(eq(50L), eq(1L), diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApi.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApi.java index 53803d4d1..e0f8c6dc6 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApi.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApi.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.infra.api.config; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.infra.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -16,6 +15,6 @@ public interface ConfigApi { @GetMapping(PREFIX + "/get-value-by-key") @Operation(summary = "根据参数键查询参数值") - CommonResult getConfigValueByKey(@RequestParam("key") String key); + String getConfigValueByKey(@RequestParam("key") String key); } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApiImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApiImpl.java index 5ab67c2c2..80d36bfbe 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApiImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/config/ConfigApiImpl.java @@ -1,14 +1,11 @@ package cn.iocoder.yudao.module.infra.api.config; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.infra.dal.dataobject.config.ConfigDO; import cn.iocoder.yudao.module.infra.service.config.ConfigService; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class ConfigApiImpl implements ConfigApi { @@ -17,9 +14,9 @@ public class ConfigApiImpl implements ConfigApi { private ConfigService configService; @Override - public CommonResult getConfigValueByKey(String key) { + public String getConfigValueByKey(String key) { ConfigDO config = configService.getConfigByKey(key); - return success(config != null ? config.getValue() : null); + return config != null ? config.getValue() : null; } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index ca7db6407..12bab8ae5 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -1,10 +1,11 @@ package cn.iocoder.yudao.module.infra.controller.admin.file; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*; @@ -27,7 +28,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.nio.charset.StandardCharsets; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -111,10 +111,10 @@ public class FileController { if (StrUtil.isEmpty(path)) { throw new IllegalArgumentException("结尾的 path 路径必须传递"); } - // 解码,解决中文路径的问题 + // 解码,解决中文、%、+ 等特殊字符路径的问题 // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ - path = URLUtil.decode(path, StandardCharsets.UTF_8, false); + path = HttpUtils.decodeUrlPath(path); // 读取内容 byte[] content = fileService.getFileContent(configId, path); @@ -123,7 +123,9 @@ public class FileController { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } - writeAttachment(response, path, content); + FileDO file = fileService.getFileByConfigIdAndPath(configId, path); + String filename = file != null && StrUtil.isNotEmpty(file.getName()) ? file.getName() : FileUtil.getName(path); + writeAttachment(response, filename, content); } @GetMapping("/page") diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java index 11233a457..a5b17c6eb 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java @@ -23,4 +23,11 @@ public interface FileMapper extends BaseMapperX { .orderByDesc(FileDO::getId)); } + default FileDO selectLatestByConfigIdAndPath(Long configId, String path) { + return selectLastOne(new LambdaQueryWrapperX() + .eq(FileDO::getConfigId, configId) + .eq(FileDO::getPath, path) + .orderByAsc(FileDO::getId)); + } + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java index 7ddade3ea..d03caca7a 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.framework.file.core.client; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import lombok.extern.slf4j.Slf4j; /** @@ -72,7 +73,7 @@ public abstract class AbstractFileClient implem * @return URL 访问地址 */ protected String formatFileUrl(String domain, String path) { - return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), HttpUtils.encodeUrlPath(path)); } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index aad9007d7..98d37d805 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java @@ -116,13 +116,18 @@ public class S3FileClient extends AbstractFileClient { @Override public String presignGetUrl(String url, Integer expirationSeconds) { // 1. 将 url 转换为 path - String path = StrUtil.removePrefix(url, config.getDomain() + "/"); - path = HttpUtils.decodeUrlPath(HttpUtils.removeUrlQuery(path)); + // 完整 Domain URL 会解码还原为原始对象名;裸 path 约定已经是未编码的对象名 + boolean domainUrl = StrUtil.startWith(url, config.getDomain() + "/"); + String path = domainUrl ? StrUtil.removePrefix(url, config.getDomain() + "/") : url; + if (domainUrl) { + path = HttpUtils.removeUrlPathQueryAndFragment(path); + path = HttpUtils.decodeUrlPath(path); + } // 2.1 情况一:公开访问:无需签名 // 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名 if (!BooleanUtil.isFalse(config.getEnablePublicAccess())) { - return config.getDomain() + "/" + path; + return config.getDomain() + "/" + HttpUtils.encodeUrlPath(path); } // 2.2 情况二:私有访问:生成 GET 预签名 URL diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java index f8cd1c45c..3b0b53327 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java @@ -85,9 +85,9 @@ public class FileTypeUtils { // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html if (isImage(mineType)) { // 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论 - response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename)); + response.setHeader("Content-Disposition", buildContentDisposition("inline", filename)); } else { - response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename)); + response.setHeader("Content-Disposition", buildContentDisposition("attachment", filename)); } // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题 if (StrUtil.containsIgnoreCase(mineType, "video")) { @@ -98,6 +98,29 @@ public class FileTypeUtils { IoUtil.write(response.getOutputStream(), false, content); } + private static String buildContentDisposition(String dispositionType, String filename) { + return StrUtil.format("{};filename=\"{}\";filename*=UTF-8''{}", + dispositionType, buildFallbackFilename(filename), HttpUtils.encodeUrlPathSegment(filename)); + } + + private static String buildFallbackFilename(String filename) { + if (StrUtil.isEmpty(filename)) { + return "download"; + } + StringBuilder result = new StringBuilder(filename.length()); + for (int i = 0; i < filename.length(); i++) { + char ch = filename.charAt(i); + if (ch == '"' || ch == '\\') { + result.append('\\').append(ch); + } else if (ch >= 0x20 && ch <= 0x7E) { + result.append(ch); + } else { + result.append('_'); + } + } + return result.toString(); + } + /** * 判断是否是图片 * diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index 70939a6e5..d675558a5 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -459,7 +459,7 @@ public class CodegenEngine { private String prettyCode(String content, String vmPath) { // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5、vue3_admin_uniapp) if (!StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) { - content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); + content = content.replaceAll(",\\r?\\n}", "\n}").replaceAll(",\\r?\\n }", "\n }"); } // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 if (StrUtil.count(content, "dateFormatter") == 1) { diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 421bbe8d4..a561b4eba 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -86,4 +86,13 @@ public interface FileService { */ byte[] getFileContent(Long configId, String path) throws Exception; + /** + * 获得文件 + * + * @param configId 配置编号 + * @param path 文件路径 + * @return 文件 + */ + FileDO getFileByConfigIdAndPath(Long configId, String path); + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index a93c07d7e..7fe36cd76 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -238,4 +238,9 @@ public class FileServiceImpl implements FileService { return client.getContent(path); } + @Override + public FileDO getFileByConfigIdAndPath(Long configId, String path) { + return fileMapper.selectLatestByConfigIdAndPath(configId, path); + } + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/api/api.ts.vm new file mode 100644 index 000000000..6c5d7068f --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/api/api.ts.vm @@ -0,0 +1,53 @@ +import type { PageParam, PageResult } from '@/http/types' +import { http } from '@/http/http' + +#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase()) +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte") +#set ($primaryTsType = "number") +#else +#set ($primaryTsType = "string") +#end +/** ${table.classComment}信息 */ +export interface ${simpleClassName} { +#foreach ($column in $columns) + #if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult) + #set ($javaType = $column.javaType.toLowerCase()) + #set ($javaFieldLower = $column.javaField.toLowerCase()) + #set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime") + #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte") + ${column.javaField}#if($optional)?#end: number + #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime") + ${column.javaField}#if($optional)?#end: Date + #elseif(${javaType} == "boolean") + ${column.javaField}#if($optional)?#end: boolean + #else + ${column.javaField}#if($optional)?#end: string + #end + #end +#end +} + +/** 获取${table.classComment}分页列表 */ +export function get${simpleClassName}Page(params: PageParam) { + return http.get>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params) +} + +/** 获取${table.classComment}详情 */ +export function get${simpleClassName}(id: ${primaryTsType}) { + return http.get<${simpleClassName}>(`/${table.moduleName}/${simpleClassName_strikeCase}/get?id=${id}`) +} + +/** 创建${table.classComment} */ +export function create${simpleClassName}(data: ${simpleClassName}) { + return http.post('/${table.moduleName}/${simpleClassName_strikeCase}/create', data) +} + +/** 更新${table.classComment} */ +export function update${simpleClassName}(data: ${simpleClassName}) { + return http.put('/${table.moduleName}/${simpleClassName_strikeCase}/update', data) +} + +/** 删除${table.classComment} */ +export function delete${simpleClassName}(id: ${primaryTsType}) { + return http.delete(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`) +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm index 40087063c..37aa315ea 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm @@ -177,7 +177,7 @@ const emit = defineEmits<{ reset: [] }>() -const visible = ref(false) +const visible = ref(false) // 搜索弹窗显示状态 const formData = reactive({ #foreach($column in $columns) #if ($column.listOperation) @@ -201,7 +201,7 @@ const formData = reactive({ #end #end #end -}) +}) // 搜索表单数据 /** 搜索条件 placeholder 拼接 */ const placeholder = computed(() => { @@ -237,21 +237,20 @@ const placeholder = computed(() => { }) #if ($hasDateTimeBetween == 1) -// 时间范围选择器状态 #foreach($column in $columns) #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN") #set ($javaField = $column.javaField) #set ($AttrName = $javaField.substring(0,1).toUpperCase() + ${javaField.substring(1)}) -const visible${AttrName} = ref<[boolean, boolean]>([false, false]) -const temp${AttrName} = ref<[number, number]>([Date.now(), Date.now()]) +const visible${AttrName} = ref<[boolean, boolean]>([false, false]) // ${column.columnComment}选择器状态 +const temp${AttrName} = ref<[number, number]>([Date.now(), Date.now()]) // ${column.columnComment}临时值 -/** ${column.columnComment}[0]确认 */ +/** 确认${column.columnComment}开始日期 */ function handle${AttrName}0Confirm() { formData.${javaField} = [temp${AttrName}.value[0], formData.${javaField}?.[1]] visible${AttrName}.value[0] = false } -/** ${column.columnComment}[1]确认 */ +/** 确认${column.columnComment}结束日期 */ function handle${AttrName}1Confirm() { formData.${javaField} = [formData.${javaField}?.[0], temp${AttrName}.value[1]] visible${AttrName}.value[1] = false @@ -260,7 +259,7 @@ function handle${AttrName}1Confirm() { #end #end -/** 搜索 */ +/** 搜索按钮操作 */ function handleSearch() { visible.value = false emit('search', { @@ -277,7 +276,7 @@ function handleSearch() { }) } -/** 重置 */ +/** 重置按钮操作 */ function handleReset() { #foreach($column in $columns) #if ($column.listOperation) diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm index b3302e521..5a02a8b17 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm @@ -62,6 +62,7 @@ #end #end import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' +import { onUnload } from '@dcloudio/uni-app' import { onMounted, ref } from 'vue' import { useToast } from 'wot-design-uni' import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}' @@ -87,8 +88,8 @@ definePage({ const { hasAccessByCodes } = useAccess() const toast = useToast() -const formData = ref<${simpleClassName}>() -const deleting = ref(false) +const formData = ref<${simpleClassName}>() // 详情数据 +const deleting = ref(false) // 删除状态 /** 返回上一页 */ function handleBack() { @@ -97,7 +98,7 @@ function handleBack() { /** 加载${table.classComment}详情 */ async function getDetail() { - if (!props.id) { + if (!props.id || deleting.value) { return } try { @@ -131,6 +132,7 @@ function handleDelete() { try { await delete${simpleClassName}(props.id) toast.success('删除成功') + uni.$emit('${table.moduleName}:${table.businessName}:reload') setTimeout(() => { handleBack() }, 500) @@ -143,8 +145,14 @@ function handleDelete() { /** 初始化 */ onMounted(() => { + uni.$on('${table.moduleName}:${table.businessName}:reload', getDetail) getDetail() }) + +/** 卸载 */ +onUnload(() => { + uni.$off('${table.moduleName}:${table.businessName}:reload', getDetail) +}) diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index new file mode 100644 index 000000000..b64d00399 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index @@ -0,0 +1,194 @@ + + + + + diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index new file mode 100644 index 000000000..acb1b8709 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/index @@ -0,0 +1,169 @@ + + + + + diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form new file mode 100644 index 000000000..654d411c6 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form @@ -0,0 +1,190 @@ + + + diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper new file mode 100644 index 000000000..155aa5c27 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/codegen/vue3_admin_uniapp_one/xml/InfraStudentMapper @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java index a13b662f6..4f14057eb 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusAutoConfiguration.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessage; import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.kafka.IotKafkaMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.local.IotLocalMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.rabbitmq.IotRabbitMQMessageBus; import cn.iocoder.yudao.module.iot.core.messagebus.core.redis.IotRedisMessageBus; @@ -18,6 +19,7 @@ import org.redisson.api.RedissonClient; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -27,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.kafka.core.KafkaTemplate; import java.util.List; @@ -77,6 +80,21 @@ public class IotMessageBusAutoConfiguration { } + // ==================== Kafka 实现 ==================== + + @Configuration + @ConditionalOnProperty(prefix = "yudao.iot.message-bus", name = "type", havingValue = "kafka") + @ConditionalOnClass(KafkaTemplate.class) + public static class IotKafkaMessageBusConfiguration { + + @Bean + public IotKafkaMessageBus iotKafkaMessageBus(KafkaProperties kafkaProperties) { + log.info("[iotKafkaMessageBus][创建 IoT Kafka 消息总线]"); + return new IotKafkaMessageBus(kafkaProperties); + } + + } + // ==================== Redis 实现 ==================== /** @@ -103,7 +121,8 @@ public class IotMessageBusAutoConfiguration { RedisMQTemplate redisTemplate, RedissonClient redissonClient) { List> listeners = getListeners(messageBus); - return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient); + return new RedisPendingMessageResendJob(listeners, redisTemplate, redissonClient, + RedisPendingMessageResendJob.IOT_RESEND_LOCK_KEY); } /** @@ -114,7 +133,8 @@ public class IotMessageBusAutoConfiguration { RedisMQTemplate redisTemplate, RedissonClient redissonClient) { List> listeners = getListeners(messageBus); - return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient); + return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient, + RedisStreamMessageCleanupJob.IOT_CLEANUP_LOCK_KEY); } private List> getListeners(IotRedisMessageBus messageBus) { diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java index 2273bc5c2..607f2b869 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/config/IotMessageBusProperties.java @@ -1,10 +1,12 @@ package cn.iocoder.yudao.module.iot.core.messagebus.config; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + /** * IoT 消息总线配置属性 * @@ -18,9 +20,9 @@ public class IotMessageBusProperties { /** * 消息总线类型 * - * 可选值:local、redis、rocketmq、rabbitmq + * 可选值:local、redis、rocketmq、kafka、rabbitmq */ @NotNull(message = "IoT 消息总线类型不能为空") private String type = "local"; -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java new file mode 100644 index 000000000..d859f6734 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/kafka/IotKafkaMessageBus.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.iot.core.messagebus.core.kafka; + +import cn.hutool.core.util.TypeUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; +import jakarta.annotation.PreDestroy; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.AcknowledgingMessageListener; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.ContainerProperties; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +/** + * 基于 Kafka 的 {@link IotMessageBus} 实现类 + * + * @author 芋道源码 + */ +@Slf4j +public class IotKafkaMessageBus implements IotMessageBus { + + private final KafkaTemplate kafkaTemplate; + + private final KafkaProperties kafkaProperties; + + @Getter + private final List> subscribers = new ArrayList<>(); + + private final List> containers = new ArrayList<>(); + + public IotKafkaMessageBus(KafkaProperties kafkaProperties) { + this.kafkaProperties = kafkaProperties; + this.kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(buildProducerProperties(kafkaProperties))); + } + + @Override + public void post(String topic, Object message) { + String messageJson = JsonUtils.toJsonString(message); + try { + kafkaTemplate.send(topic, messageJson).get(); + log.info("[post][topic({}) 发送消息({})]", topic, message); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e); + } catch (ExecutionException e) { + throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e); + } + } + + @Override + public void register(IotMessageSubscriber subscriber) { + Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0); + if (type == null) { + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); + } + + // 1. 创建消费容器 + ContainerProperties containerProperties = new ContainerProperties(subscriber.getTopic()); + containerProperties.setGroupId(subscriber.getGroup()); + containerProperties.setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); + containerProperties.setMissingTopicsFatal(false); + containerProperties.setMessageListener((AcknowledgingMessageListener) (message, acknowledgment) -> { + try { + subscriber.onMessage(JsonUtils.parseObject(message.value(), type)); + acknowledgment.acknowledge(); + } catch (Exception ex) { + log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]", + subscriber.getTopic(), subscriber.getGroup(), message, subscriber.getClass().getName(), ex); + throw ex; + } + }); + ConcurrentMessageListenerContainer container = new ConcurrentMessageListenerContainer<>( + new DefaultKafkaConsumerFactory<>(buildConsumerProperties(kafkaProperties, subscriber.getGroup())), + containerProperties); + container.start(); + + // 2. 保存消费者引用 + containers.add(container); + subscribers.add(subscriber); + } + + @PreDestroy + public void destroy() { + for (ConcurrentMessageListenerContainer container : containers) { + try { + container.stop(); + log.info("[destroy][关闭 Kafka 消费者容器成功]"); + } catch (Exception e) { + log.error("[destroy][关闭 Kafka 消费者容器异常]", e); + } + } + kafkaTemplate.destroy(); + } + + private static Map buildProducerProperties(KafkaProperties kafkaProperties) { + Map properties = new HashMap<>(kafkaProperties.buildProducerProperties()); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return properties; + } + + private static Map buildConsumerProperties(KafkaProperties kafkaProperties, String group) { + Map properties = new HashMap<>(kafkaProperties.buildConsumerProperties()); + properties.put(ConsumerConfig.GROUP_ID_CONFIG, group); + properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + properties.putIfAbsent(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + return properties; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-gateway/pom.xml b/yudao-module-iot/yudao-module-iot-gateway/pom.xml index ea2199580..46668895c 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/pom.xml +++ b/yudao-module-iot/yudao-module-iot-gateway/pom.xml @@ -29,10 +29,14 @@ + + org.springframework.kafka + spring-kafka + true + org.apache.rocketmq rocketmq-spring-boot-starter - true diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java index e62f85fcf..a41634bf9 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; /** @@ -19,6 +20,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. *

* 封装通用的定时器管理、per-device 请求队列限速逻辑。 * 子类只需实现 {@link #pollPoint(Long, Long)} 定义具体的轮询动作。 + * 如需将多个点位合并为一个轮询任务,可覆盖 {@link #buildPollTasks(IotModbusDeviceConfigRespDTO)} + * 和 {@link #pollTask(Long, String)}。 *

* * @author 芋道源码 @@ -38,9 +41,9 @@ public abstract class AbstractIotModbusPollScheduler { private static final int MAX_QUEUE_SIZE = 1000; /** - * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo) + * 设备轮询任务的定时器映射:deviceId -> (taskKey -> PollTimerInfo) */ - private final Map> devicePointTimers = new ConcurrentHashMap<>(); + private final Map> devicePollTimers = new ConcurrentHashMap<>(); /** * per-device 请求队列:deviceId -> 待执行请求队列 @@ -60,11 +63,29 @@ public abstract class AbstractIotModbusPollScheduler { } /** - * 点位定时器信息 + * 轮询任务信息 */ @Data @AllArgsConstructor - private static class PointTimerInfo { + protected static class PollTask { + + /** + * 任务标识 + */ + private String key; + /** + * 轮询间隔(用于判断是否需要更新定时器) + */ + private Integer pollInterval; + + } + + /** + * 轮询定时器信息 + */ + @Data + @AllArgsConstructor + private static class PollTimerInfo { /** * Vert.x 定时器 ID @@ -82,75 +103,84 @@ public abstract class AbstractIotModbusPollScheduler { /** * 更新轮询任务(增量更新) * - * 1. 【删除】点位:停止对应的轮询定时器 - * 2. 【新增】点位:创建对应的轮询定时器 - * 3. 【修改】点位:pollInterval 变化,重建对应的轮询定时器 - * 【修改】其他属性变化:不需要重建定时器(pollPoint 运行时从 configCache 取最新 point) + * 1. 【删除】任务:停止对应的轮询定时器 + * 2. 【新增】任务:创建对应的轮询定时器 + * 3. 【修改】任务:pollInterval 变化,重建对应的轮询定时器 + * 【修改】其他属性变化:不需要重建定时器(pollTask 运行时从 configCache 取最新配置) */ public void updatePolling(IotModbusDeviceConfigRespDTO config) { Long deviceId = config.getDeviceId(); - List newPoints = config.getPoints(); - Map currentTimers = devicePointTimers + List newTasks = buildPollTasks(config); + Map currentTimers = devicePollTimers .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>()); - // 1.1 计算新配置中的点位 ID 集合 - Set newPointIds = convertSet(newPoints, IotModbusPointRespDTO::getId); - // 1.2 计算删除的点位 ID 集合 - Set removedPointIds = new HashSet<>(currentTimers.keySet()); - removedPointIds.removeAll(newPointIds); + // 1.1 计算新配置中的任务 Key 集合 + Set newTaskKeys = convertSet(newTasks, PollTask::getKey); + // 1.2 计算删除的任务 Key 集合 + Set removedTaskKeys = new HashSet<>(currentTimers.keySet()); + removedTaskKeys.removeAll(newTaskKeys); - // 2. 处理删除的点位:停止不再存在的定时器 - for (Long pointId : removedPointIds) { - PointTimerInfo timerInfo = currentTimers.remove(pointId); + // 2. 处理删除的任务:停止不再存在的定时器 + for (String taskKey : removedTaskKeys) { + PollTimerInfo timerInfo = currentTimers.remove(taskKey); if (timerInfo != null) { vertx.cancelTimer(timerInfo.getTimerId()); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已删除]", deviceId, pointId); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已删除]", deviceId, taskKey); } } - // 3. 处理新增和修改的点位 - if (CollUtil.isEmpty(newPoints)) { + // 3. 处理新增和修改的任务 + if (CollUtil.isEmpty(newTasks)) { return; } - for (IotModbusPointRespDTO point : newPoints) { - Long pointId = point.getId(); - Integer newPollInterval = point.getPollInterval(); - PointTimerInfo existingTimer = currentTimers.get(pointId); - // 3.1 新增点位:创建定时器 + for (PollTask task : newTasks) { + String taskKey = task.getKey(); + Integer newPollInterval = task.getPollInterval(); + PollTimerInfo existingTimer = currentTimers.get(taskKey); + // 3.1 新增任务:创建定时器 if (existingTimer == null) { - Long timerId = createPollTimer(deviceId, pointId, newPollInterval); + Long timerId = createPollTimer(deviceId, taskKey, newPollInterval); if (timerId != null) { - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval)); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已创建, interval={}ms]", - deviceId, pointId, newPollInterval); + currentTimers.put(taskKey, new PollTimerInfo(timerId, newPollInterval)); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已创建, interval={}ms]", + deviceId, taskKey, newPollInterval); } } else if (!Objects.equals(existingTimer.getPollInterval(), newPollInterval)) { // 3.2 pollInterval 变化:重建定时器 vertx.cancelTimer(existingTimer.getTimerId()); - Long timerId = createPollTimer(deviceId, pointId, newPollInterval); + Long timerId = createPollTimer(deviceId, taskKey, newPollInterval); if (timerId != null) { - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval)); - log.debug("[updatePolling][设备 {} 点位 {} 定时器已更新, interval={}ms -> {}ms]", - deviceId, pointId, existingTimer.getPollInterval(), newPollInterval); + currentTimers.put(taskKey, new PollTimerInfo(timerId, newPollInterval)); + log.debug("[updatePolling][设备 {} 轮询任务 {} 定时器已更新, interval={}ms -> {}ms]", + deviceId, taskKey, existingTimer.getPollInterval(), newPollInterval); } else { - currentTimers.remove(pointId); + currentTimers.remove(taskKey); } } - // 3.3 其他属性变化:无需重建定时器,因为 pollPoint() 运行时从 configCache 获取最新 point,自动使用新配置 + // 3.3 其他属性变化:无需重建定时器,因为 pollTask() 运行时从 configCache 获取最新配置 } } + /** + * 构建轮询任务列表 + * + * 默认每个点位一个轮询任务。TCP Client 等协议可覆盖该方法,将多个点位合并为一个批量读取任务。 + */ + protected List buildPollTasks(IotModbusDeviceConfigRespDTO config) { + return convertList(config.getPoints(), point -> new PollTask(String.valueOf(point.getId()), point.getPollInterval())); + } + /** * 创建轮询定时器 */ - private Long createPollTimer(Long deviceId, Long pointId, Integer pollInterval) { + private Long createPollTimer(Long deviceId, String taskKey, Integer pollInterval) { if (pollInterval == null || pollInterval <= 0) { return null; } return vertx.setPeriodic(pollInterval, timerId -> { try { - submitPollRequest(deviceId, pointId); + submitPollRequest(deviceId, taskKey); } catch (Exception e) { - log.error("[createPollTimer][轮询点位失败, deviceId={}, pointId={}]", deviceId, pointId, e); + log.error("[createPollTimer][轮询任务失败, deviceId={}, taskKey={}]", deviceId, taskKey, e); } }); } @@ -160,7 +190,7 @@ public abstract class AbstractIotModbusPollScheduler { /** * 提交轮询请求到设备请求队列(保证同设备请求间隔) */ - private void submitPollRequest(Long deviceId, Long pointId) { + private void submitPollRequest(Long deviceId, String taskKey) { // 1. 【重要】将请求添加到设备的请求队列 Queue queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>()); while (queue.size() >= MAX_QUEUE_SIZE) { @@ -168,7 +198,7 @@ public abstract class AbstractIotModbusPollScheduler { queue.poll(); log.warn("[submitPollRequest][设备 {} 请求队列已满({}), 丢弃最旧请求]", deviceId, MAX_QUEUE_SIZE); } - queue.offer(() -> pollPoint(deviceId, pointId)); + queue.offer(() -> pollTask(deviceId, taskKey)); // 2. 处理设备请求队列(如果没有延迟 timer 在等待) processDeviceQueue(deviceId); @@ -238,6 +268,15 @@ public abstract class AbstractIotModbusPollScheduler { // ========== 轮询执行 ========== + /** + * 轮询任务 + * + * 默认将任务标识作为点位 ID,执行单点轮询。 + */ + protected void pollTask(Long deviceId, String taskKey) { + pollPoint(deviceId, Long.valueOf(taskKey)); + } + /** * 轮询单个点位(子类实现具体的读取逻辑) * @@ -252,25 +291,25 @@ public abstract class AbstractIotModbusPollScheduler { * 停止设备的轮询 */ public void stopPolling(Long deviceId) { - Map timers = devicePointTimers.remove(deviceId); - if (CollUtil.isEmpty(timers)) { - return; - } - for (PointTimerInfo timerInfo : timers.values()) { - vertx.cancelTimer(timerInfo.getTimerId()); + Map timers = devicePollTimers.remove(deviceId); + if (CollUtil.isNotEmpty(timers)) { + for (PollTimerInfo timerInfo : timers.values()) { + vertx.cancelTimer(timerInfo.getTimerId()); + } } // 清理请求队列 deviceRequestQueues.remove(deviceId); deviceLastRequestTime.remove(deviceId); deviceDelayTimerActive.remove(deviceId); - log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timers.size()); + log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, + CollUtil.isEmpty(timers) ? 0 : timers.size()); } /** * 停止所有轮询 */ public void stopAll() { - for (Long deviceId : new ArrayList<>(devicePointTimers.keySet())) { + for (Long deviceId : new ArrayList<>(devicePollTimers.keySet())) { stopPolling(deviceId); } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java index 1324f3aa5..1d52f8d49 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpClientUtils.java @@ -39,11 +39,38 @@ public class IotModbusTcpClientUtils { public static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, Integer slaveId, IotModbusPointRespDTO point) { + return read(connection, slaveId, point.getFunctionCode(), + point.getRegisterAddress(), point.getRegisterCount(), point.getIdentifier()); + } + + /** + * 读取 Modbus 数据 + * + * @param connection Modbus 连接 + * @param slaveId 从站地址 + * @param functionCode 功能码 + * @param registerAddress 寄存器起始地址 + * @param registerCount 寄存器数量 + * @return 原始值(int 数组) + */ + public static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, + Integer slaveId, + Integer functionCode, + Integer registerAddress, + Integer registerCount) { + return read(connection, slaveId, functionCode, registerAddress, registerCount, null); + } + + private static Future read(IotModbusTcpClientConnectionManager.ModbusConnection connection, + Integer slaveId, + Integer functionCode, + Integer registerAddress, + Integer registerCount, + String identifier) { return connection.executeBlocking(tcpConnection -> { try { // 1. 创建请求 - ModbusRequest request = createReadRequest(point.getFunctionCode(), - point.getRegisterAddress(), point.getRegisterCount()); + ModbusRequest request = createReadRequest(functionCode, registerAddress, registerCount); request.setUnitID(slaveId); // 2. 执行事务(请求) @@ -53,10 +80,10 @@ public class IotModbusTcpClientUtils { // 3. 解析响应 ModbusResponse response = transaction.getResponse(); - return extractValues(response, point.getFunctionCode()); + return extractValues(response, functionCode); } catch (Exception e) { - throw new RuntimeException(String.format("Modbus 读取失败 [slaveId=%d, identifier=%s, address=%d]", - slaveId, point.getIdentifier(), point.getRegisterAddress()), e); + throw new RuntimeException(String.format("Modbus 读取失败 [slaveId=%d, identifier=%s, functionCode=%d, address=%d, count=%d]", + slaveId, identifier, functionCode, registerAddress, registerCount), e); } }); } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java index 946937d40..fba2f756b 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpclient/manager/IotModbusTcpClientPollScheduler.java @@ -9,8 +9,16 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbu import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpClientUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.handler.upstream.IotModbusTcpClientUpstreamHandler; import io.vertx.core.Vertx; +import lombok.AllArgsConstructor; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; + /** * IoT Modbus TCP Client 轮询调度器:管理点位的轮询定时器,调度读取任务并上报结果 * @@ -35,6 +43,47 @@ public class IotModbusTcpClientPollScheduler extends AbstractIotModbusPollSchedu // ========== 轮询执行 ========== + @Override + protected List buildPollTasks(IotModbusDeviceConfigRespDTO config) { + return convertList(buildReadSegments(config), segment -> new PollTask(segment.getKey(), segment.getPollInterval())); + } + + /** + * 轮询读取段 + */ + @Override + protected void pollTask(Long deviceId, String taskKey) { + // 1.1 从 configCache 获取最新配置 + IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); + if (config == null || CollUtil.isEmpty(config.getPoints())) { + log.warn("[pollTask][设备 {} 没有配置]", deviceId); + return; + } + // 1.2 查找读取段。配置变化后,如果当前 taskKey 已不存在,直接跳过等待下一轮 updatePolling 清理 timer + ReadSegment segment = findReadSegment(config, taskKey); + if (segment == null) { + log.debug("[pollTask][设备 {} 读取段 {} 未找到,跳过陈旧轮询任务]", deviceId, taskKey); + return; + } + + // 2.1 获取连接 + IotModbusTcpClientConnectionManager.ModbusConnection connection = connectionManager.getConnection(deviceId); + if (connection == null) { + log.warn("[pollTask][设备 {} 没有连接]", deviceId); + return; + } + // 2.2 获取 slave ID + Integer slaveId = connectionManager.getSlaveId(deviceId); + Assert.notNull(slaveId, "设备 {} 没有配置 slaveId", deviceId); + + // 3. 执行 Modbus 批量读取 + IotModbusTcpClientUtils.read(connection, slaveId, segment.getFunctionCode(), + segment.getStartAddress(), segment.getRegisterCount()) + .onSuccess(rawValues -> handleSegmentReadResult(config, segment, rawValues)) + .onFailure(e -> log.error("[pollTask][读取点位段失败, deviceId={}, segment={}]", + deviceId, segment.getKey(), e)); + } + /** * 轮询单个点位 */ @@ -70,4 +119,174 @@ public class IotModbusTcpClientPollScheduler extends AbstractIotModbusPollSchedu deviceId, point.getIdentifier(), e)); } + private void handleSegmentReadResult(IotModbusDeviceConfigRespDTO config, + ReadSegment segment, + int[] rawValues) { + for (IotModbusPointRespDTO point : segment.getPoints()) { + // 批量读取返回的是整个连续地址段,需要按点位地址切片后再复用单点上报逻辑 + int[] pointRawValues = extractPointRawValues(rawValues, segment, point); + if (pointRawValues == null) { + log.warn("[handleSegmentReadResult][读取段结果长度不足, deviceId={}, segment={}, identifier={}]", + config.getDeviceId(), segment.getKey(), point.getIdentifier()); + continue; + } + upstreamHandler.handleReadResult(config, point, pointRawValues); + } + } + + private ReadSegment findReadSegment(IotModbusDeviceConfigRespDTO config, String taskKey) { + return CollUtil.findOne(buildReadSegments(config), segment -> segment.getKey().equals(taskKey)); + } + + /** + * 构建批量读取段 + * + *

只合并功能码、轮询间隔相同,且地址连续或重叠的点位;不跨功能码、不跨轮询间隔,避免改变原有轮询语义。 + * 同时按 Modbus 协议限制控制单次读取长度,超过限制时拆成多个读取段。 + */ + static List buildReadSegments(IotModbusDeviceConfigRespDTO config) { + if (config == null) { + return Collections.emptyList(); + } + + // 1. 按功能码和轮询间隔分组:两者任一不同,都不能共用同一次 Modbus 读请求 + List validPoints = filterList(config.getPoints(), IotModbusTcpClientPollScheduler::isValidReadPoint); + if (CollUtil.isEmpty(validPoints)) { + return Collections.emptyList(); + } + Map> pointsByGroup = convertMultiMap(validPoints, + point -> new SegmentGroupKey(point.getFunctionCode(), point.getPollInterval())); + + // 2. 组内按地址排序后,合并连续或重叠区间,生成实际轮询的读取段 + List segments = new ArrayList<>(); + for (Map.Entry> entry : pointsByGroup.entrySet()) { + List points = entry.getValue(); + points.sort(Comparator.comparing(IotModbusPointRespDTO::getRegisterAddress) + .thenComparing(IotModbusPointRespDTO::getRegisterCount) + .thenComparing(IotModbusPointRespDTO::getId)); + buildReadSegments(entry.getKey(), points, segments); + } + // 3. 固定排序,保证生成的 taskKey 稳定,便于 updatePolling 做增量更新 + segments.sort(Comparator.comparing(ReadSegment::getFunctionCode) + .thenComparing(ReadSegment::getPollInterval) + .thenComparing(ReadSegment::getStartAddress)); + return segments; + } + + private static void buildReadSegments(SegmentGroupKey groupKey, + List points, + List segments) { + ReadSegment current = null; + int maxRegisterCount = getMaxRegisterCount(groupKey.getFunctionCode()); + // points 已按 registerAddress 排序,因此可以线性合并连续/重叠地址段 + for (IotModbusPointRespDTO point : points) { + int pointStartAddress = point.getRegisterAddress(); + int pointEndAddress = pointStartAddress + point.getRegisterCount(); + // 1. 当前点位无法合并时,新建一个读取段 + if (current == null || !canMerge(current, pointStartAddress, pointEndAddress, maxRegisterCount)) { + current = new ReadSegment(groupKey.getFunctionCode(), groupKey.getPollInterval(), + pointStartAddress, point.getRegisterCount(), new ArrayList<>()); + segments.add(current); + } else { + // 2. 当前点位可合并时,扩展读取段覆盖范围 + current.setRegisterCount(Math.max(current.getEndAddress(), pointEndAddress) - current.getStartAddress()); + } + // 3. 记录读取段包含的点位,读取成功后按点位逐个切片上报 + current.getPoints().add(point); + } + } + + /** + * 判断点位是否可以合并到当前读取段 + * + *

仅合并连续或重叠区间,不合并存在地址空洞的区间,避免额外读取无关寄存器。 + */ + private static boolean canMerge(ReadSegment segment, int pointStartAddress, int pointEndAddress, int maxRegisterCount) { + if (pointStartAddress > segment.getEndAddress()) { + return false; + } + int mergedRegisterCount = Math.max(segment.getEndAddress(), pointEndAddress) - segment.getStartAddress(); + return mergedRegisterCount <= maxRegisterCount; + } + + /** + * 从批量读取结果中提取单个点位的原始值 + * + *

例如读取段从地址 10 开始,点位地址为 12、数量为 2,则取 rawValues[2..4)。 + */ + static int[] extractPointRawValues(int[] rawValues, ReadSegment segment, IotModbusPointRespDTO point) { + if (rawValues == null) { + return null; + } + // 1. 计算点位在批量读取结果中的相对偏移 + int offset = point.getRegisterAddress() - segment.getStartAddress(); + int end = offset + point.getRegisterCount(); + // 2. 防御异常响应长度,避免越界影响同一读取段内其它点位 + if (offset < 0 || end > rawValues.length) { + return null; + } + // 3. 返回单个点位需要的原始寄存器值 + return Arrays.copyOfRange(rawValues, offset, end); + } + + private static boolean isValidReadPoint(IotModbusPointRespDTO point) { + return point != null + && point.getId() != null + && point.getFunctionCode() != null + && point.getRegisterAddress() != null + && point.getRegisterCount() != null + && point.getRegisterCount() > 0 + && point.getPollInterval() != null + && point.getPollInterval() > 0; + } + + @SuppressWarnings("EnhancedSwitchMigration") + private static int getMaxRegisterCount(Integer functionCode) { + switch (functionCode) { + case IotModbusCommonUtils.FC_READ_COILS: + case IotModbusCommonUtils.FC_READ_DISCRETE_INPUTS: + return 2000; + case IotModbusCommonUtils.FC_READ_HOLDING_REGISTERS: + case IotModbusCommonUtils.FC_READ_INPUT_REGISTERS: + return 125; + default: + return 0; + } + } + + /** + * 读取段分组 Key + */ + @Data + @AllArgsConstructor + static class SegmentGroupKey { + + private Integer functionCode; + private Integer pollInterval; + + } + + /** + * 一次 Modbus 批量读请求对应的连续地址段 + */ + @Data + @AllArgsConstructor + static class ReadSegment { + + private Integer functionCode; + private Integer pollInterval; + private Integer startAddress; + private Integer registerCount; + private List points; + + String getKey() { + return functionCode + ":" + pollInterval + ":" + startAddress + ":" + registerCount; + } + + int getEndAddress() { + return startAddress + registerCount; + } + + } + } diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java index 835ef6293..e3f732b98 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/rule/vo/scene/IotSceneRuleRespVO.java @@ -29,6 +29,9 @@ public class IotSceneRuleRespVO { @Schema(description = "执行器数组", requiredMode = Schema.RequiredMode.REQUIRED) private List actions; + @Schema(description = "最后触发时间") + private LocalDateTime lastTriggerTime; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java index 52b0d360d..330e1055a 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java @@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX { default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(IotAlertConfigDO::getStatus, status) - .apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId))); + .apply(MyBatisUtils.findInSet("scene_rule_ids"), sceneRuleId)); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 1e3fb2e57..84c2d9937 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; @@ -34,7 +35,7 @@ public interface IotDeviceMapper extends BaseMapperX { .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname()) .eqIfPresent(IotDeviceDO::getState, reqVO.getStatus()) .eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId()) - .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), "FIND_IN_SET(" + reqVO.getGroupId() + ",group_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), MyBatisUtils.findInSet("group_ids"), reqVO.getGroupId()) .orderByDesc(IotDeviceDO::getId)); } @@ -72,7 +73,7 @@ public interface IotDeviceMapper extends BaseMapperX { default Long selectCountByGroupId(Long groupId) { return selectCount(new LambdaQueryWrapperX() - .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0")); + .apply(MyBatisUtils.findInSet("group_ids"), groupId)); } default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) { diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java index ce2eeb04b..579b62e9d 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java @@ -28,7 +28,7 @@ public interface IotDataRuleMapper extends BaseMapperX { default List selectListBySinkId(Long sinkId) { return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("sink_ids", sinkId))); + .apply(MyBatisUtils.findInSet("sink_ids"), sinkId)); } default List selectListByStatus(Integer status) { @@ -39,4 +39,4 @@ public interface IotDataRuleMapper extends BaseMapperX { return selectOne(IotDataRuleDO::getName, name); } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java index 820cc41c1..7310ca68c 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/service/alert/IotAlertConfigServiceImpl.java @@ -47,7 +47,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService { public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) { // 校验关联数据是否存在 sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds()); - adminUserApi.validateUserList(createReqVO.getReceiveUserIds()).checkError(); + adminUserApi.validateUserList(createReqVO.getReceiveUserIds()); validateReceiveTemplates(createReqVO); // 插入 @@ -62,7 +62,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService { validateAlertConfigExists(updateReqVO.getId()); // 校验关联数据是否存在 sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds()); - adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()).checkError(); + adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()); validateReceiveTemplates(updateReqVO); // 更新 diff --git a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java index 68693e0a3..fdadd685a 100755 --- a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java +++ b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO; import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum; @@ -34,7 +35,7 @@ public interface CouponTemplateMapper extends BaseMapperX { .eqIfPresent(CouponTemplateDO::getDiscountType, reqVO.getDiscountType()) .betweenIfPresent(CouponTemplateDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(CouponTemplateDO::getProductScope, reqVO.getProductScope()) - .and(reqVO.getProductScopeValue() != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(reqVO.getProductScopeValue() != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), reqVO.getProductScopeValue())) .and(canTakeConsumer != null, canTakeConsumer) .orderByDesc(CouponTemplateDO::getId)); @@ -61,7 +62,7 @@ public interface CouponTemplateMapper extends BaseMapperX { Consumer> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes); return selectList(new LambdaQueryWrapperX() .eqIfPresent(CouponTemplateDO::getProductScope, productScope) - .and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)", + .and(productScopeValue != null, w -> w.apply(MyBatisUtils.findInSet("product_scope_values"), productScopeValue)) .and(canTakeConsumer != null, canTakeConsumer) .last(" LIMIT " + count) diff --git a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java index 6d8e9c684..831ad0d8b 100755 --- a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/reward/RewardActivityMapper.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.promotion.dal.mysql.reward; -import cn.hutool.core.util.StrUtil; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.reward.vo.RewardActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.reward.RewardActivityDO; import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum; @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 满减送活动 Mapper @@ -34,18 +35,18 @@ public interface RewardActivityMapper extends BaseMapperX { Collection categoryIds, Integer status) { LocalDateTime now = LocalDateTime.now(); - Function, String> productScopeValuesFindInSetFunc = ids -> ids.stream() - .map(id -> StrUtil.format("FIND_IN_SET({}, product_scope_values) ", id)) + Function, String> productScopeValuesFindInSetFunc = ids -> IntStream.range(0, ids.size()) + .mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("product_scope_values", index)) .collect(Collectors.joining(" OR ")); return selectList(new LambdaQueryWrapperX() .eq(RewardActivityDO::getStatus, status) .lt(RewardActivityDO::getStartTime, now) .gt(RewardActivityDO::getEndTime, now) .and(i -> i.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.SPU.getScope()) - .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds))) + .and(i1 -> i1.apply(productScopeValuesFindInSetFunc.apply(spuIds), spuIds.toArray())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.ALL.getScope())) .or(i1 -> i1.eq(RewardActivityDO::getProductScope, PromotionProductScopeEnum.CATEGORY.getScope()) - .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds))))) + .and(i2 -> i2.apply(productScopeValuesFindInSetFunc.apply(categoryIds), categoryIds.toArray())))) .orderByDesc(RewardActivityDO::getId) ); } diff --git a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java index 34e50f1f5..7f0c07daf 100644 --- a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java +++ b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillActivityMapper.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO; @@ -27,7 +28,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .likeIfPresent(SeckillActivityDO::getName, reqVO.getName()) .eqIfPresent(SeckillActivityDO::getStatus, reqVO.getStatus()) .betweenIfPresent(SeckillActivityDO::getCreateTime, reqVO.getCreateTime()) - .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), "FIND_IN_SET(" + reqVO.getConfigId() + ", config_ids) > 0") + .apply(ObjectUtil.isNotNull(reqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), reqVO.getConfigId()) .orderByDesc(SeckillActivityDO::getId)); } @@ -70,7 +71,7 @@ public interface SeckillActivityMapper extends BaseMapperX { .eqIfPresent(SeckillActivityDO::getStatus, status) .lt(SeckillActivityDO::getStartTime, dateTime) .gt(SeckillActivityDO::getEndTime, dateTime)// 开始时间 < 指定时间 < 结束时间,也就是说获取指定时间段的活动 - .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), "FIND_IN_SET(" + pageReqVO.getConfigId() + ",config_ids) > 0")); + .apply(ObjectUtil.isNotNull(pageReqVO.getConfigId()), MyBatisUtils.findInSet("config_ids"), pageReqVO.getConfigId())); } default SeckillActivityDO selectBySpuIdAndStatusAndNow(Long spuId, Integer status) { diff --git a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index cfca03a9b..ce808790a 100644 --- a/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-server/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -69,7 +69,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { conversationService.updateConversationLastMessage(kefuMessage); // 3.1 发送消息给会员 - AdminUserRespDTO user = adminUserApi.getUser(kefuMessage.getSenderId()).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(kefuMessage.getSenderId()); KeFuMessageRespVO message = BeanUtils.toBean(kefuMessage, KeFuMessageRespVO.class).setSenderAvatar(user.getAvatar()); getSelf().sendAsyncMessageToMember(conversation.getUserId(), KEFU_MESSAGE_TYPE, message); // 3.2 通知所有管理员更新对话 diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java index 71432ec81..904c246f2 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java @@ -72,7 +72,7 @@ public class DeliveryPickUpStoreController { return success(null); } List verifyUsers = CollUtil.isNotEmpty(deliveryPickUpStore.getVerifyUserIds()) ? - adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()).getCheckedData() : null; + adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()) : null; return success(BeanUtils.toBean(deliveryPickUpStore, DeliveryPickUpStoreRespVO.class) .setVerifyUsers(BeanUtils.toBean(verifyUsers, UserSimpleBaseVO.class))); } diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java index f2e38ce5e..c8090a793 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java @@ -93,7 +93,7 @@ public class DeliveryPickUpStoreServiceImpl implements DeliveryPickUpStoreServic // 1.1 校验门店存在 validateDeliveryPickUpStoreExists(bindReqVO.getId()); // 1.2 校验用户存在 - adminUserApi.validateUserList(bindReqVO.getVerifyUserIds()).checkError(); + adminUserApi.validateUserList(bindReqVO.getVerifyUserIds()); // 2. 更新 DeliveryPickUpStoreDO updateObj = BeanUtils.toBean(bindReqVO, DeliveryPickUpStoreDO.class); diff --git a/yudao-module-member/yudao-module-member-server/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java b/yudao-module-member/yudao-module-member-server/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java index 6200bf125..9da83b1f8 100644 --- a/yudao-module-member/yudao-module-member-server/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java +++ b/yudao-module-member/yudao-module-member-server/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.member.dal.mysql.user; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; 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.util.MyBatisUtils; import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Mapper; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * 会员 User Mapper @@ -38,10 +39,12 @@ public interface MemberUserMapper extends BaseMapperX { default PageResult selectPage(MemberUserPageReqVO reqVO) { // 处理 tagIds 过滤条件 String tagIdSql = ""; + Object[] tagIdParams = new Object[0]; if (CollUtil.isNotEmpty(reqVO.getTagIds())) { - tagIdSql = reqVO.getTagIds().stream() - .map(tagId -> "FIND_IN_SET(" + tagId + ", tag_ids)") + tagIdSql = IntStream.range(0, reqVO.getTagIds().size()) + .mapToObj(index -> MyBatisUtils.findInSetWithParamIndex("tag_ids", index)) .collect(Collectors.joining(" OR ")); + tagIdParams = reqVO.getTagIds().toArray(); } // 分页查询 return selectPage(reqVO, new LambdaQueryWrapperX() @@ -52,7 +55,7 @@ public interface MemberUserMapper extends BaseMapperX { .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime()) .eqIfPresent(MemberUserDO::getLevelId, reqVO.getLevelId()) .eqIfPresent(MemberUserDO::getGroupId, reqVO.getGroupId()) - .apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql) + .apply(CollUtil.isNotEmpty(reqVO.getTagIds()), tagIdSql, tagIdParams) .orderByDesc(MemberUserDO::getId)); } @@ -66,7 +69,7 @@ public interface MemberUserMapper extends BaseMapperX { default Long selectCountByTagId(Long tagId) { return selectCount(new LambdaQueryWrapperX() - .apply("FIND_IN_SET({0}, tag_ids)", tagId)); + .apply(MyBatisUtils.findInSet("tag_ids"), tagId)); } /** diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workrecord/MesProWorkRecordController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workrecord/MesProWorkRecordController.java index 5a594103f..2b95fa021 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workrecord/MesProWorkRecordController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workrecord/MesProWorkRecordController.java @@ -143,7 +143,7 @@ public class MesProWorkRecordController { vo.setWorkstationCode(ws.getCode()).setWorkstationName(ws.getName()); } // 拼接用户信息 - AdminUserRespDTO user = adminUserApi.getUser(record.getUserId()).getCheckedData(); + AdminUserRespDTO user = adminUserApi.getUser(record.getUserId()); if (user != null) { vo.setUserNickname(user.getNickname()); } diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java index 55f7361b6..24a537903 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptController.java @@ -26,11 +26,6 @@ import java.util.List; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -/** - * MES 杂项入库单 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 杂项入库单") @RestController @RequestMapping("/mes/wm/misc-receipt") diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptLineController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptLineController.java index 8d6d93d67..8abd396d1 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptLineController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/miscreceipt/MesWmMiscReceiptLineController.java @@ -36,9 +36,6 @@ import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; -/** - * MES 杂项入库单行 Controller - */ @Tag(name = "管理后台 - MES 杂项入库单行") @RestController @RequestMapping("/mes/wm/misc-receipt-line") @@ -47,19 +44,14 @@ public class MesWmMiscReceiptLineController { @Resource private MesWmMiscReceiptLineService miscReceiptLineService; - @Resource private MesMdItemService itemService; - @Resource private MesMdUnitMeasureService unitMeasureService; - @Resource private MesWmWarehouseService warehouseService; - @Resource private MesWmWarehouseLocationService warehouseLocationService; - @Resource private MesWmWarehouseAreaService warehouseAreaService; diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java index 54548ff29..a461d135b 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueController.java @@ -36,11 +36,6 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPOR import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * MES 外协发料单 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 外协发料单") @RestController @RequestMapping("/mes/wm/outsource-issue") @@ -49,7 +44,6 @@ public class MesWmOutsourceIssueController { @Resource private MesWmOutsourceIssueService outsourceIssueService; - @Resource private MesMdVendorService vendorService; @Resource diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueDetailController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueDetailController.java index 526e43b8b..e92fca2ed 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueDetailController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueDetailController.java @@ -34,11 +34,6 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * MES 外协发料单明细 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 外协发料单明细") @RestController @RequestMapping("/mes/wm/outsource-issue-detail") @@ -47,19 +42,14 @@ public class MesWmOutsourceIssueDetailController { @Resource private MesWmOutsourceIssueDetailService outsourceIssueDetailService; - @Resource private MesMdItemService itemService; - @Resource private MesMdUnitMeasureService unitMeasureService; - @Resource private MesWmWarehouseService warehouseService; - @Resource private MesWmWarehouseLocationService locationService; - @Resource private MesWmWarehouseAreaService areaService; diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java index 11ac9d5ba..f62937997 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/outsourceissue/MesWmOutsourceIssueLineController.java @@ -34,11 +34,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * MES 外协发料单行 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 外协发料单行") @RestController @RequestMapping("/mes/wm/outsource-issue-line") @@ -47,13 +42,10 @@ public class MesWmOutsourceIssueLineController { @Resource private MesWmOutsourceIssueLineService outsourceIssueLineService; - @Resource private MesMdItemService itemService; - @Resource private MesMdUnitMeasureService unitMeasureService; - @Resource private MesWmBatchService batchService; diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java index 805a9b1fd..5c52e6f39 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageController.java @@ -32,12 +32,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; -// DONE @AI:检查下 java 类,是不是 author 没加。 -/** - * MES 装箱单 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 装箱单") @RestController @RequestMapping("/mes/wm/package") diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageLineController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageLineController.java index 12ae8c1ab..2b8724a1b 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageLineController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/packages/MesWmPackageLineController.java @@ -32,11 +32,6 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * MES 装箱明细 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 装箱明细") @RestController @RequestMapping("/mes/wm/package-line") diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java index d765f69da..074e7b186 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesController.java @@ -34,11 +34,6 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPOR import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * 管理后台 - MES 销售退货单 Controller - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 销售退货单") @RestController @RequestMapping("/mes/wm/return-sales") diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesDetailController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesDetailController.java index 83a550c7e..d9d291f24 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesDetailController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesDetailController.java @@ -34,11 +34,6 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * 管理后台 - MES 销售退货相关 - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 销售退货明细") @RestController @RequestMapping("/mes/wm/return-sales-detail") @@ -47,19 +42,14 @@ public class MesWmReturnSalesDetailController { @Resource private MesWmReturnSalesDetailService returnSalesDetailService; - @Resource private MesMdItemService itemService; - @Resource private MesMdUnitMeasureService unitMeasureService; - @Resource private MesWmWarehouseService warehouseService; - @Resource private MesWmWarehouseLocationService locationService; - @Resource private MesWmWarehouseAreaService areaService; diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesLineController.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesLineController.java index cf6689542..e32fff181 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesLineController.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/MesWmReturnSalesLineController.java @@ -30,11 +30,6 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -/** - * 管理后台 - MES 销售退货相关 - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - MES 销售退货单行") @RestController @RequestMapping("/mes/wm/return-sales-line") @@ -43,10 +38,8 @@ public class MesWmReturnSalesLineController { @Resource private MesWmReturnSalesLineService returnSalesLineService; - @Resource private MesMdItemService itemService; - @Resource private MesMdUnitMeasureService unitMeasureService; diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java index 29242fe30..3c7280a17 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/template/MesQcTemplateMapper.java @@ -24,7 +24,7 @@ public interface MesQcTemplateMapper extends BaseMapperX { .likeIfPresent(MesQcTemplateDO::getName, reqVO.getName()) .orderByDesc(MesQcTemplateDO::getId); if (reqVO.getType() != null) { - query.apply(MyBatisUtils.findInSet("types", reqVO.getType())); + query.apply(MyBatisUtils.findInSet("types"), reqVO.getType()); } query.eqIfPresent(MesQcTemplateDO::getStatus, reqVO.getStatus()); return selectPage(reqVO, query); diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamMemberServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamMemberServiceImpl.java index eb89f54ce..dcf717ae8 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamMemberServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamMemberServiceImpl.java @@ -95,7 +95,7 @@ public class MesCalTeamMemberServiceImpl implements MesCalTeamMemberService { } private void validateUserExists(Long userId) { - if (adminUserApi.getUser(userId).getCheckedData() == null) { + if (adminUserApi.getUser(userId) == null) { throw exception(CAL_TEAM_MEMBER_USER_NOT_EXISTS); } } diff --git a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImpl.java b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImpl.java index 25cf9ae92..13bd66383 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-server/src/main/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImpl.java @@ -32,6 +32,8 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @Validated public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordService { + private static final int GENERATE_MAX_RETRY_COUNT = 10; + @Resource private MesMdAutoCodeRecordMapper recordMapper; @@ -63,10 +65,34 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic throw exception(AUTO_CODE_GENERATE_FAILED); } - // 2.1 构建上下文 - MesMdAutoCodeContext context = new MesMdAutoCodeContext() - .setRule(rule).setParts(parts).setInputChar(inputChar); - // 2.2 遍历分段,生成编码 + // 2. 循环生成编码,跳过因 Redis 流水号落后导致的历史重复编码 + for (int i = 0; i < GENERATE_MAX_RETRY_COUNT; i++) { + // 2.1 生成编码 + MesMdAutoCodeContext context = new MesMdAutoCodeContext() + .setRule(rule).setParts(parts).setInputChar(inputChar); + String result = generateCode(rule, parts, context); + // 2.2 二次校验(防止重复) + MesMdAutoCodeRecordDO existRecord = recordMapper.selectByResult(result); + if (existRecord != null) { + continue; + } + // 2.3 保存生成记录 + MesMdAutoCodeRecordDO record = new MesMdAutoCodeRecordDO() + .setRuleId(rule.getId()).setResult(result) + .setSerialNo(context.getSerialNo()).setInputChar(inputChar); + recordMapper.insert(record); + return result; + } + + // 3. 重试多次后仍然失败,抛出异常 + throw exception(AUTO_CODE_GENERATE_FAILED); + } + + /** + * 生成编码 + */ + private String generateCode(MesMdAutoCodeRuleDO rule, List parts, + MesMdAutoCodeContext context) { StringBuilder codeBuilder = new StringBuilder(); for (MesMdAutoCodePartDO part : parts) { MesMdAutoCodePartStrategy strategy = strategyMap.get(part.getType()); @@ -78,22 +104,11 @@ public class MesMdAutoCodeRecordServiceImpl implements MesMdAutoCodeRecordServic // 拼接分段编码 codeBuilder.append(partCode); } - // 2.3 补齐处理 + // 补齐处理 String result = codeBuilder.toString(); if (Boolean.TRUE.equals(rule.getPadded()) && rule.getMaxLength() != null) { result = padCode(result, rule); } - - // 3.1 二次校验(防止重复) - MesMdAutoCodeRecordDO existRecord = recordMapper.selectByResult(result); - if (existRecord != null) { - throw exception(AUTO_CODE_GENERATE_FAILED); - } - // 3.2 保存生成记录 - MesMdAutoCodeRecordDO record = new MesMdAutoCodeRecordDO() - .setRuleId(rule.getId()).setResult(result) - .setSerialNo(context.getSerialNo()).setInputChar(inputChar); - recordMapper.insert(record); return result; } diff --git a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImplTest.java b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImplTest.java index a566df9d2..275e13cb9 100644 --- a/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImplTest.java +++ b/yudao-module-mes/yudao-module-mes-server/src/test/java/cn/iocoder/yudao/module/mes/service/md/autocode/MesMdAutoCodeRecordServiceImplTest.java @@ -98,6 +98,42 @@ public class MesMdAutoCodeRecordServiceImplTest { assertEquals(1, record.getSerialNo()); } + @Test + public void testGenerateAutoCode_retryWhenResultExists() { + // 准备参数 + String ruleCode = "DV_MACHINERY_CODE"; + MesMdAutoCodeRuleDO rule = new MesMdAutoCodeRuleDO().setId(5L).setCode(ruleCode).setPadded(false); + MesMdAutoCodePartDO part1 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.FIXED_CHAR.getType()) + .setFixCharacter("M").setLength(1).setSort(1); + MesMdAutoCodePartDO part2 = new MesMdAutoCodePartDO().setType(MesMdAutoCodePartTypeEnum.SERIAL_NUMBER.getType()) + .setSerialStartNo(1).setSerialStep(1).setLength(5).setSort(2).setCycleFlag(false); + // mock + when(ruleService.getAutoCodeRuleByCode(ruleCode)).thenReturn(rule); + when(partService.getAutoCodePartListByRuleId(5L)).thenReturn(Arrays.asList(part1, part2)); + when(fixedCharStrategy.generate(eq(part1), any(MesMdAutoCodeContext.class))).thenReturn("M"); + long[] serialNoRef = {0L}; + when(serialNumberStrategy.generate(eq(part2), any(MesMdAutoCodeContext.class))).thenAnswer(invocation -> { + MesMdAutoCodeContext context = invocation.getArgument(1); + long serialNo = ++serialNoRef[0]; + context.setSerialNo(serialNo); + return String.format("%05d", serialNo); + }); + when(recordMapper.selectByResult("M00001")).thenReturn(new MesMdAutoCodeRecordDO()); + when(recordMapper.selectByResult("M00002")).thenReturn(new MesMdAutoCodeRecordDO()); + when(recordMapper.selectByResult("M00003")).thenReturn(null); + + // 调用 + String result = recordService.generateAutoCode(ruleCode, null); + // 断言 + assertEquals("M00003", result); + ArgumentCaptor captor = ArgumentCaptor.forClass(MesMdAutoCodeRecordDO.class); + verify(recordMapper).insert(captor.capture()); + MesMdAutoCodeRecordDO record = captor.getValue(); + assertEquals(5L, record.getRuleId()); + assertEquals("M00003", record.getResult()); + assertEquals(3L, record.getSerialNo()); + } + @Test public void testGenerateAutoCode_withInputChar() { // 准备参数 diff --git a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java index 6d47f9a7c..98283d9e3 100644 --- a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java +++ b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java @@ -19,9 +19,6 @@ import jakarta.annotation.Resource; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -/** - * @author jason - */ @Tag(name = "用户 APP - 钱包") @RestController @RequestMapping("/pay/wallet") diff --git a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java index 59e6b3eb5..b6fb5f399 100644 --- a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java +++ b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java @@ -26,6 +26,14 @@ public interface PayTransferMapper extends BaseMapperX { .eq(PayTransferDO::getStatus, whereStatus)); } + default int updateChannelPackageInfoIfAbsent(Long id, String channelPackageInfo) { + return update(new PayTransferDO().setChannelPackageInfo(channelPackageInfo), + new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id) + .and(wrapper -> wrapper.isNull(PayTransferDO::getChannelPackageInfo) + .or().eq(PayTransferDO::getChannelPackageInfo, ""))); + } + default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { return selectOne(PayTransferDO::getAppId, appId, PayTransferDO::getMerchantTransferId, merchantOrderId); @@ -59,7 +67,3 @@ public interface PayTransferMapper extends BaseMapperX { } } - - - - diff --git a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index 8f123f810..e2edb1569 100644 --- a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.transfer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -162,6 +163,7 @@ public class PayTransferServiceImpl implements PayTransferService { throw exception(PAY_TRANSFER_NOT_FOUND); } if (PayTransferStatusEnum.isProcessing(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + updateChannelPackageInfoIfAbsent(transfer, notify); log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); return; } @@ -175,11 +177,35 @@ public class PayTransferServiceImpl implements PayTransferService { new PayTransferDO().setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) .setChannelPackageInfo(notify.getChannelPackageInfo())); if (updateCounts == 0) { + PayTransferDO latestTransfer = transferMapper.selectById(transfer.getId()); + if (latestTransfer != null && PayTransferStatusEnum.isProcessing(latestTransfer.getStatus())) { + updateChannelPackageInfoIfAbsent(latestTransfer, notify); + log.info("[notifyTransferProgressing][transfer({}) 已被并发更新为转账中状态,无需重复更新]", + transfer.getId()); + return; + } throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); } log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId()); } + /** + * 补充渠道 package 信息:处理同步任务先更新为转账中,发起转账接口后返回 channelPackageInfo 的场景 + * + * @see Issue #1144 + */ + private void updateChannelPackageInfoIfAbsent(PayTransferDO transfer, PayTransferRespDTO notify) { + if (StrUtil.isBlank(notify.getChannelPackageInfo()) + || StrUtil.isNotBlank(transfer.getChannelPackageInfo())) { + return; + } + int updateCount = transferMapper.updateChannelPackageInfoIfAbsent(transfer.getId(), + notify.getChannelPackageInfo()); + if (updateCount > 0) { + log.info("[updateChannelPackageInfoIfAbsent][transfer({}) 补充渠道 package 信息]", transfer.getId()); + } + } + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { // 1. 校验状态 PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); diff --git a/yudao-module-pay/yudao-module-pay-server/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java b/yudao-module-pay/yudao-module-pay-server/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java new file mode 100644 index 000000000..26bd189ba --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-server/src/test/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceTest.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.module.pay.service.transfer; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; +import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties; +import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.service.app.PayAppService; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link PayTransferServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import({PayTransferServiceImpl.class, PayNoRedisDAO.class}) +public class PayTransferServiceTest extends BaseDbAndRedisUnitTest { + + @Resource + private PayTransferServiceImpl transferService; + + @Resource + private PayTransferMapper transferMapper; + + @MockitoBean + private PayProperties payProperties; + @MockitoBean + private PayAppService appService; + @MockitoBean + private PayChannelService channelService; + @MockitoBean + private PayNotifyService notifyService; + + @Test + public void testNotifyTransferProgressing_fillChannelPackageInfoIfAbsent() { + // mock 数据(PayTransferDO):同步任务先把 WAITING 更新为 PROCESSING,但是不返回 package 信息 + PayTransferDO transfer = randomPojo(PayTransferDO.class, + o -> o.setAppId(10L) + .setNo("T110") + .setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) + .setChannelPackageInfo(null)); + transferMapper.insert(transfer); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setAppId(10L)); + PayTransferRespDTO createNotify = PayTransferRespDTO.processingOf("WX_TRANSFER_110", + "T110", "create"); + createNotify.setChannelPackageInfo("package-info-110"); + + // 调用:模拟发起转账接口随后返回 PROCESSING,并携带微信确认收款 package 信息 + transferService.notifyTransfer(channel, createNotify); + + // 断言:已是 PROCESSING 时仍会补写 package 信息 + PayTransferDO dbTransfer = transferMapper.selectById(transfer.getId()); + assertEquals(PayTransferStatusEnum.PROCESSING.getStatus(), dbTransfer.getStatus()); + assertEquals("package-info-110", dbTransfer.getChannelPackageInfo()); + } + + @Test + public void testNotifyTransferProgressing_notOverwriteChannelPackageInfo() { + // mock 数据(PayTransferDO):已经存在 package 信息 + PayTransferDO transfer = randomPojo(PayTransferDO.class, + o -> o.setAppId(10L) + .setNo("T110") + .setStatus(PayTransferStatusEnum.PROCESSING.getStatus()) + .setChannelPackageInfo("package-info-110")); + transferMapper.insert(transfer); + // 准备参数 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setAppId(10L)); + PayTransferRespDTO syncNotify = PayTransferRespDTO.processingOf("WX_TRANSFER_110", + "T110", "sync"); + + // 调用:后续同步任务不返回 package 信息 + transferService.notifyTransfer(channel, syncNotify); + + // 断言:已有 package 信息不被空值覆盖 + PayTransferDO dbTransfer = transferMapper.selectById(transfer.getId()); + assertEquals("package-info-110", dbTransfer.getChannelPackageInfo()); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-server/src/test/resources/sql/create_tables.sql b/yudao-module-pay/yudao-module-pay-server/src/test/resources/sql/create_tables.sql index 6af612f01..a3df280bb 100644 --- a/yudao-module-pay/yudao-module-pay-server/src/test/resources/sql/create_tables.sql +++ b/yudao-module-pay/yudao-module-pay-server/src/test/resources/sql/create_tables.sql @@ -165,12 +165,14 @@ CREATE TABLE IF NOT EXISTS `pay_transfer` ( `user_name` varchar(64) NULL DEFAULT NULL, `status` tinyint(4) NOT NULL, `notify_url` varchar(1024) NULL DEFAULT NULL, + `user_ip` varchar(50) NULL DEFAULT NULL, `channel_transfer_no` varchar(64) NULL DEFAULT NULL, `success_time` datetime(0) NULL DEFAULT NULL, `channel_error_code` varchar(128) NULL DEFAULT NULL, `channel_error_msg` varchar(256) NULL DEFAULT NULL, `channel_notify_data` varchar(1024) NULL DEFAULT NULL, `channel_extras` varchar(1024) NULL DEFAULT NULL, + `channel_package_info` varchar(1024) NULL DEFAULT NULL, `creator` varchar(64) NULL DEFAULT '', `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP, `updater` varchar(64) NULL DEFAULT '', diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java index 179840971..9545904ea 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.system.api.dept; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.enums.ApiConstants; @@ -25,17 +24,17 @@ public interface DeptApi { @GetMapping(PREFIX + "/get") @Operation(summary = "获得部门信息") @Parameter(name = "id", description = "部门编号", example = "1024", required = true) - CommonResult getDept(@RequestParam("id") Long id); + DeptRespDTO getDept(@RequestParam("id") Long id); @GetMapping(PREFIX + "/list") @Operation(summary = "获得部门信息数组") @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) - CommonResult> getDeptList(@RequestParam("ids") Collection ids); + List getDeptList(@RequestParam("ids") Collection ids); @GetMapping(PREFIX + "/valid") @Operation(summary = "校验部门是否合法") @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) - CommonResult validateDeptList(@RequestParam("ids") Collection ids); + void validateDeptList(@RequestParam("ids") Collection ids); /** * 获得指定编号的部门 Map @@ -44,13 +43,13 @@ public interface DeptApi { * @return 部门 Map */ default Map getDeptMap(Collection ids) { - List list = getDeptList(ids).getCheckedData(); + List list = getDeptList(ids); return CollectionUtils.convertMap(list, DeptRespDTO::getId); } @GetMapping(PREFIX + "/list-child") @Operation(summary = "获得指定部门的所有子部门") @Parameter(name = "id", description = "部门编号", example = "1024", required = true) - CommonResult> getChildDeptList(@RequestParam("id") Long id); + List getChildDeptList(@RequestParam("id") Long id); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 6e6ab20a0..6794c8c86 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.system.api.user; import cn.hutool.core.convert.Convert; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.enums.ApiConstants; @@ -32,27 +31,32 @@ public interface AdminUserApi extends AutoTransable { @GetMapping(PREFIX + "/get") @Operation(summary = "通过用户 ID 查询用户") @Parameter(name = "id", description = "用户编号", example = "1", required = true) - CommonResult getUser(@RequestParam("id") Long id); + AdminUserRespDTO getUser(@RequestParam("id") Long id); @GetMapping(PREFIX + "/list-by-subordinate") @Operation(summary = "通过用户 ID 查询用户下属") @Parameter(name = "id", description = "用户编号", example = "1", required = true) - CommonResult> getUserListBySubordinate(@RequestParam("id") Long id); + List getUserListBySubordinate(@RequestParam("id") Long id); @GetMapping(PREFIX + "/list") @Operation(summary = "通过用户 ID 查询用户们") @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) - CommonResult> getUserList(@RequestParam("ids") Collection ids); + List getUserList(@RequestParam("ids") Collection ids); @GetMapping(PREFIX + "/list-by-dept-id") @Operation(summary = "获得指定部门的用户数组") @Parameter(name = "deptIds", description = "部门编号数组", example = "1,2", required = true) - CommonResult> getUserListByDeptIds(@RequestParam("deptIds") Collection deptIds); + List getUserListByDeptIds(@RequestParam("deptIds") Collection deptIds); @GetMapping(PREFIX + "/list-by-post-id") @Operation(summary = "获得指定岗位的用户数组") @Parameter(name = "postIds", description = "岗位编号数组", example = "2,3", required = true) - CommonResult> getUserListByPostIds(@RequestParam("postIds") Collection postIds); + List getUserListByPostIds(@RequestParam("postIds") Collection postIds); + + @GetMapping(PREFIX + "/list-by-nickname") + @Operation(summary = "根据昵称模糊搜索用户") + @Parameter(name = "nickname", description = "昵称关键词", example = "芋道", required = true) + List getUserListByNickname(@RequestParam("nickname") String nickname); /** * 获得用户 Map @@ -61,7 +65,7 @@ public interface AdminUserApi extends AutoTransable { * @return 用户 Map */ default Map getUserMap(Collection ids) { - List users = getUserList(ids).getCheckedData(); + List users = getUserList(ids); return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); } @@ -73,24 +77,24 @@ public interface AdminUserApi extends AutoTransable { * @param id 用户编号 */ default void validateUser(Long id) { - validateUserList(Collections.singleton(id)).getCheckedData(); + validateUserList(Collections.singleton(id)); } @GetMapping(PREFIX + "/valid") @Operation(summary = "校验用户们是否有效") @Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true) - CommonResult validateUserList(@RequestParam("ids") Collection ids); + void validateUserList(@RequestParam("ids") Collection ids); @Override @FeignIgnore default List selectByIds(List ids) { - return getUserList(Convert.toList(Long.class, ids)).getCheckedData(); + return getUserList(Convert.toList(Long.class, ids)); } @Override @FeignIgnore default AdminUserRespDTO selectById(Object id) { - return getUser(Convert.toLong(id)).getCheckedData(); + return getUser(Convert.toLong(id)); } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java index fa628224d..1fd87ffb5 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java @@ -1,11 +1,9 @@ package cn.iocoder.yudao.module.system.api.dept; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.service.dept.DeptService; -import org.springframework.context.annotation.Bean; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @@ -13,8 +11,6 @@ import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated public class DeptApiImpl implements DeptApi { @@ -23,27 +19,26 @@ public class DeptApiImpl implements DeptApi { private DeptService deptService; @Override - public CommonResult getDept(Long id) { + public DeptRespDTO getDept(Long id) { DeptDO dept = deptService.getDept(id); - return success(BeanUtils.toBean(dept, DeptRespDTO.class)); + return BeanUtils.toBean(dept, DeptRespDTO.class); } @Override - public CommonResult> getDeptList(Collection ids) { + public List getDeptList(Collection ids) { List depts = deptService.getDeptList(ids); - return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + return BeanUtils.toBean(depts, DeptRespDTO.class); } @Override - public CommonResult validateDeptList(Collection ids) { + public void validateDeptList(Collection ids) { deptService.validateDeptList(ids); - return success(true); } @Override - public CommonResult> getChildDeptList(Long id) { + public List getChildDeptList(Long id) { List depts = deptService.getChildDeptList(id); - return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + return BeanUtils.toBean(depts, DeptRespDTO.class); } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index e60bc1d4a..4e22dae82 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.system.api.user; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; @@ -20,7 +19,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @RestController // 提供 RESTful API 接口,给 Feign 调用 @@ -34,25 +32,25 @@ public class AdminUserApiImpl implements AdminUserApi { @Override @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致无法查询用户。类似:https://github.com/YunaiV/ruoyi-vue-pro/issues/1051 - public CommonResult getUser(Long id) { + public AdminUserRespDTO getUser(Long id) { AdminUserDO user = userService.getUser(id); - return success(BeanUtils.toBean(user, AdminUserRespDTO.class)); + return BeanUtils.toBean(user, AdminUserRespDTO.class); } @Override - public CommonResult> getUserListBySubordinate(Long id) { + public List getUserListBySubordinate(Long id) { // 1.1 获取用户负责的部门 AdminUserDO user = userService.getUser(id); if (user == null) { - return success(Collections.emptyList()); + return Collections.emptyList(); } ArrayList deptIds = new ArrayList<>(); DeptDO dept = deptService.getDept(user.getDeptId()); if (dept == null) { - return success(Collections.emptyList()); + return Collections.emptyList(); } if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人 - return success(Collections.emptyList()); + return Collections.emptyList(); } deptIds.add(dept.getId()); // 1.2 获取所有子部门 @@ -64,33 +62,38 @@ public class AdminUserApiImpl implements AdminUserApi { // 2. 获取部门对应的用户信息 List users = userService.getUserListByDeptIds(deptIds); users.removeIf(item -> ObjUtil.equal(item.getId(), id)); // 排除自己 - return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + return BeanUtils.toBean(users, AdminUserRespDTO.class); } @Override - public CommonResult> getUserList(Collection ids) { + public List getUserList(Collection ids) { return DataPermissionUtils.executeIgnore(() -> { // 禁用数据权限。原因是,一般基于指定 id 的 API 查询,都是数据拼接为主 List users = userService.getUserList(ids); - return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + return BeanUtils.toBean(users, AdminUserRespDTO.class); }); } @Override - public CommonResult> getUserListByDeptIds(Collection deptIds) { + public List getUserListByDeptIds(Collection deptIds) { List users = userService.getUserListByDeptIds(deptIds); - return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + return BeanUtils.toBean(users, AdminUserRespDTO.class); } @Override - public CommonResult> getUserListByPostIds(Collection postIds) { + public List getUserListByPostIds(Collection postIds) { List users = userService.getUserListByPostIds(postIds); - return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + return BeanUtils.toBean(users, AdminUserRespDTO.class); } @Override - public CommonResult validateUserList(Collection ids) { + public List getUserListByNickname(String nickname) { + List users = userService.getUserListByNickname(nickname); + return BeanUtils.toBean(users, AdminUserRespDTO.class); + } + + @Override + public void validateUserList(Collection ids) { userService.validateUserList(ids); - return success(true); } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java index c7bec77df..147ce8253 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/PermissionController.java @@ -7,9 +7,9 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission. import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.tenant.TenantService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -20,11 +20,6 @@ import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -/** - * 权限 Controller,提供赋予用户、角色的权限的 API 接口 - * - * @author 芋道源码 - */ @Tag(name = "管理后台 - 权限") @RestController @RequestMapping("/system/permission") diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index 820c13206..7cb7d783c 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.controller.admin.user; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -130,8 +132,16 @@ public class UserController { @GetMapping({"/list-all-simple", "/simple-list"}) @Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项") - public CommonResult> getSimpleUserList() { - List list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus()); + public CommonResult> getSimpleUserList( + @RequestParam(value = "deptId", required = false) Long deptId) { + List list; + if (deptId != null) { + List deptIds = Collections.singletonList(deptId); + list = userService.getDeptUsers(deptIds); + } else { + list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus()); + } + // 拼接数据 Map deptMap = deptService.getDeptMap( convertList(list, AdminUserDO::getDeptId)); @@ -194,4 +204,34 @@ public class UserController { return success(userService.importUserList(list, updateSupport)); } + // ==================== 免鉴权接口(用于 IM 点头像弹名片、加好友搜索等场景) ==================== + + @GetMapping("/get-simple") + @Operation(summary = "获得用户精简信息", description = "用于点头像弹名片等场景;免鉴权") + @Parameter(name = "id", description = "用户编号", required = true, example = "1024") + public CommonResult getSimpleUser(@RequestParam("id") Long id) { + AdminUserDO user = userService.getUser(id); + if (user == null) { + return success(null); + } + // 拼接数据 + DeptDO dept = user.getDeptId() != null ? deptService.getDept(user.getDeptId()) : null; + Map deptMap = dept != null ? Collections.singletonMap(dept.getId(), dept) : Collections.emptyMap(); + return success(CollUtil.getFirst(UserConvert.INSTANCE.convertSimpleList( + Collections.singletonList(user), deptMap))); + } + + @GetMapping("/list-by-nickname") + @Operation(summary = "按昵称模糊搜索用户精简信息", description = "用于加好友等场景;免鉴权;当前仅按昵称匹配") + @Parameter(name = "nickname", description = "昵称关键词", required = true, example = "芋道") + public CommonResult> getSimpleUserListByNickname(@RequestParam("nickname") String nickname) { + if (StrUtil.isBlank(nickname)) { + return success(Collections.emptyList()); + } + // 拼接数据 + List list = userService.getUserListByNickname(nickname.trim()); + Map deptMap = deptService.getDeptMap(convertList(list, AdminUserDO::getDeptId)); + return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap)); + } + } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java index 6dac2869d..3d19d67a5 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java @@ -17,6 +17,11 @@ public class UserSimpleRespVO { @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String nickname; + @Schema(description = "头像") + private String avatar; + @Schema(description = "性别", example = "1") + private Integer sex; + @Schema(description = "部门ID", example = "我是一个用户") private Long deptId; @Schema(description = "部门名称", example = "IT 部") diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java index 44fab07a0..fe2ed4899 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java @@ -21,7 +21,7 @@ public interface MailLogMapper extends BaseMapperX { .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus()) .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime()) .apply(StrUtil.isNotBlank(reqVO.getToMail()), - MyBatisUtils.findInSet("to_mails", reqVO.getToMail())) + MyBatisUtils.findInSet("to_mails"), reqVO.getToMail()) .orderByDesc(MailLogDO::getId)); } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java index a58b057de..c3e5ae3ac 100755 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java @@ -29,7 +29,7 @@ public interface TenantMapper extends BaseMapperX { default List selectListByWebsite(String website) { return selectList(new LambdaQueryWrapperX() - .apply(MyBatisUtils.findInSet("websites", website))); + .apply(MyBatisUtils.findInSet("websites"), website)); } default Long selectCountByPackageId(Long packageId) { diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index 947c72376..4ec976561 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -158,6 +158,14 @@ public interface AdminUserService { */ List getUserList(Collection ids); + + /** + * 获得全部用户列表 + * + * @return 用户列表 + */ + List getUserListAll(); + /** * 校验用户们是否有效。如下情况,视为无效: * 1. 用户编号不存在 @@ -205,6 +213,13 @@ public interface AdminUserService { */ List getUserListByStatus(Integer status); + /** + * 获得指定部门的用户 + * @param deptIds + * @return + */ + List getDeptUsers(Collection deptIds); + /** * 判断密码是否匹配 * diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index abcb67a78..c2840b887 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -126,7 +126,7 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public Long registerUser(AuthRegisterReqVO registerReqVO) { // 1.1 校验是否开启注册 - if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY).getCheckedData(), "true")) { + if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY), "true")) { throw exception(USER_REGISTER_DISABLED); } // 1.2 校验账户配合 @@ -354,6 +354,11 @@ public class AdminUserServiceImpl implements AdminUserService { return userMapper.selectByIds(ids); } + public List getUserListAll() { + return userMapper.selectList(); + } + + @Override public void validateUserList(Collection ids) { if (CollUtil.isEmpty(ids)) { @@ -504,7 +509,7 @@ public class AdminUserServiceImpl implements AdminUserService { throw exception(USER_IMPORT_LIST_IS_EMPTY); } // 1.2 初始化密码不能为空 - String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY).getCheckedData(); + String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY); if (StrUtil.isEmpty(initPassword)) { throw exception(USER_IMPORT_INIT_PASSWORD); } @@ -558,6 +563,11 @@ public class AdminUserServiceImpl implements AdminUserService { return userMapper.selectListByStatus(status); } + @Override + public List getDeptUsers(Collection deptIds) { + return userMapper.selectListByDeptIds(deptIds); + } + @Override public boolean isPasswordMatch(String rawPassword, String encodedPassword) { return passwordEncoder.matches(rawPassword, encodedPassword); diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java index 09312d5c9..8817519f5 100755 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java @@ -142,50 +142,50 @@ public class MailLogServiceImplTest extends BaseDbUnitTest { @Test public void testGetMailLogPage() { - // mock 数据 - MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到 - o.setUserId(1L); - o.setUserType(UserTypeEnum.ADMIN.getValue()); - o.setToMails(Lists.newArrayList("768@qq.com")); - o.setCcMails(Lists.newArrayList()); - o.setBccMails(Lists.newArrayList()); - o.setAccountId(10L); - o.setTemplateId(100L); - o.setSendStatus(MailSendStatusEnum.INIT.getStatus()); - o.setSendTime(buildTime(2023, 2, 10)); - o.setTemplateParams(randomTemplateParams()); - }); - mailLogMapper.insert(dbMailLog); - // 测试 userId 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L))); - // 测试 userType 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue()))); - // 测试 toMails 不匹配(特殊:find_in_set 无法单测) -// mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com")))); - // 测试 accountId 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L))); - // 测试 templateId 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setTemplateId(101L))); - // 测试 sendStatus 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()))); - // 测试 sendTime 不匹配 - mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendTime(buildTime(2023, 3, 10)))); - // 准备参数 - MailLogPageReqVO reqVO = new MailLogPageReqVO(); - reqVO.setUserId(1L); - reqVO.setUserType(UserTypeEnum.ADMIN.getValue()); -// reqVO.setToMail("768@qq.com"); - reqVO.setAccountId(10L); - reqVO.setTemplateId(100L); - reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus()); - reqVO.setSendTime((buildBetweenTime(2023, 2, 1, 2023, 2, 15))); + // mock 数据 + MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到 + o.setUserId(1L); + o.setUserType(UserTypeEnum.ADMIN.getValue()); + o.setToMails(Lists.newArrayList("768@qq.com")); + o.setCcMails(Lists.newArrayList()); + o.setBccMails(Lists.newArrayList()); + o.setAccountId(10L); + o.setTemplateId(100L); + o.setSendStatus(MailSendStatusEnum.INIT.getStatus()); + o.setSendTime(buildTime(2023, 2, 10)); + o.setTemplateParams(randomTemplateParams()); + }); + mailLogMapper.insert(dbMailLog); + // 测试 userId 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L))); + // 测试 userType 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue()))); + // 测试 toMails 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMails(Lists.newArrayList("788@qq.com")))); + // 测试 accountId 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L))); + // 测试 templateId 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setTemplateId(101L))); + // 测试 sendStatus 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()))); + // 测试 sendTime 不匹配 + mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendTime(buildTime(2023, 3, 10)))); + // 准备参数 + MailLogPageReqVO reqVO = new MailLogPageReqVO(); + reqVO.setUserId(1L); + reqVO.setUserType(UserTypeEnum.ADMIN.getValue()); + reqVO.setToMail("768@qq.com"); + reqVO.setAccountId(10L); + reqVO.setTemplateId(100L); + reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus()); + reqVO.setSendTime((buildBetweenTime(2023, 2, 1, 2023, 2, 15))); - // 调用 - PageResult pageResult = mailLogService.getMailLogPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbMailLog, pageResult.getList().get(0)); + // 调用 + PageResult pageResult = mailLogService.getMailLogPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbMailLog, pageResult.getList().get(0)); } private static Map randomTemplateParams() { diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java index 0194072b2..253cbe5c3 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImplTest.java @@ -22,7 +22,6 @@ import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import jakarta.annotation.Resource; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -334,7 +333,6 @@ public class TenantServiceImplTest extends BaseDbUnitTest { } @Test - @Disabled // H2 不支持 find_in_set 函数 public void testGetTenantByWebsite() { // mock 数据 TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setWebsites(singletonList("https://www.iocoder.cn"))); diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index a081f5eb1..68c85103a 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.user; import cn.hutool.core.util.RandomUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -24,6 +23,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper; import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; import cn.iocoder.yudao.module.system.enums.common.SexEnum; +import cn.iocoder.yudao.module.system.mq.producer.user.AdminUserProducer; import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; @@ -43,7 +43,6 @@ import java.util.Map; import java.util.function.Consumer; import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; @@ -87,11 +86,13 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { private ConfigApi configApi; @MockitoBean private OAuth2TokenService oauth2TokenService; + @MockitoBean + private AdminUserProducer adminUserProducer; @BeforeEach public void before() { // mock 初始化密码 - when(configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY)).thenReturn(success("yudaoyuanma")); + when(configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY)).thenReturn("yudaoyuanma"); } @Test