diff --git a/README.md b/README.md
index d5957e571..55c799595 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,7 @@
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
+| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql
index efb464e9c..8d34f8c14 100644
--- a/sql/mysql/ruoyi-vue-pro.sql
+++ b/sql/mysql/ruoyi-vue-pro.sql
@@ -11,7 +11,7 @@
Target Server Version : 80026
File Encoding : 65001
- Date: 17/01/2023 23:47:35
+ Date: 27/01/2023 20:57:31
*/
SET NAMES utf8mb4;
@@ -300,7 +300,7 @@ CREATE TABLE `bpm_form` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义';
-- ----------------------------
-- Records of bpm_form
@@ -329,7 +329,7 @@ CREATE TABLE `bpm_oa_leave` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 33 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OA 请假申请表';
+) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OA 请假申请表';
-- ----------------------------
-- Records of bpm_oa_leave
@@ -359,7 +359,7 @@ CREATE TABLE `bpm_process_definition_ext` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 135 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
+) ENGINE = InnoDB AUTO_INCREMENT = 140 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义的拓展表\n';
-- ----------------------------
-- Records of bpm_process_definition_ext
@@ -389,7 +389,7 @@ CREATE TABLE `bpm_process_instance_ext` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 290 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
+) ENGINE = InnoDB AUTO_INCREMENT = 295 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程实例的拓展';
-- ----------------------------
-- Records of bpm_process_instance_ext
@@ -415,7 +415,7 @@ CREATE TABLE `bpm_task_assign_rule` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 265 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
+) ENGINE = InnoDB AUTO_INCREMENT = 274 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 任务规则表';
-- ----------------------------
-- Records of bpm_task_assign_rule
@@ -444,7 +444,7 @@ CREATE TABLE `bpm_task_ext` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 341 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
+) ENGINE = InnoDB AUTO_INCREMENT = 350 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的流程任务的拓展表';
-- ----------------------------
-- Records of bpm_task_ext
@@ -469,7 +469,7 @@ CREATE TABLE `bpm_user_group` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 111 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户组';
+) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户组';
-- ----------------------------
-- Records of bpm_user_group
@@ -546,7 +546,7 @@ CREATE TABLE `infra_api_error_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 931 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 949 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
-- ----------------------------
-- Records of infra_api_error_log
@@ -584,7 +584,7 @@ CREATE TABLE `infra_codegen_column` (
`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 = 1533 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 1582 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义';
-- ----------------------------
-- Records of infra_codegen_column
@@ -616,7 +616,7 @@ CREATE TABLE `infra_codegen_table` (
`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 = 119 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
+) ENGINE = InnoDB AUTO_INCREMENT = 122 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义';
-- ----------------------------
-- Records of infra_codegen_table
@@ -699,7 +699,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 = 355 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
+) ENGINE = InnoDB AUTO_INCREMENT = 356 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
-- ----------------------------
-- Records of infra_file
@@ -1191,7 +1191,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 = 1213 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
+) ENGINE = InnoDB AUTO_INCREMENT = 1227 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
-- ----------------------------
-- Records of system_dict_data
@@ -1391,6 +1391,10 @@ 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 (1220, 8, '地理位置', 'location', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 地理位置', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:23:51', 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 (1221, 9, '链接', 'link', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 链接', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', 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 (1222, 10, '事件', 'event', 'mp_message_type', 0, 'default', '', '公众号的消息类型 - 事件', '1', '2023-01-17 22:17:32', '1', '2023-01-17 14:24:49', 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 (1223, 0, '初始化', '0', 'system_mail_send_status', 0, 'primary', '', '邮件发送状态 - 初始化\n', '1', '2023-01-26 09:53:49', '1', '2023-01-26 16:36:14', 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 (1224, 10, '发送成功', '10', 'system_mail_send_status', 0, 'success', '', '邮件发送状态 - 发送成功', '1', '2023-01-26 09:54:28', '1', '2023-01-26 16:36:22', 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 (1225, 20, '发送失败', '20', 'system_mail_send_status', 0, 'danger', '', '邮件发送状态 - 发送失败', '1', '2023-01-26 09:54:50', '1', '2023-01-26 16:36:26', 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 (1226, 30, '不发送', '30', 'system_mail_send_status', 0, 'info', '', '邮件发送状态 - 不发送', '1', '2023-01-26 09:55:06', '1', '2023-01-26 16:36:36', b'0');
COMMIT;
-- ----------------------------
@@ -1411,7 +1415,7 @@ CREATE TABLE `system_dict_type` (
`deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 165 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
+) ENGINE = InnoDB AUTO_INCREMENT = 167 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
-- ----------------------------
-- Records of system_dict_type
@@ -1474,6 +1478,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (163, '交易订单项的售后状态', 'trade_order_item_after_sale_status', 0, '交易订单项的售后状态', '1', '2022-12-10 20:58:08', '1', '2022-12-10 20:58:08', b'0', NULL);
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (164, '公众号自动回复的请求关键字匹配模式', 'mp_auto_reply_request_match', 0, '公众号自动回复的请求关键字匹配模式', '1', '2023-01-16 23:29:56', '1', '2023-01-16 23:29:56', b'0', '1970-01-01 00:00:00');
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (165, '公众号的消息类型', 'mp_message_type', 0, '公众号的消息类型', '1', '2023-01-17 22:17:09', '1', '2023-01-17 22:17:09', b'0', '1970-01-01 00:00:00');
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (166, '邮件发送状态', 'system_mail_send_status', 0, '邮件发送状态', '1', '2023-01-26 09:53:13', '1', '2023-01-26 09:53:13', b'0', '1970-01-01 00:00:00');
COMMIT;
-- ----------------------------
@@ -1522,7 +1527,7 @@ CREATE TABLE `system_login_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1963 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 1980 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
-- ----------------------------
-- Records of system_login_log
@@ -1530,6 +1535,102 @@ CREATE TABLE `system_login_log` (
BEGIN;
COMMIT;
+-- ----------------------------
+-- Table structure for system_mail_account
+-- ----------------------------
+DROP TABLE IF EXISTS `system_mail_account`;
+CREATE TABLE `system_mail_account` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱',
+ `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
+ `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
+ `host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'SMTP 服务器域名',
+ `port` int NOT NULL COMMENT 'SMTP 服务器端口',
+ `ssl_enable` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否开启 SSL',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+ `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 = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮箱账号表';
+
+-- ----------------------------
+-- Records of system_mail_account
+-- ----------------------------
+BEGIN;
+INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1, '7684413@qq.com', '7684413@qq.com', '123457', '127.0.0.1', 8080, b'0', '1', '2023-01-25 17:39:52', '1', '2023-01-26 22:59:04', b'0');
+INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'ydym_test@163.com', 'ydym_test@163.com', 'WBZTEINMIFVRYSOE', 'smtp.163.com', 465, b'1', '1', '2023-01-26 01:26:03', '1', '2023-01-26 01:26:03', b'0');
+INSERT INTO `system_mail_account` (`id`, `mail`, `username`, `password`, `host`, `port`, `ssl_enable`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3, '76854114@qq.com', '3335', '11234', 'yunai1.cn', 466, b'0', '1', '2023-01-27 15:06:38', '1', '2023-01-27 07:08:36', b'1');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_mail_log
+-- ----------------------------
+DROP TABLE IF EXISTS `system_mail_log`;
+CREATE TABLE `system_mail_log` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+ `user_id` bigint NULL DEFAULT NULL COMMENT '用户编号',
+ `user_type` tinyint NULL DEFAULT NULL COMMENT '用户类型',
+ `to_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收邮箱地址',
+ `account_id` bigint NOT NULL COMMENT '邮箱账号编号',
+ `from_mail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发送邮箱地址',
+ `template_id` bigint NOT NULL COMMENT '模板编号',
+ `template_code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码',
+ `template_nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版发送人名称',
+ `template_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件标题',
+ `template_content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件内容',
+ `template_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮件参数',
+ `send_status` tinyint NOT NULL DEFAULT 0 COMMENT '发送状态',
+ `send_time` datetime NULL DEFAULT NULL COMMENT '发送时间',
+ `send_message_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送返回的消息 ID',
+ `send_exception` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送异常',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+ `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 = 354 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件日志表';
+
+-- ----------------------------
+-- Records of system_mail_log
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for system_mail_template
+-- ----------------------------
+DROP TABLE IF EXISTS `system_mail_template`;
+CREATE TABLE `system_mail_template` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
+ `name` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板名称',
+ `code` varchar(63) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板编码',
+ `account_id` bigint NOT NULL COMMENT '发送的邮箱账号编号',
+ `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送人名称',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板标题',
+ `content` varchar(10240) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板内容',
+ `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '参数数组',
+ `status` tinyint NOT NULL COMMENT '开启状态',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
+ `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 = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '邮件模版表';
+
+-- ----------------------------
+-- Records of system_mail_template
+-- ----------------------------
+BEGIN;
+INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (13, '后台用户短信登录', 'admin-sms-login', 1, '奥特曼', '你猜我猜', '
您的验证码是{code},名字是{name}
', '[\"code\",\"name\"]', 0, '3', '1', '2021-10-11 08:10:00', '1', '2023-01-26 23:22:05', b'0');
+INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (14, '测试模版', 'test_01', 2, '芋艿', '一个标题', '你是 {key01} 吗?
是的话,赶紧 {key02} 一下!
', '[\"key01\",\"key02\"]', 0, NULL, '1', '2023-01-26 01:27:40', '1', '2023-01-27 10:32:16', b'0');
+INSERT INTO `system_mail_template` (`id`, `name`, `code`, `account_id`, `nickname`, `title`, `content`, `params`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (15, '3', '2', 2, '7', '4', '45
', '[]', 1, '80', '1', '2023-01-27 15:50:35', '1', '2023-01-27 16:34:49', b'0');
+COMMIT;
+
-- ----------------------------
-- Table structure for system_menu
-- ----------------------------
@@ -1553,7 +1654,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 = 2125 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
+) ENGINE = InnoDB AUTO_INCREMENT = 2144 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表';
-- ----------------------------
-- Records of system_menu
@@ -1776,7 +1877,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`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1250, '敏感词更新', 'system:sensitive-word:update', 3, 3, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1251, '敏感词删除', 'system:sensitive-word:delete', 3, 4, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1252, '敏感词导出', 'system:sensitive-word:export', 3, 5, 1247, '', '', '', 0, b'1', b'1', '', '2022-04-07 16:55:03', '', '2022-04-20 17:03:10', b'0');
-INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'people', NULL, 0, b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2022-06-22 13:35:05', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1254, '作者动态', '', 1, 0, 0, 'https://www.iocoder.cn', 'people', NULL, 0, b'1', b'1', '1', '2022-04-23 01:03:15', '1', '2023-01-25 20:05:55', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1255, '数据源配置', '', 2, 1, 2, 'data-source-config', 'rate', 'infra/dataSourceConfig/index', 0, b'1', b'1', '', '2022-04-27 14:37:32', '1', '2022-04-27 22:42:06', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1256, '数据源配置查询', 'infra:data-source-config:query', 3, 1, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1257, '数据源配置创建', 'infra:data-source-config:create', 3, 2, 1255, '', '', '', 0, b'1', b'1', '', '2022-04-27 14:37:32', '', '2022-04-27 14:37:32', b'0');
@@ -1908,6 +2009,20 @@ 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`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2127, '删除菜单', 'mp:menu:delete', 3, 2, 2119, '', '', '', 0, b'1', b'1', '1', '2023-01-17 23:06:16', '1', '2023-01-17 23:06:16', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2128, '查询消息', 'mp:message:query', 3, 0, 2103, '', '', '', 0, b'1', b'1', '1', '2023-01-17 23:07:14', '1', '2023-01-17 23:07:14', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2129, '发送消息', 'mp:message:send', 3, 1, 2103, '', '', '', 0, b'1', b'1', '1', '2023-01-17 23:07:26', '1', '2023-01-17 23:07:26', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2130, '邮箱管理', '', 2, 11, 1, 'mail', 'email', NULL, 0, b'1', b'1', '1', '2023-01-25 17:27:44', '1', '2023-01-25 17:27:44', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2131, '邮箱账号', '', 2, 0, 2130, 'mail-account', 'user', 'system/mail/account/index', 0, b'1', b'1', '', '2023-01-25 09:33:48', '1', '2023-01-26 16:37:37', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2132, '账号查询', 'system:mail-account:query', 3, 1, 2131, '', '', '', 0, b'1', b'1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2133, '账号创建', 'system:mail-account:create', 3, 2, 2131, '', '', '', 0, b'1', b'1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2134, '账号更新', 'system:mail-account:update', 3, 3, 2131, '', '', '', 0, b'1', b'1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2135, '账号删除', 'system:mail-account:delete', 3, 4, 2131, '', '', '', 0, b'1', b'1', '', '2023-01-25 09:33:48', '', '2023-01-25 09:33:48', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2136, '邮件模版', '', 2, 0, 2130, 'mail-template', 'education', 'system/mail/template/index', 0, b'1', b'1', '', '2023-01-25 12:05:31', '1', '2023-01-26 16:38:35', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2137, '模版查询', 'system:mail-template:query', 3, 1, 2136, '', '', '', 0, b'1', b'1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2138, '模版创建', 'system:mail-template:create', 3, 2, 2136, '', '', '', 0, b'1', b'1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2139, '模版更新', 'system:mail-template:update', 3, 3, 2136, '', '', '', 0, b'1', b'1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2140, '模版删除', 'system:mail-template:delete', 3, 4, 2136, '', '', '', 0, b'1', b'1', '', '2023-01-25 12:05:31', '', '2023-01-25 12:05:31', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2141, '邮件记录', '', 2, 0, 2130, 'mail-log', 'log', 'system/mail/log/index', 0, b'1', b'1', '', '2023-01-26 02:16:50', '1', '2023-01-26 16:38:27', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2142, '日志查询', 'system:mail-log:query', 3, 1, 2141, '', '', '', 0, b'1', b'1', '', '2023-01-26 02:16:50', '', '2023-01-26 02:16:50', b'0');
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2143, '发送测试邮件', 'system:mail-template:send-mail', 3, 5, 2136, '', '', '', 0, b'1', b'1', '1', '2023-01-26 23:29:15', '1', '2023-01-26 23:29:15', b'0');
COMMIT;
-- ----------------------------
@@ -1958,7 +2073,7 @@ CREATE TABLE `system_oauth2_access_token` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1083 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 1214 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
-- ----------------------------
-- Records of system_oauth2_access_token
@@ -2080,7 +2195,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 571 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
+) ENGINE = InnoDB AUTO_INCREMENT = 588 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
-- ----------------------------
-- Records of system_oauth2_refresh_token
@@ -2120,7 +2235,7 @@ CREATE TABLE `system_operate_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 4018 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
+) ENGINE = InnoDB AUTO_INCREMENT = 4177 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录';
-- ----------------------------
-- Records of system_operate_log
@@ -2212,7 +2327,7 @@ CREATE TABLE `system_role_menu` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 1991 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
+) ENGINE = InnoDB AUTO_INCREMENT = 2185 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表';
-- ----------------------------
-- Records of system_role_menu
@@ -2628,6 +2743,194 @@ INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_t
INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1988, 118, 1018, '1', '2022-12-30 11:47:52', '1', '2022-12-30 11:47:52', b'0', 129);
INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1989, 118, 1019, '1', '2022-12-30 11:47:52', '1', '2022-12-30 11:47:52', b'0', 129);
INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1990, 118, 1020, '1', '2022-12-30 11:47:52', '1', '2022-12-30 11:47:52', b'0', 129);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1994, 2, 1027, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1995, 2, 1028, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1996, 2, 1029, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1997, 2, 1030, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1998, 2, 1031, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1999, 2, 1032, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2000, 2, 1033, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2001, 2, 1034, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2002, 2, 1035, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2003, 2, 1036, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2004, 2, 1037, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2005, 2, 1038, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2006, 2, 1039, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2007, 2, 1040, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2008, 2, 1042, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2009, 2, 1043, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2010, 2, 1045, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2011, 2, 1046, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2012, 2, 1048, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2013, 2, 1050, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2014, 2, 1051, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2015, 2, 1052, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2016, 2, 1053, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2017, 2, 1054, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2018, 2, 1056, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2019, 2, 1057, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2020, 2, 1058, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2021, 2, 2083, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2022, 2, 1059, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2023, 2, 1060, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2024, 2, 1063, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2025, 2, 1064, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2026, 2, 1065, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2027, 2, 1066, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2028, 2, 1067, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2029, 2, 1070, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2030, 2, 1071, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2031, 2, 1072, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2032, 2, 1073, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2033, 2, 1074, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2034, 2, 1075, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2035, 2, 1076, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2036, 2, 1082, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2037, 2, 1085, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2038, 2, 1086, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2039, 2, 1087, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2040, 2, 1088, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2041, 2, 1089, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2042, 2, 1091, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2043, 2, 1092, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2044, 2, 1095, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2045, 2, 1096, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2046, 2, 1097, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2047, 2, 1098, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2048, 2, 1101, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2049, 2, 1102, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2050, 2, 1103, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2051, 2, 1104, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2052, 2, 1105, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2053, 2, 1106, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2054, 2, 1108, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2055, 2, 1109, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2056, 2, 1111, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2057, 2, 1112, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2058, 2, 1113, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2059, 2, 1114, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2060, 2, 1115, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2061, 2, 1127, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2062, 2, 1128, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2063, 2, 1129, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2064, 2, 1130, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2065, 2, 1131, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2066, 2, 1132, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2067, 2, 1133, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2068, 2, 1134, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2069, 2, 1135, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2070, 2, 1136, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2071, 2, 1137, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2072, 2, 114, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2073, 2, 1139, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2074, 2, 115, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2075, 2, 1140, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2076, 2, 116, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2077, 2, 1141, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2078, 2, 1142, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2079, 2, 1143, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2080, 2, 1150, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2081, 2, 1161, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2082, 2, 1162, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2083, 2, 1163, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2084, 2, 1164, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2085, 2, 1165, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2086, 2, 1166, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2087, 2, 1173, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2088, 2, 1174, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2089, 2, 1175, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2090, 2, 1176, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2091, 2, 1177, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2092, 2, 1178, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2093, 2, 1179, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2094, 2, 1180, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2095, 2, 1181, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2096, 2, 1182, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2097, 2, 1183, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2098, 2, 1184, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2099, 2, 1226, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2100, 2, 1227, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2101, 2, 1228, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2102, 2, 1229, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2103, 2, 1237, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2104, 2, 1238, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2105, 2, 1239, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2106, 2, 1240, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2107, 2, 1241, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2108, 2, 1242, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2109, 2, 1243, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2110, 2, 1247, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2111, 2, 1248, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2112, 2, 1249, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2113, 2, 1250, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2114, 2, 1251, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2115, 2, 1252, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2116, 2, 1254, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2117, 2, 1255, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2118, 2, 1256, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2119, 2, 1257, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2120, 2, 1258, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2121, 2, 1259, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2122, 2, 1260, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2123, 2, 1261, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2124, 2, 1263, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2125, 2, 1264, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2126, 2, 1265, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2127, 2, 1266, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2128, 2, 1267, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2129, 2, 1001, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2130, 2, 1002, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2131, 2, 1003, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2132, 2, 1004, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2133, 2, 1005, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2134, 2, 1006, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2135, 2, 1007, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2136, 2, 1008, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2137, 2, 1009, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2138, 2, 1010, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2142, 2, 1014, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2144, 2, 1016, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2148, 2, 1020, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2149, 2, 1021, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2150, 2, 1022, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2151, 2, 1023, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2152, 2, 1281, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2153, 2, 1282, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2154, 2, 2000, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2155, 2, 2002, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2156, 2, 2003, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2157, 2, 2004, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2158, 2, 2005, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2159, 2, 2006, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2160, 2, 2008, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2161, 2, 2009, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2162, 2, 2010, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2163, 2, 2011, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2164, 2, 2012, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2170, 2, 2019, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2171, 2, 2020, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2172, 2, 2021, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2173, 2, 2022, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2174, 2, 2023, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2175, 2, 2025, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2177, 2, 2027, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2178, 2, 2028, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2179, 2, 2029, '1', '2023-01-25 08:42:58', '1', '2023-01-25 08:42:58', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2180, 2, 2014, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2181, 2, 2015, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2182, 2, 2016, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2183, 2, 2017, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1);
+INSERT INTO `system_role_menu` (`id`, `role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2184, 2, 2018, '1', '2023-01-25 08:43:12', '1', '2023-01-25 08:43:12', b'0', 1);
COMMIT;
-- ----------------------------
@@ -2752,7 +3055,7 @@ CREATE TABLE `system_sms_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
-) ENGINE = InnoDB AUTO_INCREMENT = 339 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
+) ENGINE = InnoDB AUTO_INCREMENT = 347 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
-- ----------------------------
-- Records of system_sms_log
@@ -3050,7 +3353,7 @@ CREATE TABLE `system_users` (
-- Records of system_users
-- ----------------------------
BEGIN;
-INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '127.0.0.1', '2023-01-17 12:33:07', 'admin', '2021-01-05 17:03:47', NULL, '2023-01-17 12:33:07', 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$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '15612345678', 1, 'http://test.yudao.iocoder.cn/e1fdd7271685ec143a0900681606406621717a666ad0b2798b096df41422b32f.png', 0, '0:0:0:0:0:0:0:1', '2023-01-27 14:31:00', 'admin', '2021-01-05 17:03:47', NULL, '2023-01-27 14:31:00', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', 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$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26: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$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '127.0.0.1', '2022-05-28 15:43:17', '', '2021-01-21 02:13:53', NULL, '2022-07-09 09:00:33', b'0', 1);
diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml
index 55868d676..a0484535e 100644
--- a/yudao-dependencies/pom.xml
+++ b/yudao-dependencies/pom.xml
@@ -143,6 +143,11 @@
yudao-spring-boot-starter-captcha
${revision}
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-desensitize
+ ${revision}
+
diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml
index 3bfc020ba..12244f5ce 100644
--- a/yudao-framework/pom.xml
+++ b/yudao-framework/pom.xml
@@ -41,6 +41,7 @@
yudao-spring-boot-starter-flowable
yudao-spring-boot-starter-captcha
yudao-spring-boot-starter-websocket
+ yudao-spring-boot-starter-desensitize
yudao-framework
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 a29d5fb2e..62e0e4441 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
@@ -41,6 +41,11 @@ public class LocalDateTimeUtils {
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
}
+ public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
+ int year2, int mouth2, int day2) {
+ return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
+ }
+
/**
* 判断当前时间是否在该时间范围内
*
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/pom.xml b/yudao-framework/yudao-spring-boot-starter-desensitize/pom.xml
new file mode 100644
index 000000000..c4a96ebe1
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+ yudao-framework
+ cn.iocoder.boot
+ ${revision}
+
+
+ yudao-spring-boot-starter-desensitize
+ 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+ test
+
+
+
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/annotation/DesensitizeBy.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/annotation/DesensitizeBy.java
new file mode 100644
index 000000000..0166cb6ef
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/annotation/DesensitizeBy.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.framework.desensitize.core.base.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
+import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 顶级脱敏注解,自定义注解需要使用此注解
+ *
+ * @author gaibu
+ */
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解,打上了此注解的注解表明是 jackson 注解的一部分
+@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器
+public @interface DesensitizeBy {
+
+ /**
+ * 脱敏处理器
+ */
+ @SuppressWarnings("rawtypes")
+ Class extends DesensitizationHandler> handler();
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java
new file mode 100644
index 000000000..470a0becf
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/handler/DesensitizationHandler.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.desensitize.core.base.handler;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * 脱敏处理器接口
+ *
+ * @author gaibu
+ */
+public interface DesensitizationHandler {
+
+ /**
+ * 脱敏
+ *
+ * @param origin 原始字符串
+ * @param annotation 注解信息
+ * @return 脱敏后的字符串
+ */
+ String desensitize(String origin, T annotation);
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/serializer/StringDesensitizeSerializer.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/serializer/StringDesensitizeSerializer.java
new file mode 100644
index 000000000..2c15a747d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/base/serializer/StringDesensitizeSerializer.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.framework.desensitize.core.base.serializer;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.lang.Singleton;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+
+/**
+ * 脱敏序列化器
+ *
+ * 实现 JSON 返回数据时,使用 {@link DesensitizationHandler} 对声明脱敏注解的字段,进行脱敏处理。
+ *
+ * @author gaibu
+ */
+@SuppressWarnings("rawtypes")
+public class StringDesensitizeSerializer extends StdSerializer implements ContextualSerializer {
+
+ @Getter
+ @Setter
+ private DesensitizationHandler desensitizationHandler;
+
+ protected StringDesensitizeSerializer() {
+ super(String.class);
+ }
+
+ @Override
+ public JsonSerializer> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
+ DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
+ if (annotation == null) {
+ return this;
+ }
+ // 创建一个 StringDesensitizeSerializer 对象,使用 DesensitizeBy 对应的处理器
+ StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();
+ serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));
+ return serializer;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
+ if (StrUtil.isBlank(value)) {
+ gen.writeNull();
+ return;
+ }
+ // 获取序列化字段
+ Field field = getField(gen);
+
+ // 自定义处理器
+ DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);
+ if (ArrayUtil.isEmpty(annotations)) {
+ gen.writeString(value);
+ return;
+ }
+ for (Annotation annotation : field.getAnnotations()) {
+ if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {
+ value = this.desensitizationHandler.desensitize(value, annotation);
+ gen.writeString(value);
+ return;
+ }
+ }
+ gen.writeString(value);
+ }
+
+ /**
+ * 获取字段
+ *
+ * @param generator JsonGenerator
+ * @return 字段
+ */
+ private Field getField(JsonGenerator generator) {
+ String currentName = generator.getOutputContext().getCurrentName();
+ Object currentValue = generator.getCurrentValue();
+ Class> currentValueClass = currentValue.getClass();
+ return ReflectUtil.getField(currentValueClass, currentName);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/package-info.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/package-info.java
new file mode 100644
index 000000000..d56282535
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 脱敏组件:支持 JSON 返回数据时,将邮箱、手机等字段进行脱敏
+ */
+package cn.iocoder.yudao.framework.desensitize.core;
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java
new file mode 100644
index 000000000..227f25499
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/EmailDesensitize.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 邮箱脱敏注解
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = EmailDesensitizationHandler.class)
+public @interface EmailDesensitize {
+
+ /**
+ * 匹配的正则表达式
+ */
+ String regex() default "(^.)[^@]*(@.*$)";
+
+ /**
+ * 替换规则,邮箱;
+ *
+ * 比如:example@gmail.com 脱敏之后为 e****@gmail.com
+ */
+ String replacer() default "$1****$2";
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java
new file mode 100644
index 000000000..4ab7c7415
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/annotation/RegexDesensitize.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.framework.desensitize.core.regex.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 正则脱敏注解
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)
+public @interface RegexDesensitize {
+
+ /**
+ * 匹配的正则表达式(默认匹配所有)
+ */
+ String regex() default "^[\\s\\S]*$";
+
+ /**
+ * 替换规则,会将匹配到的字符串全部替换成 replacer
+ *
+ * 例如:regex=123; replacer=******
+ * 原始字符串 123456789
+ * 脱敏后字符串 ******456789
+ */
+ String replacer() default "******";
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java
new file mode 100644
index 000000000..f43431b1d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * 正则表达式脱敏处理器抽象类,已实现通用的方法
+ *
+ * @author gaibu
+ */
+public abstract class AbstractRegexDesensitizationHandler
+ implements DesensitizationHandler {
+
+ @Override
+ public String desensitize(String origin, T annotation) {
+ String regex = getRegex(annotation);
+ String replacer = getReplacer(annotation);
+ return origin.replaceAll(regex, replacer);
+ }
+
+ /**
+ * 获取注解上的 regex 参数
+ *
+ * @param annotation 注解信息
+ * @return 正则表达式
+ */
+ abstract String getRegex(T annotation);
+
+ /**
+ * 获取注解上的 replacer 参数
+ *
+ * @param annotation 注解信息
+ * @return 待替换的字符串
+ */
+ abstract String getReplacer(T annotation);
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java
new file mode 100644
index 000000000..f92414e0c
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
+
+/**
+ * {@link RegexDesensitize} 的正则脱敏处理器
+ *
+ * @author gaibu
+ */
+public class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler {
+
+ @Override
+ String getRegex(RegexDesensitize annotation) {
+ return annotation.regex();
+ }
+
+ @Override
+ String getReplacer(RegexDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java
new file mode 100644
index 000000000..8d1867a64
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.framework.desensitize.core.regex.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
+
+/**
+ * {@link EmailDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler {
+
+ @Override
+ String getRegex(EmailDesensitize annotation) {
+ return annotation.regex();
+ }
+
+ @Override
+ String getReplacer(EmailDesensitize annotation) {
+ return annotation.replacer();
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java
new file mode 100644
index 000000000..19ad54e25
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/BankCardDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.BankCardDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 银行卡号
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = BankCardDesensitization.class)
+public @interface BankCardDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 6;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 2;
+
+ /**
+ * 替换规则,银行卡号; 比如:9988002866797031 脱敏之后为 998800********31
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java
new file mode 100644
index 000000000..9000e1ec4
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.CarLicenseDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 车牌号
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = CarLicenseDesensitization.class)
+public @interface CarLicenseDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 3;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 1;
+
+ /**
+ * 替换规则,车牌号;比如:粤A66666 脱敏之后为粤A6***6
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java
new file mode 100644
index 000000000..73a0d0ee5
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.ChineseNameDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 中文名
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = ChineseNameDesensitization.class)
+public @interface ChineseNameDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 1;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 0;
+
+ /**
+ * 替换规则,中文名;比如:刘子豪脱敏之后为刘**
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java
new file mode 100644
index 000000000..862235346
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 固定电话
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = FixedPhoneDesensitization.class)
+public @interface FixedPhoneDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 4;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 2;
+
+ /**
+ * 替换规则,固定电话;比如:01086551122 脱敏之后为 0108*****22
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java
new file mode 100644
index 000000000..8a654c915
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/IdCardDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.IdCardDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 身份证
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = IdCardDesensitization.class)
+public @interface IdCardDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 6;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 2;
+
+ /**
+ * 替换规则,身份证号码;比如:530321199204074611 脱敏之后为 530321**********11
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java
new file mode 100644
index 000000000..f0c42f192
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/MobileDesensitize.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.MobileDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 手机号
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = MobileDesensitization.class)
+public @interface MobileDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 3;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 4;
+
+ /**
+ * 替换规则,手机号;比如:13248765917 脱敏之后为 132****5917
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java
new file mode 100644
index 000000000..6a3b2694f
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/PasswordDesensitize.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.PasswordDesensitization;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 密码
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = PasswordDesensitization.class)
+public @interface PasswordDesensitize {
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 0;
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 0;
+
+ /**
+ * 替换规则,密码;
+ *
+ * 比如:123456 脱敏之后为 ******
+ */
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java
new file mode 100644
index 000000000..ec79635b9
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/annotation/SliderDesensitize.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 滑动脱敏注解
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = DefaultDesensitizationHandler.class)
+public @interface SliderDesensitize {
+
+ /**
+ * 后缀保留长度
+ */
+ int suffixKeep() default 0;
+
+ /**
+ * 替换规则,会将前缀后缀保留后,全部替换成 replacer
+ *
+ * 例如:prefixKeep = 1; suffixKeep = 2; replacer = "*";
+ * 原始字符串 123456
+ * 脱敏后 1***56
+ */
+ String replacer() default "*";
+
+ /**
+ * 前缀保留长度
+ */
+ int prefixKeep() default 0;
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java
new file mode 100644
index 000000000..7dd2a7fd1
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * 滑动脱敏处理器抽象类,已实现通用的方法
+ *
+ * @author gaibu
+ */
+public abstract class AbstractSliderDesensitizationHandler
+ implements DesensitizationHandler {
+
+ @Override
+ public String desensitize(String origin, T annotation) {
+ int prefixKeep = getPrefixKeep(annotation);
+ int suffixKeep = getSuffixKeep(annotation);
+ String replacer = getReplacer(annotation);
+ int length = origin.length();
+
+ // 情况一:原始字符串长度小于等于保留长度,则原始字符串全部替换
+ if (prefixKeep >= length || suffixKeep >= length) {
+ return buildReplacerByLength(replacer, length);
+ }
+
+ // 情况二:原始字符串长度小于等于前后缀保留字符串长度,则原始字符串全部替换
+ if ((prefixKeep + suffixKeep) >= length) {
+ return buildReplacerByLength(replacer, length);
+ }
+
+ // 情况三:原始字符串长度大于前后缀保留字符串长度,则替换中间字符串
+ int interval = length - prefixKeep - suffixKeep;
+ return origin.substring(0, prefixKeep) +
+ buildReplacerByLength(replacer, interval) +
+ origin.substring(prefixKeep + interval);
+ }
+
+ /**
+ * 根据长度循环构建替换符
+ *
+ * @param replacer 替换符
+ * @param length 长度
+ * @return 构建后的替换符
+ */
+ private String buildReplacerByLength(String replacer, int length) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ builder.append(replacer);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * 前缀保留长度
+ *
+ * @param annotation 注解信息
+ * @return 前缀保留长度
+ */
+ abstract Integer getPrefixKeep(T annotation);
+
+ /**
+ * 后缀保留长度
+ *
+ * @param annotation 注解信息
+ * @return 后缀保留长度
+ */
+ abstract Integer getSuffixKeep(T annotation);
+
+ /**
+ * 替换符
+ *
+ * @param annotation 注解信息
+ * @return 替换符
+ */
+ abstract String getReplacer(T annotation);
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java
new file mode 100644
index 000000000..e1d90ea6d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/BankCardDesensitization.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
+
+/**
+ * {@link BankCardDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class BankCardDesensitization extends AbstractSliderDesensitizationHandler {
+
+ @Override
+ Integer getPrefixKeep(BankCardDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(BankCardDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(BankCardDesensitize annotation) {
+ return annotation.replacer();
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java
new file mode 100644
index 000000000..34b3e9a69
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
+
+/**
+ * {@link CarLicenseDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler {
+ @Override
+ Integer getPrefixKeep(CarLicenseDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(CarLicenseDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(CarLicenseDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java
new file mode 100644
index 000000000..f71dac0e0
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
+
+/**
+ * {@link ChineseNameDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler {
+
+ @Override
+ Integer getPrefixKeep(ChineseNameDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(ChineseNameDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(ChineseNameDesensitize annotation) {
+ return annotation.replacer();
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java
new file mode 100644
index 000000000..8b0adaeab
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
+
+/**
+ * {@link SliderDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler {
+ @Override
+ Integer getPrefixKeep(SliderDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(SliderDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(SliderDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java
new file mode 100644
index 000000000..6e2326171
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
+
+/**
+ * {@link FixedPhoneDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler {
+ @Override
+ Integer getPrefixKeep(FixedPhoneDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(FixedPhoneDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(FixedPhoneDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java
new file mode 100644
index 000000000..9d525b34c
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/IdCardDesensitization.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
+
+/**
+ * {@link IdCardDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class IdCardDesensitization extends AbstractSliderDesensitizationHandler {
+ @Override
+ Integer getPrefixKeep(IdCardDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(IdCardDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(IdCardDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java
new file mode 100644
index 000000000..582900ad4
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/MobileDesensitization.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
+
+/**
+ * {@link MobileDesensitize} 的脱敏处理器
+ *
+ * @author gaibu
+ */
+public class MobileDesensitization extends AbstractSliderDesensitizationHandler {
+
+ @Override
+ Integer getPrefixKeep(MobileDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(MobileDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(MobileDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java
new file mode 100644
index 000000000..1bccaa2a4
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/main/java/cn/iocoder/yudao/framework/desensitize/core/slider/handler/PasswordDesensitization.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.framework.desensitize.core.slider.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
+
+/**
+ * {@link PasswordDesensitize} 的码脱敏处理器
+ *
+ * @author gaibu
+ */
+public class PasswordDesensitization extends AbstractSliderDesensitizationHandler {
+ @Override
+ Integer getPrefixKeep(PasswordDesensitize annotation) {
+ return annotation.prefixKeep();
+ }
+
+ @Override
+ Integer getSuffixKeep(PasswordDesensitize annotation) {
+ return annotation.suffixKeep();
+ }
+
+ @Override
+ String getReplacer(PasswordDesensitize annotation) {
+ return annotation.replacer();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/DesensitizeTest.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/DesensitizeTest.java
new file mode 100644
index 000000000..c308a0eb5
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/DesensitizeTest.java
@@ -0,0 +1,98 @@
+package cn.iocoder.yudao.framework.desensitize.core;
+
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.EmailDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.regex.annotation.RegexDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.BankCardDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.IdCardDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.PasswordDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.MobileDesensitize;
+import cn.iocoder.yudao.framework.desensitize.core.slider.annotation.SliderDesensitize;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link DesensitizeTest} 的单元测试
+ */
+public class DesensitizeTest extends BaseMockitoUnitTest {
+
+ @Test
+ public void test() {
+ // 准备参数
+ DesensitizeDemo desensitizeDemo = new DesensitizeDemo();
+ desensitizeDemo.setNickname("芋道源码");
+ desensitizeDemo.setBankCard("9988002866797031");
+ desensitizeDemo.setCarLicense("粤A66666");
+ desensitizeDemo.setFixedPhone("01086551122");
+ desensitizeDemo.setIdCard("530321199204074611");
+ desensitizeDemo.setPassword("123456");
+ desensitizeDemo.setPhoneNumber("13248765917");
+ desensitizeDemo.setSlider1("ABCDEFG");
+ desensitizeDemo.setSlider2("ABCDEFG");
+ desensitizeDemo.setSlider3("ABCDEFG");
+ desensitizeDemo.setEmail("1@email.com");
+ desensitizeDemo.setRegex("你好,我是芋道源码");
+ desensitizeDemo.setAddress("北京市海淀区上地十街10号");
+ desensitizeDemo.setOrigin("芋道源码");
+
+ // 调用
+ DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);
+ // 断言
+ assertNotNull(d);
+ assertEquals("芋***", d.getNickname());
+ assertEquals("998800********31", d.getBankCard());
+ assertEquals("粤A6***6", d.getCarLicense());
+ assertEquals("0108*****22", d.getFixedPhone());
+ assertEquals("530321**********11", d.getIdCard());
+ assertEquals("******", d.getPassword());
+ assertEquals("132****5917", d.getPhoneNumber());
+ assertEquals("#######", d.getSlider1());
+ assertEquals("ABC*EFG", d.getSlider2());
+ assertEquals("*******", d.getSlider3());
+ assertEquals("1****@email.com", d.getEmail());
+ assertEquals("你好,我是*", d.getRegex());
+ assertEquals("北京市海淀区上地十街10号*", d.getAddress());
+ assertEquals("芋道源码", d.getOrigin());
+ }
+
+ @Data
+ public static class DesensitizeDemo {
+
+ @ChineseNameDesensitize
+ private String nickname;
+ @BankCardDesensitize
+ private String bankCard;
+ @CarLicenseDesensitize
+ private String carLicense;
+ @FixedPhoneDesensitize
+ private String fixedPhone;
+ @IdCardDesensitize
+ private String idCard;
+ @PasswordDesensitize
+ private String password;
+ @MobileDesensitize
+ private String phoneNumber;
+ @SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = "#")
+ private String slider1;
+ @SliderDesensitize(prefixKeep = 3, suffixKeep = 3)
+ private String slider2;
+ @SliderDesensitize(prefixKeep = 10)
+ private String slider3;
+ @EmailDesensitize
+ private String email;
+ @RegexDesensitize(regex = "芋道源码", replacer = "*")
+ private String regex;
+ @Address
+ private String address;
+ private String origin;
+
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/annotation/Address.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/annotation/Address.java
new file mode 100644
index 000000000..735d25b34
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/annotation/Address.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.framework.desensitize.core.annotation;
+
+import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
+import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
+import cn.iocoder.yudao.framework.desensitize.core.handler.AddressHandler;
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 地址
+ *
+ * 用于 {@link DesensitizeTest} 测试使用
+ *
+ * @author gaibu
+ */
+@Documented
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@DesensitizeBy(handler = AddressHandler.class)
+public @interface Address {
+
+ String replacer() default "*";
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/handler/AddressHandler.java b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/handler/AddressHandler.java
new file mode 100644
index 000000000..7a8455f8d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-desensitize/src/test/java/cn/iocoder/yudao/framework/desensitize/core/handler/AddressHandler.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.framework.desensitize.core.handler;
+
+import cn.iocoder.yudao.framework.desensitize.core.DesensitizeTest;
+import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
+import cn.iocoder.yudao.framework.desensitize.core.annotation.Address;
+
+/**
+ * {@link Address} 的脱敏处理器
+ *
+ * 用于 {@link DesensitizeTest} 测试使用
+ */
+public class AddressHandler implements DesensitizationHandler {
+
+ @Override
+ public String desensitize(String origin, Address annotation) {
+ return origin + annotation.replacer();
+ }
+
+}
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 e0b5e4bb5..a831337e8 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
@@ -81,22 +81,19 @@ public interface BaseMapperX extends BaseMapper {
}
/**
- * 逐条插入,适合少量数据插入,或者对性能要求不高的场景
- *
- * 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
- * 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
+ * 批量插入,适合大量数据插入
*
* @param entities 实体们
*/
default void insertBatch(Collection entities) {
- entities.forEach(this::insert);
+ Db.saveBatch(entities);
}
/**
* 批量插入,适合大量数据插入
*
* @param entities 实体们
- * @param size 插入数量 Db.saveBatch 默认为1000
+ * @param size 插入数量 Db.saveBatch 默认为 1000
*/
default void insertBatch(Collection entities, int size) {
Db.saveBatch(entities, size);
diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
index 8664dae42..11a810cf1 100644
--- a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
@@ -7,7 +7,10 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import uk.co.jemos.podam.api.PodamFactory;
import uk.co.jemos.podam.api.PodamFactoryImpl;
+import uk.co.jemos.podam.common.AttributeStrategy;
+import javax.validation.constraints.Email;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Arrays;
@@ -95,6 +98,10 @@ public class RandomUtils {
return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
}
+ public static String randomEmail() {
+ return randomString() + "@qq.com";
+ }
+
@SafeVarargs
public static T randomPojo(Class clazz, Consumer... consumers) {
T pojo = PODAM_FACTORY.manufacturePojo(clazz);
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/package-info.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/package-info.java
index 8c69219d0..2dc531692 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/package-info.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/package-info.java
@@ -1 +1,4 @@
+/**
+ * Web 框架,全局异常、API 日志等
+ */
package cn.iocoder.yudao.framework;
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
index 95955d5ee..b20565eca 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
@@ -2,27 +2,18 @@ package cn.iocoder.yudao.framework.web.config;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
-import cn.iocoder.yudao.framework.web.core.clean.JsoupXssCleaner;
-import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
-import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
-import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
-import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.util.AntPathMatcher;
-import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@@ -34,7 +25,7 @@ import javax.annotation.Resource;
import javax.servlet.Filter;
@AutoConfiguration
-@EnableConfigurationProperties({WebProperties.class, XssProperties.class})
+@EnableConfigurationProperties(WebProperties.class)
public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
@Resource
@@ -107,15 +98,6 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);
}
- /**
- * 创建 XssFilter Bean,解决 Xss 安全问题
- */
- @Bean
- @ConditionalOnBean(XssCleaner.class)
- public FilterRegistrationBean xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
- return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
- }
-
/**
* 创建 DemoFilter Bean,演示模式
*/
@@ -125,33 +107,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
}
-
- /**
- * Xss 清理者
- *
- * @return XssCleaner
- */
- @Bean
- @ConditionalOnMissingBean(XssCleaner.class)
- public XssCleaner xssCleaner() {
- return new JsoupXssCleaner();
- }
-
- /**
- * 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
- *
- * @return Jackson2ObjectMapperBuilderCustomizer
- */
- @Bean
- @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
- @ConditionalOnBean(ObjectMapper.class)
- @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
- public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
- // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
- return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
- }
-
- private static FilterRegistrationBean createFilterBean(T filter, Integer order) {
+ public static FilterRegistrationBean createFilterBean(T filter, Integer order) {
FilterRegistrationBean bean = new FilterRegistrationBean<>(filter);
bean.setOrder(order);
return bean;
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/XssProperties.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/XssProperties.java
similarity index 92%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/XssProperties.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/XssProperties.java
index e4d123089..c914b0465 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/XssProperties.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/XssProperties.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.web.config;
+package cn.iocoder.yudao.framework.xss.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
new file mode 100644
index 000000000..d5d892c07
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.framework.xss.config;
+
+import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
+import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
+import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
+import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
+import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import static cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration.createFilterBean;
+
+@AutoConfiguration
+@EnableConfigurationProperties(XssProperties.class)
+public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
+
+ /**
+ * Xss 清理者
+ *
+ * @return XssCleaner
+ */
+ @Bean
+ @ConditionalOnMissingBean(XssCleaner.class)
+ public XssCleaner xssCleaner() {
+ return new JsoupXssCleaner();
+ }
+
+ /**
+ * 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
+ *
+ * @return Jackson2ObjectMapperBuilderCustomizer
+ */
+ @Bean
+ @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
+ @ConditionalOnBean(ObjectMapper.class)
+ @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
+ public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner) {
+ // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
+ return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner));
+ }
+
+ /**
+ * 创建 XssFilter Bean,解决 Xss 安全问题
+ */
+ @Bean
+ @ConditionalOnBean(XssCleaner.class)
+ public FilterRegistrationBean xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
+ return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/JsoupXssCleaner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/JsoupXssCleaner.java
similarity index 84%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/JsoupXssCleaner.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/JsoupXssCleaner.java
index 559267c3f..4cbc907b9 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/JsoupXssCleaner.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/JsoupXssCleaner.java
@@ -1,11 +1,11 @@
-package cn.iocoder.yudao.framework.web.core.clean;
+package cn.iocoder.yudao.framework.xss.core.clean;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Safelist;
/**
- * jsonp 过滤字符串
+ * 基于 JSONP 实现 XSS 过滤字符串
*/
public class JsoupXssCleaner implements XssCleaner {
@@ -24,21 +24,6 @@ public class JsoupXssCleaner implements XssCleaner {
this.baseUri = "";
}
- public JsoupXssCleaner(Safelist safelist) {
- this.safelist = safelist;
- this.baseUri = "";
- }
-
- public JsoupXssCleaner(String baseUri) {
- this.safelist = buildSafelist();
- this.baseUri = baseUri;
- }
-
- public JsoupXssCleaner(Safelist safelist, String baseUri) {
- this.safelist = safelist;
- this.baseUri = baseUri;
- }
-
/**
* 构建一个 Xss 清理的 Safelist 规则。
* 基于 Safelist#relaxed() 的基础上:
@@ -67,7 +52,6 @@ public class JsoupXssCleaner implements XssCleaner {
// 虽然可以重写 WhiteList#isSafeAttribute 来处理,但是有隐患,所以暂时不支持相对路径
// WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
// WHITELIST.removeProtocols("img", "src", "http", "https");
-
return relaxedSafelist;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/XssCleaner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/XssCleaner.java
similarity index 83%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/XssCleaner.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/XssCleaner.java
index 433f7e775..529bc64a2 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/XssCleaner.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/clean/XssCleaner.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.web.core.clean;
+package cn.iocoder.yudao.framework.xss.core.clean;
/**
* 对 html 文本中的有 Xss 风险的数据进行清理
@@ -12,4 +12,5 @@ public interface XssCleaner {
* @return 清理后的 html
*/
String clean(String html);
+
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssFilter.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssFilter.java
similarity index 80%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssFilter.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssFilter.java
index 2da18768d..5f2ce6e3d 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssFilter.java
@@ -1,7 +1,7 @@
-package cn.iocoder.yudao.framework.web.core.filter;
+package cn.iocoder.yudao.framework.xss.core.filter;
-import cn.iocoder.yudao.framework.web.config.XssProperties;
-import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
+import cn.iocoder.yudao.framework.xss.config.XssProperties;
+import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import lombok.AllArgsConstructor;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -14,8 +14,6 @@ import java.io.IOException;
/**
* Xss 过滤器
- *
- * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
*
* @author 芋道源码
*/
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssRequestWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssRequestWrapper.java
similarity index 95%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssRequestWrapper.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssRequestWrapper.java
index 7beed46cc..35819f443 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssRequestWrapper.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/filter/XssRequestWrapper.java
@@ -1,6 +1,6 @@
-package cn.iocoder.yudao.framework.web.core.filter;
+package cn.iocoder.yudao.framework.xss.core.filter;
-import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
+import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@@ -13,6 +13,7 @@ import java.util.Map;
* @author 芋道源码
*/
public class XssRequestWrapper extends HttpServletRequestWrapper {
+
private final XssCleaner xssCleaner;
public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/json/XssStringJsonDeserializer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/json/XssStringJsonDeserializer.java
similarity index 94%
rename from yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/json/XssStringJsonDeserializer.java
rename to yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/json/XssStringJsonDeserializer.java
index 7e1f631c7..2b1725d96 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/json/XssStringJsonDeserializer.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/core/json/XssStringJsonDeserializer.java
@@ -1,6 +1,6 @@
-package cn.iocoder.yudao.framework.web.core.json;
+package cn.iocoder.yudao.framework.xss.core.json;
-import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
+import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/package-info.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/package-info.java
new file mode 100644
index 000000000..c6e46f070
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 针对 XSS 的基础封装
+ *
+ * XSS 说明:https://tech.meituan.com/2018/09/27/fe-security.html
+ */
+package cn.iocoder.yudao.framework.xss;
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 44678daef..93b2afc3a 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -2,4 +2,5 @@ cn.iocoder.yudao.framework.apilog.config.YudaoApiLogAutoConfiguration
cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration
com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration
cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration
-cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
\ No newline at end of file
+cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration
+cn.iocoder.yudao.framework.xss.config.YudaoXssAutoConfiguration
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
index e770dcafd..a2bf16be4 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileConfigMapper.java
@@ -6,15 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
-
-/**
- * 文件配置 Mapper
- *
- * @author 芋道源码
- */
@Mapper
public interface FileConfigMapper extends BaseMapperX {
@@ -26,7 +18,4 @@ public interface FileConfigMapper extends BaseMapperX {
.orderByDesc(FileConfigDO::getId));
}
- @Select("SELECT COUNT(*) FROM infra_file_config WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
index 671b41943..a3b41197f 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/mq/consumer/file/FileConfigRefreshConsumer.java
@@ -23,7 +23,7 @@ public class FileConfigRefreshConsumer extends AbstractChannelMessageListener getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
List tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
- // 移除置顶前缀的表名 // TODO 未来做成可配置
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("QRTZ_"));
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("ACT_"));
- tables.removeIf(table -> table.getName().toUpperCase().startsWith("FLW_"));
// 移除已经生成的表
// 移除在 Codegen 中,已经存在的
Set existsTables = CollectionUtils.convertSet(
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
index 6b8346cf0..d0c80ba61 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
@@ -52,7 +52,11 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder();
if (StrUtil.isNotEmpty(name)) {
strategyConfig.addInclude(name);
+ } else {
+ // 移除工作流和定时任务前缀的表名 // TODO 未来做成可配置
+ strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
}
+
GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 Date 类型,不使用 LocalDate
ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, strategyConfig.build(),
null, globalConfig, null);
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
index 02197b98e..b3fc6bad2 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
@@ -21,7 +21,7 @@ public interface FileConfigService {
/**
* 初始化文件客户端
*/
- void initFileClients();
+ void initLocalCache();
/**
* 创建文件配置
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
index 48361c14c..590ff8116 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.infra.service.file;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.file.core.client.FileClient;
@@ -19,7 +18,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -29,7 +27,6 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Validator;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -48,18 +45,6 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CONFIG
@Slf4j
public class FileConfigServiceImpl implements FileConfigService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
- /**
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime maxUpdateTime;
-
@Resource
private FileClientFactory fileClientFactory;
/**
@@ -79,34 +64,12 @@ public class FileConfigServiceImpl implements FileConfigService {
@Override
@PostConstruct
- public void initFileClients() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ public void initLocalCache() {
+ // 第一步:查询数据
List configs = fileConfigMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
+ log.info("[initLocalCache][缓存文件配置,数量为:{}]", configs.size());
- // 第二步:构建缓存。创建或更新文件 Client
+ // 第二步:构建缓存:创建或更新文件 Client
configs.forEach(config -> {
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
// 如果是 master,进行设置
@@ -114,9 +77,6 @@ public class FileConfigServiceImpl implements FileConfigService {
masterFileClient = fileClientFactory.getFileClient(config.getId());
}
});
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
}
@Override
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java
index 92153b022..731a29ccc 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/test/TestDemoServiceImpl.java
@@ -79,7 +79,7 @@ public class TestDemoServiceImpl implements TestDemoService {
@Override
public PageResult getTestDemoPage(TestDemoPageReqVO pageReqVO) {
-// testDemoMapper.selectList2();
+ testDemoMapper.selectList2();
return testDemoMapper.selectPage(pageReqVO);
}
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
index 33d0d1ad7..61c9827f6 100755
--- a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
@@ -30,7 +30,6 @@ import java.util.Map;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
@@ -74,16 +73,13 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
when(fileClientFactory.getFileClient(eq(1L))).thenReturn(masterFileClient);
// 调用
- fileConfigService.initFileClients();
+ fileConfigService.initLocalCache();
// 断言 fileClientFactory 调用
verify(fileClientFactory).createOrUpdateFileClient(eq(1L),
eq(configDO1.getStorage()), eq(configDO1.getConfig()));
verify(fileClientFactory).createOrUpdateFileClient(eq(2L),
eq(configDO2.getStorage()), eq(configDO2.getConfig()));
assertSame(masterFileClient, fileConfigService.getMasterFileClient());
- // 断言 maxUpdateTime 缓存
- assertEquals(max(configDO1.getUpdateTime(), configDO2.getUpdateTime()),
- fileConfigService.getMaxUpdateTime());
}
@Test
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java
index 216cf689d..bea47f8ba 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/merchant/PayChannelMapper.java
@@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChann
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -21,9 +19,6 @@ public interface PayChannelMapper extends BaseMapperX {
return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code);
}
- @Select("SELECT COUNT(*) FROM pay_channel WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
default PageResult selectPage(PayChannelPageReqVO reqVO) {
return selectPage(reqVO, new QueryWrapperX()
.eqIfPresent("code", reqVO.getCode())
@@ -67,14 +62,14 @@ public interface PayChannelMapper extends BaseMapperX {
* 根据条件获取渠道
*
* @param merchantId 商户编号
- * @param appid 应用编号 // TODO @aquan:appid =》appId
+ * @param appI 应用编号
* @param code 渠道编码
* @return 数量
*/
- default PayChannelDO selectOne(Long merchantId, Long appid, String code) {
+ default PayChannelDO selectOne(Long merchantId, Long appI, String code) {
return this.selectOne((new QueryWrapper().lambda()
.eq(PayChannelDO::getMerchantId, merchantId)
- .eq(PayChannelDO::getAppId, appid)
+ .eq(PayChannelDO::getAppId, appI)
.eq(PayChannelDO::getCode, code)
));
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
index e2fac7cad..d4869a414 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/merchant/PayChannelServiceImpl.java
@@ -5,7 +5,6 @@ import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
@@ -19,7 +18,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper;
import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -44,12 +42,6 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_E
@Validated
public class PayChannelServiceImpl implements PayChannelService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
@@ -70,40 +62,15 @@ public class PayChannelServiceImpl implements PayChannelService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && channelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List channels = channelMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存支付渠道,数量为:{}]", channels.size());
+ log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size());
- // 第二步:构建缓存。创建或更新支付 Client
+ // 第二步:构建缓存:创建或更新支付 Client
channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
payChannel.getCode(), payChannel.getConfig()));
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(channels, PayChannelDO::getUpdateTime);
});
}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApi.java
new file mode 100644
index 000000000..542727795
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApi.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.system.api.mail;
+
+import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * 邮箱发送 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface MailSendApi {
+
+ /**
+ * 发送单条邮箱给 Admin 用户
+ *
+ * 在 mail 为空时,使用 userId 加载对应 Admin 的邮箱
+ *
+ * @param reqDTO 发送请求
+ * @return 发送日志编号
+ */
+ Long sendSingleMailToAdmin(@Valid MailSendSingleToUserReqDTO reqDTO);
+
+ /**
+ * 发送单条邮箱给 Member 用户
+ *
+ * 在 mail 为空时,使用 userId 加载对应 Member 的邮箱
+ *
+ * @param reqDTO 发送请求
+ * @return 发送日志编号
+ */
+ Long sendSingleMailToMember(@Valid MailSendSingleToUserReqDTO reqDTO);
+
+}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java
new file mode 100644
index 000000000..c8c5cbae5
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.system.api.mail.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+/**
+ * 邮件发送 Request DTO
+ *
+ * @author wangjingqi
+ */
+@Data
+public class MailSendSingleToUserReqDTO {
+
+ /**
+ * 用户编号
+ */
+ private Long userId;
+ /**
+ * 邮箱
+ */
+ @Email
+ private String mail;
+
+ /**
+ * 邮件模板编号
+ */
+ @NotNull(message = "邮件模板编号不能为空")
+ private String templateCode;
+ /**
+ * 邮件模板参数
+ */
+ private Map templateParams;
+
+}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
index e5e416d0e..5eea402f7 100644
--- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -141,4 +141,16 @@ public interface ErrorCodeConstants {
ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1002022000, "code 不存在");
ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1002022001, "code 已过期");
+ // ========== 邮箱账号 1002023000 ==========
+ ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1002023000, "邮箱账号不存在");
+ ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1002023001, "无法删除,该邮箱账号还有邮件模板");
+
+ // ========== 邮件模版 1002024000 ==========
+ ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1002024000, "邮件模版不存在");
+ ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1002024001, "邮件模版 code({}) 已存在");
+
+ // ========== 邮件发送 1002025000 ==========
+ ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1002025000, "模板参数({})缺失");
+ ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1002025000, "邮箱不存在");
+
}
diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/mail/MailSendStatusEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/mail/MailSendStatusEnum.java
new file mode 100644
index 000000000..7cb16edef
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/mail/MailSendStatusEnum.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.system.enums.mail;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 邮件的发送状态枚举
+ *
+ * @author wangjingyi
+ * @since 2022/4/10 13:39
+ */
+@Getter
+@AllArgsConstructor
+public enum MailSendStatusEnum {
+
+ INIT(0), // 初始化
+ SUCCESS(10), // 发送成功
+ FAILURE(20), // 发送失败
+ IGNORE(30), // 忽略,即不发送
+ ;
+
+ private final int status;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml
index 7ee93b9d8..898029163 100644
--- a/yudao-module-system/yudao-module-system-biz/pom.xml
+++ b/yudao-module-system/yudao-module-system-biz/pom.xml
@@ -111,6 +111,10 @@
yudao-spring-boot-starter-captcha
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java
new file mode 100644
index 000000000..72d03afbc
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.system.api.mail;
+
+import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
+import cn.iocoder.yudao.module.system.service.mail.MailSendService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 邮件发送 API 实现类
+ *
+ * @author wangjingyi
+ */
+@Service
+@Validated
+public class MailSendApiImpl implements MailSendApi {
+
+ @Resource
+ private MailSendService mailSendService;
+
+ @Override
+ public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {
+ return mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(),
+ reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
+ }
+
+ @Override
+ public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {
+ return mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(),
+ reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailAccountController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailAccountController.java
new file mode 100644
index 000000000..e2271ceb0
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailAccountController.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail;
+
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.*;
+import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 邮箱账号")
+@RestController
+@RequestMapping("/system/mail-account")
+public class MailAccountController {
+
+ @Resource
+ private MailAccountService mailAccountService;
+
+ @PostMapping("/create")
+ @ApiOperation("创建邮箱账号")
+ @PreAuthorize("@ss.hasPermission('system:mail-account:create')")
+ public CommonResult createMailAccount(@Valid @RequestBody MailAccountCreateReqVO createReqVO) {
+ return success(mailAccountService.createMailAccount(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation("修改邮箱账号")
+ @PreAuthorize("@ss.hasPermission('system:mail-account:update')")
+ public CommonResult updateMailAccount(@Valid @RequestBody MailAccountUpdateReqVO updateReqVO) {
+ mailAccountService.updateMailAccount(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @ApiOperation("删除邮箱账号")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('system:mail-account:delete')")
+ public CommonResult deleteMailAccount(@RequestParam Long id) {
+ mailAccountService.deleteMailAccount(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得邮箱账号")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('system:mail-account:get')")
+ public CommonResult getMailAccount(@RequestParam("id") Long id) {
+ MailAccountDO mailAccountDO = mailAccountService.getMailAccount(id);
+ return success(MailAccountConvert.INSTANCE.convert(mailAccountDO));
+ }
+
+ @GetMapping("/page")
+ @ApiOperation("获得邮箱账号分页")
+ @PreAuthorize("@ss.hasPermission('system:mail-account:query')")
+ public CommonResult> getMailAccountPage(@Valid MailAccountPageReqVO pageReqVO) {
+ PageResult pageResult = mailAccountService.getMailAccountPage(pageReqVO);
+ return success(MailAccountConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @GetMapping("/list-all-simple")
+ @ApiOperation(value = "获得邮箱账号精简列表")
+ public CommonResult> getSimpleMailAccountList() {
+ List list = mailAccountService.getMailAccountList();
+ return success(MailAccountConvert.INSTANCE.convertList02(list));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailLogController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailLogController.java
new file mode 100644
index 000000000..2252c8789
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailLogController.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail;
+
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateRespVO;
+import cn.iocoder.yudao.module.system.convert.mail.MailLogConvert;
+import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.service.mail.MailLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+@Api(tags = "管理后台 - 邮件日志")
+@RestController
+@RequestMapping("/system/mail-log")
+public class MailLogController {
+
+ @Resource
+ private MailLogService mailLogService;
+
+ @GetMapping("/page")
+ @ApiOperation("获得邮箱日志分页")
+ @PreAuthorize("@ss.hasPermission('system:mail-log:query')")
+ public CommonResult> getMailLogPage(@Valid MailLogPageReqVO pageVO) {
+ PageResult pageResult = mailLogService.getMailLogPage(pageVO);
+ return success(MailLogConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得邮箱日志")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('system:mail-log:query')")
+ public CommonResult getMailTemplate(@RequestParam("id") Long id) {
+ MailLogDO mailLogDO = mailLogService.getMailLog(id);
+ return success(MailLogConvert.INSTANCE.convert(mailLogDO));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.http
new file mode 100644
index 000000000..f3c47f514
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.http
@@ -0,0 +1,14 @@
+### 请求 /system/mail-template/send-mail 接口 => 成功
+POST {{baseUrl}}/system/mail-template/send-mail
+Authorization: Bearer {{token}}
+Content-Type: application/json
+tenant-id: {{adminTenentId}}
+
+{
+ "templateCode": "test_01",
+ "mail": "7685413@qq.com",
+ "templateParams": {
+ "key01": "value01",
+ "key02": "value02"
+ }
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java
new file mode 100644
index 000000000..e98b4f084
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.*;
+import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplateSendReqVO;
+import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.service.mail.MailSendService;
+import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 邮件模版")
+@RestController
+@RequestMapping("/system/mail-template")
+public class MailTemplateController {
+
+ @Resource
+ private MailTemplateService mailTempleService;
+ @Resource
+ private MailSendService mailSendService;
+
+ @PostMapping("/create")
+ @ApiOperation("创建邮件模版")
+ @PreAuthorize("@ss.hasPermission('system:mail-template:create')")
+ public CommonResult createMailTemplate(@Valid @RequestBody MailTemplateCreateReqVO createReqVO){
+ return success(mailTempleService.createMailTemplate(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation("修改邮件模版")
+ @PreAuthorize("@ss.hasPermission('system:mail-template:update')")
+ public CommonResult updateMailTemplate(@Valid @RequestBody MailTemplateUpdateReqVO updateReqVO){
+ mailTempleService.updateMailTemplate(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @ApiOperation("删除邮件模版")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('system:mail-template:delete')")
+ public CommonResult deleteMailTemplate(@RequestParam("id") Long id) {
+ mailTempleService.deleteMailTemplate(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得邮件模版")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('system:mail-template:get')")
+ public CommonResult getMailTemplate(@RequestParam("id") Long id) {
+ MailTemplateDO mailTemplateDO = mailTempleService.getMailTemplate(id);
+ return success(MailTemplateConvert.INSTANCE.convert(mailTemplateDO));
+ }
+
+ @GetMapping("/page")
+ @ApiOperation("获得邮件模版分页")
+ @PreAuthorize("@ss.hasPermission('system:mail-template:query')")
+ public CommonResult> getMailTemplatePage(@Valid MailTemplatePageReqVO pageReqVO) {
+ PageResult pageResult = mailTempleService.getMailTemplatePage(pageReqVO);
+ return success(MailTemplateConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @GetMapping("/list-all-simple")
+ @ApiOperation(value = "获得邮件模版精简列表")
+ public CommonResult> getSimpleTemplateList() {
+ List list = mailTempleService.getMailTemplateList();
+ return success(MailTemplateConvert.INSTANCE.convertList02(list));
+ }
+
+ @PostMapping("/send-mail")
+ @ApiOperation("发送短信")
+ @PreAuthorize("@ss.hasPermission('system:mail-template:send-mail')")
+ public CommonResult sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) {
+ return success(mailSendService.sendSingleMailToAdmin(sendReqVO.getMail(), null,
+ sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java
new file mode 100644
index 000000000..ef18cb6ac
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 邮箱账号 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MailAccountBaseVO {
+
+ @ApiModelProperty(value = "邮箱", required = true, example = "yudaoyuanma@123.com")
+ @NotNull(message = "邮箱不能为空")
+ @Email(message = "必须是 Email 格式")
+ private String mail;
+
+ @ApiModelProperty(value = "用户名", required = true, example = "yudao")
+ @NotNull(message = "用户名不能为空")
+ private String username;
+
+ @ApiModelProperty(value = "密码", required = true, example = "123456")
+ @NotNull(message = "密码必填")
+ private String password;
+
+ @ApiModelProperty(value = "SMTP 服务器域名", required = true, example = "www.iocoder.cn")
+ @NotNull(message = "SMTP 服务器域名不能为空")
+ private String host;
+
+ @ApiModelProperty(value = "SMTP 服务器端口", required = true, example = "80")
+ @NotNull(message = "SMTP 服务器端口不能为空")
+ private Integer port;
+
+ @ApiModelProperty(value = "是否开启 ssl", required = true, example = "true")
+ @NotNull(message = "是否开启 ssl 必填")
+ private Boolean sslEnable;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountCreateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountCreateReqVO.java
new file mode 100644
index 000000000..2bcca7850
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountCreateReqVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 邮箱账号创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailAccountCreateReqVO extends MailAccountBaseVO {
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java
new file mode 100644
index 000000000..9e5869172
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 邮箱账号分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailAccountPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "邮箱", required = true, example = "yudaoyuanma@123.com")
+ private String mail;
+
+ @ApiModelProperty(value = "用户名" , required = true , example = "yudao")
+ private String username;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountRespVO.java
new file mode 100644
index 000000000..9eef7281a
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountRespVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+@ApiModel("管理后台 - 邮箱账号 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailAccountRespVO extends MailAccountBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java
new file mode 100644
index 000000000..96916a715
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel("管理后台 - 邮箱账号的精简 Response VO")
+@Data
+public class MailAccountSimpleRespVO {
+
+ @ApiModelProperty(value = "邮箱编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "邮箱", required = true, example = "768541388@qq.com")
+ private String mail;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountUpdateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountUpdateReqVO.java
new file mode 100644
index 000000000..43a13ad18
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountUpdateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.account;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 邮箱账号修改 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailAccountUpdateReqVO extends MailAccountBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogBaseVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogBaseVO.java
new file mode 100755
index 000000000..4a06fb893
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogBaseVO.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log;
+
+import lombok.*;
+import java.util.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 邮件日志 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class MailLogBaseVO {
+
+ @ApiModelProperty(value = "用户编号", example = "30883")
+ private Long userId;
+
+ @ApiModelProperty(value = "用户类型", example = "2", notes = "参见 UserTypeEnum 枚举")
+ private Byte userType;
+
+ @ApiModelProperty(value = "接收邮箱地址", required = true, example = "76854@qq.com")
+ @NotNull(message = "接收邮箱地址不能为空")
+ private String toMail;
+
+ @ApiModelProperty(value = "邮箱账号编号", required = true, example = "18107")
+ @NotNull(message = "邮箱账号编号不能为空")
+ private Long accountId;
+
+ @ApiModelProperty(value = "发送邮箱地址", required = true, example = "85757@qq.com")
+ @NotNull(message = "发送邮箱地址不能为空")
+ private String fromMail;
+
+ @ApiModelProperty(value = "模板编号", required = true, example = "5678")
+ @NotNull(message = "模板编号不能为空")
+ private Long templateId;
+
+ @ApiModelProperty(value = "模板编码", required = true, example = "test_01")
+ @NotNull(message = "模板编码不能为空")
+ private String templateCode;
+
+ @ApiModelProperty(value = "模版发送人名称", example = "李四")
+ private String templateNickname;
+
+ @ApiModelProperty(value = "邮件标题", required = true, example = "测试标题")
+ @NotNull(message = "邮件标题不能为空")
+ private String templateTitle;
+
+ @ApiModelProperty(value = "邮件内容", required = true, example = "测试内容")
+ @NotNull(message = "邮件内容不能为空")
+ private String templateContent;
+
+ @ApiModelProperty(value = "邮件参数", required = true)
+ @NotNull(message = "邮件参数不能为空")
+ private Map templateParams;
+
+ @ApiModelProperty(value = "发送状态", required = true, example = "1", notes = "参见 MailSendStatusEnum 枚举")
+ @NotNull(message = "发送状态不能为空")
+ private Byte sendStatus;
+
+ @ApiModelProperty(value = "发送时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime sendTime;
+
+ @ApiModelProperty(value = "发送返回的消息 ID", example = "28568")
+ private String sendMessageId;
+
+ @ApiModelProperty(value = "发送异常")
+ private String sendException;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogPageReqVO.java
new file mode 100644
index 000000000..1c02a15d0
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogPageReqVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 邮箱日志分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailLogPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "用户编号", example = "30883")
+ private Long userId;
+
+ @ApiModelProperty(value = "用户类型", example = "2", notes = "参见 UserTypeEnum 枚举")
+ private Integer userType;
+
+ @ApiModelProperty(value = "接收邮箱地址", example = "76854@qq.com", notes = "模糊匹配")
+ private String toMail;
+
+ @ApiModelProperty(value = "邮箱账号编号", example = "18107")
+ private Long accountId;
+
+ @ApiModelProperty(value = "模板编号", example = "5678")
+ private Long templateId;
+
+ @ApiModelProperty(value = "发送状态", example = "1", notes = "参见 MailSendStatusEnum 枚举")
+ private Integer sendStatus;
+
+ @ApiModelProperty(value = "发送时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] sendTime;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
new file mode 100755
index 000000000..b0e10355a
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.log;
+
+import lombok.*;
+import java.time.LocalDateTime;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 邮件日志 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailLogRespVO extends MailLogBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "31020")
+ private Long id;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java
new file mode 100644
index 000000000..e2835debd
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 邮件模版 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MailTemplateBaseVO {
+
+ @ApiModelProperty(value = "模版名称", required = true, example = "测试名字")
+ @NotNull(message = "名称不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "模版编号", required = true, example = "test")
+ @NotNull(message = "模版编号不能为空")
+ private String code;
+
+ @ApiModelProperty(value = "发送的邮箱账号编号", required = true, example = "1")
+ @NotNull(message = "发送的邮箱账号编号不能为空")
+ private Long accountId;
+
+ @ApiModelProperty(value = "发送人名称", example = "芋头")
+ private String nickname;
+
+ @ApiModelProperty(value = "标题", required = true, example = "注册成功")
+ @NotEmpty(message = "标题不能为空")
+ private String title;
+
+ @ApiModelProperty(value = "内容", required = true, example = "你好,注册成功啦")
+ @NotEmpty(message = "内容不能为空")
+ private String content;
+
+ @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+ @ApiModelProperty(value = "备注", example = "奥特曼")
+ private String remark;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateCreateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateCreateReqVO.java
new file mode 100644
index 000000000..6eab6e9ab
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateCreateReqVO.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 邮件模版创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailTemplateCreateReqVO extends MailTemplateBaseVO {
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java
new file mode 100644
index 000000000..f3cec08f9
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 邮件模版分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailTemplatePageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "状态", example = "1", notes = "参见 CommonStatusEnum 枚举")
+ private Integer status;
+
+ @ApiModelProperty(value = "标识", example = "code_1024", notes = "模糊匹配")
+ private String code;
+
+ @ApiModelProperty(value = "名称", example = "芋头", notes = "模糊匹配")
+ private String name;
+
+ @ApiModelProperty(value = "账号编号", example = "2048")
+ private Long accountId;
+
+ @ApiModelProperty(value = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateRespVO.java
new file mode 100644
index 000000000..a2153f9cd
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@ApiModel("管理后台 - 邮件末班 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailTemplateRespVO extends MailTemplateBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "参数数组", example = "name,code")
+ private List params;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java
new file mode 100644
index 000000000..ccef59770
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@ApiModel("管理后台 - 邮件发送 Req VO")
+@Data
+public class MailTemplateSendReqVO {
+
+ @ApiModelProperty(value = "接收邮箱", required = true, example = "7685413@qq.com")
+ @NotEmpty(message = "接收邮箱不能为空")
+ private String mail;
+
+ @ApiModelProperty(value = "模板编码", required = true, example = "test_01")
+ @NotNull(message = "模板编码不能为空")
+ private String templateCode;
+
+ @ApiModelProperty(value = "模板参数")
+ private Map templateParams;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java
new file mode 100644
index 000000000..2ff488036
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel("管理后台 - 邮件模版的精简 Response VO")
+@Data
+public class MailTemplateSimpleRespVO {
+
+ @ApiModelProperty(value = "模版编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "模版名字", required = true, example = "哒哒哒")
+ private String name;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateUpdateReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateUpdateReqVO.java
new file mode 100644
index 000000000..5dfa377a2
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateUpdateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.controller.admin.mail.vo.template;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 邮件模版修改 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MailTemplateUpdateReqVO extends MailTemplateBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailAccountConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailAccountConvert.java
new file mode 100644
index 000000000..ffd150135
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailAccountConvert.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.system.convert.mail;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.mail.MailAccount;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface MailAccountConvert {
+
+ MailAccountConvert INSTANCE = Mappers.getMapper(MailAccountConvert.class);
+
+ MailAccountDO convert(MailAccountCreateReqVO bean);
+
+ MailAccountDO convert(MailAccountUpdateReqVO bean);
+
+ MailAccountRespVO convert(MailAccountDO bean);
+
+ PageResult convertPage(PageResult pageResult);
+
+ List convertList02(List list);
+
+ default MailAccount convert(MailAccountDO account, String nickname) {
+ String from = StrUtil.isNotEmpty(nickname) ? nickname + " <" + account.getMail() + ">" : account.getMail();
+ return new MailAccount().setFrom(from).setAuth(true)
+ .setUser(account.getUsername()).setPass(account.getPassword())
+ .setHost(account.getHost()).setPort(account.getPort()).setSslEnable(account.getSslEnable());
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailLogConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailLogConvert.java
new file mode 100644
index 000000000..c5599e355
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailLogConvert.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.system.convert.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface MailLogConvert {
+
+ MailLogConvert INSTANCE = Mappers.getMapper(MailLogConvert.class);
+
+ PageResult convertPage(PageResult pageResult);
+
+ MailLogRespVO convert(MailLogDO bean);
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailTemplateConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailTemplateConvert.java
new file mode 100644
index 000000000..4430ffb05
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailTemplateConvert.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.system.convert.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.*;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface MailTemplateConvert {
+
+ MailTemplateConvert INSTANCE = Mappers.getMapper(MailTemplateConvert.class);
+
+ MailTemplateDO convert(MailTemplateUpdateReqVO bean);
+
+ MailTemplateDO convert(MailTemplateCreateReqVO bean);
+
+ MailTemplateRespVO convert(MailTemplateDO bean);
+
+ PageResult convertPage(PageResult pageResult);
+
+ List convertList02(List list);
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java
new file mode 100644
index 000000000..275fc8570
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.mail;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 邮箱账号 DO
+ *
+ * 用途:配置发送邮箱的账号
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@TableName(value = "system_mail_account", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MailAccountDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 邮箱
+ */
+ private String mail;
+
+ /**
+ * 用户名
+ */
+ private String username;
+ /**
+ * 密码
+ */
+ private String password;
+ /**
+ * SMTP 服务器域名
+ */
+ private String host;
+ /**
+ * SMTP 服务器端口
+ */
+ private Integer port;
+ /**
+ * 是否开启 SSL
+ */
+ private Boolean sslEnable;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
new file mode 100644
index 000000000..e2b2c3cc9
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java
@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.mail;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 邮箱日志 DO
+ * 记录每一次邮件的发送
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@TableName(value = "system_mail_log", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MailLogDO extends BaseDO implements Serializable {
+
+ /**
+ * 日志编号,自增
+ */
+ private Long id;
+
+ /**
+ * 用户编码
+ */
+ private Long userId;
+ /**
+ * 用户类型
+ *
+ * 枚举 {@link UserTypeEnum}
+ */
+ private Integer userType;
+ /**
+ * 接收邮箱地址
+ */
+ private String toMail;
+
+ /**
+ * 邮箱账号编号
+ *
+ * 关联 {@link MailAccountDO#getId()}
+ */
+ private Long accountId;
+ /**
+ * 发送邮箱地址
+ *
+ * 冗余 {@link MailAccountDO#getMail()}
+ */
+ private String fromMail;
+
+ // ========= 模板相关字段 =========
+ /**
+ * 模版编号
+ *
+ * 关联 {@link MailTemplateDO#getId()}
+ */
+ private Long templateId;
+ /**
+ * 模版编码
+ *
+ * 冗余 {@link MailTemplateDO#getCode()}
+ */
+ private String templateCode;
+ /**
+ * 模版发送人名称
+ *
+ * 冗余 {@link MailTemplateDO#getNickname()}
+ */
+ private String templateNickname;
+ /**
+ * 模版标题
+ */
+ private String templateTitle;
+ /**
+ * 模版内容
+ *
+ * 基于 {@link MailTemplateDO#getContent()} 格式化后的内容
+ */
+ private String templateContent;
+ /**
+ * 模版参数
+ *
+ * 基于 {@link MailTemplateDO#getParams()} 输入后的参数
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private Map templateParams;
+
+ // ========= 发送相关字段 =========
+ /**
+ * 发送状态
+ *
+ * 枚举 {@link MailSendStatusEnum}
+ */
+ private Integer sendStatus;
+ /**
+ * 发送时间
+ */
+ private Date sendTime;
+ /**
+ * 发送返回的消息 ID
+ */
+ private String sendMessageId;
+ /**
+ * 发送异常
+ */
+ private String sendException;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java
new file mode 100644
index 000000000..f669b455f
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.mail;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 邮件模版 DO
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@TableName(value = "system_mail_template", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MailTemplateDO extends BaseDO {
+
+ /**
+ * 主键
+ */
+ private Long id;
+ /**
+ * 模版名称
+ */
+ private String name;
+ /**
+ * 模版编号
+ */
+ private String code;
+ /**
+ * 发送的邮箱账号编号
+ *
+ * 关联 {@link MailAccountDO#getId()}
+ */
+ private Long accountId;
+
+ /**
+ * 发送人名称
+ */
+ private String nickname;
+ /**
+ * 标题
+ */
+ private String title;
+ /**
+ * 内容
+ */
+ private String content;
+ /**
+ * 参数数组(自动根据内容生成)
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List params;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java
index c8e6cf1f8..a7050a182 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java
@@ -6,9 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -30,7 +28,4 @@ public interface DeptMapper extends BaseMapperX {
return selectCount(DeptDO::getParentId, parentId);
}
- @Select("SELECT COUNT(*) FROM system_dept WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java
new file mode 100644
index 000000000..e14fc18d7
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.system.dal.mysql.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MailAccountMapper extends BaseMapperX {
+
+ default PageResult selectPage(MailAccountPageReqVO pageReqVO) {
+ return selectPage(pageReqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(MailAccountDO::getMail, pageReqVO.getMail())
+ .likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername()));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java
new file mode 100644
index 000000000..6b147cff6
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.system.dal.mysql.mail;
+
+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.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MailLogMapper extends BaseMapperX {
+
+ default PageResult selectPage(MailLogPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())
+ .likeIfPresent(MailLogDO::getToMail, reqVO.getToMail())
+ .eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())
+ .eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())
+ .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())
+ .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())
+ .orderByDesc(MailLogDO::getId));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java
new file mode 100644
index 000000000..f03aa4ae9
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.system.dal.mysql.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Date;
+
+@Mapper
+public interface MailTemplateMapper extends BaseMapperX {
+
+ default PageResult selectPage(MailTemplatePageReqVO pageReqVO){
+ return selectPage(pageReqVO , new LambdaQueryWrapperX()
+ .eqIfPresent(MailTemplateDO::getStatus, pageReqVO.getStatus())
+ .likeIfPresent(MailTemplateDO::getCode, pageReqVO.getCode())
+ .likeIfPresent(MailTemplateDO::getName, pageReqVO.getName())
+ .eqIfPresent(MailTemplateDO::getAccountId, pageReqVO.getAccountId())
+ .betweenIfPresent(MailTemplateDO::getCreateTime, pageReqVO.getCreateTime()));
+ }
+
+ default Long selectCountByAccountId(Long accountId) {
+ return selectCount(MailTemplateDO::getAccountId, accountId);
+ }
+
+ default MailTemplateDO selectByCode(String code) {
+ return selectOne(MailTemplateDO::getCode, code);
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java
index 902fae7bb..e6012b0f0 100755
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java
@@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.time.LocalDateTime;
/**
@@ -30,7 +27,4 @@ public interface OAuth2ClientMapper extends BaseMapperX {
return selectOne(OAuth2ClientDO::getClientId, clientId);
}
- @Select("SELECT COUNT(*) FROM system_oauth2_client WHERE update_time > #{maxUpdateTime}")
- int selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java
index 1334a6e15..cdab84b53 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/MenuMapper.java
@@ -6,9 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuLi
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.List;
@Mapper
@@ -28,7 +26,4 @@ public interface MenuMapper extends BaseMapperX {
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
}
- @Select("SELECT COUNT(*) FROM system_menu WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
index 1882236a3..55e853273 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
@@ -8,10 +8,8 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleEx
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
import org.springframework.lang.Nullable;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -47,7 +45,4 @@ public interface RoleMapper extends BaseMapperX {
return selectList(RoleDO::getStatus, statuses);
}
- @Select("SELECT COUNT(*) FROM system_role WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuBatchInsertMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuBatchInsertMapper.java
deleted file mode 100644
index 69ddfaf90..000000000
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuBatchInsertMapper.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.mysql.permission;
-
-import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.stereotype.Repository;
-
-/**
- * 实体 {@link RoleMenuDO} 的批量插入 Mapper
- *
- * @author 芋道源码
- */
-@Repository
-public class RoleMenuBatchInsertMapper extends ServiceImpl {
-}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java
index a07e87cc9..20f418676 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMenuMapper.java
@@ -2,13 +2,11 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -20,23 +18,21 @@ public interface RoleMenuMapper extends BaseMapperX {
}
default List selectListByRoleId(Long roleId) {
- return selectList(new QueryWrapper().eq("role_id", roleId));
+ return selectList(RoleMenuDO::getRoleId, roleId);
}
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection menuIds) {
- delete(new QueryWrapper().eq("role_id", roleId)
- .in("menu_id", menuIds));
+ delete(new LambdaQueryWrapper()
+ .eq(RoleMenuDO::getRoleId, roleId)
+ .in(RoleMenuDO::getMenuId, menuIds));
}
default void deleteListByMenuId(Long menuId) {
- delete(new QueryWrapper().eq("menu_id", menuId));
+ delete(new LambdaQueryWrapper().eq(RoleMenuDO::getMenuId, menuId));
}
default void deleteListByRoleId(Long roleId) {
- delete(new QueryWrapper().eq("role_id", roleId));
+ delete(new LambdaQueryWrapper().eq(RoleMenuDO::getRoleId, roleId));
}
- @Select("SELECT COUNT(*) FROM system_role_menu WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleBatchInsertMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleBatchInsertMapper.java
deleted file mode 100644
index 474bdb2c1..000000000
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleBatchInsertMapper.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package cn.iocoder.yudao.module.system.dal.mysql.permission;
-
-import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.stereotype.Repository;
-
-/**
- * 实体 {@link UserRoleDO} 的批量插入 Mapper
- *
- * @author 芋道源码
- */
-@Repository
-public class UserRoleBatchInsertMapper extends ServiceImpl {
-}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleMapper.java
index e3700da92..12ff88a0f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/UserRoleMapper.java
@@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -14,32 +12,25 @@ import java.util.List;
public interface UserRoleMapper extends BaseMapperX {
default List selectListByUserId(Long userId) {
- return selectList(new QueryWrapper().eq("user_id", userId));
- }
-
- default List selectListByRoleId(Long roleId) {
- return selectList(new QueryWrapper().eq("role_id", roleId));
+ return selectList(UserRoleDO::getUserId, userId);
}
default void deleteListByUserIdAndRoleIdIds(Long userId, Collection roleIds) {
- delete(new QueryWrapper().eq("user_id", userId)
- .in("role_id", roleIds));
+ delete(new LambdaQueryWrapper()
+ .eq(UserRoleDO::getUserId, userId)
+ .in(UserRoleDO::getRoleId, roleIds));
}
default void deleteListByUserId(Long userId) {
- delete(new QueryWrapper().eq("user_id", userId));
+ delete(new LambdaQueryWrapper().eq(UserRoleDO::getUserId, userId));
}
default void deleteListByRoleId(Long roleId) {
- delete(new QueryWrapper().eq("role_id", roleId));
+ delete(new LambdaQueryWrapper().eq(UserRoleDO::getRoleId, roleId));
}
-
default List selectListByRoleIds(Collection roleIds) {
return selectList(UserRoleDO::getRoleId, roleIds);
}
- @Select("SELECT COUNT(*) FROM system_user_role WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
index 8e2788f09..831188761 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
@@ -7,9 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.Sensitiv
import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.List;
/**
@@ -42,7 +40,4 @@ public interface SensitiveWordMapper extends BaseMapperX {
return selectOne(SensitiveWordDO::getName, name);
}
- @Select("SELECT COUNT(*) FROM system_sensitive_word WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.java
index ec38e896e..4af731fc6 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsChannelMapper.java
@@ -6,9 +6,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-
-import java.time.LocalDateTime;
@Mapper
public interface SmsChannelMapper extends BaseMapperX {
@@ -21,7 +18,4 @@ public interface SmsChannelMapper extends BaseMapperX {
.orderByDesc(SmsChannelDO::getId));
}
- @Select("SELECT COUNT(*) FROM system_sms_channel WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsTemplateMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsTemplateMapper.java
index 08029f51b..ee6f54fef 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsTemplateMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sms/SmsTemplateMapper.java
@@ -7,17 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTempla
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface SmsTemplateMapper extends BaseMapperX {
- @Select("SELECT COUNT(*) FROM system_sms_template WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
default SmsTemplateDO selectByCode(String code) {
return selectOne(SmsTemplateDO::getCode, code);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java
index 6fe0e649a..8731e4628 100755
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenant/TenantMapper.java
@@ -1,15 +1,13 @@
package cn.iocoder.yudao.module.system.dal.mysql.tenant;
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
-import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
-import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
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.module.system.controller.admin.tenant.vo.tenant.TenantExportReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Select;
-import java.time.LocalDateTime;
import java.util.List;
/**
@@ -52,7 +50,4 @@ public interface TenantMapper extends BaseMapperX {
return selectList(TenantDO::getPackageId, packageId);
}
- @Select("SELECT COUNT(*) FROM system_tenant WHERE update_time > #{maxUpdateTime}")
- Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);
-
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java
new file mode 100644
index 000000000..69ddfce33
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailAccountRefreshConsumer.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.system.mq.consumer.mail;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
+import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
+import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link MailAccountRefreshMessage} 的消费者
+ *
+ * @author wangjingyi
+ */
+@Component
+@Slf4j
+public class MailAccountRefreshConsumer extends AbstractChannelMessageListener {
+
+ @Resource
+ private MailAccountService mailAccountService;
+
+ @Override
+ public void onMessage(MailAccountRefreshMessage message) {
+ log.info("[onMessage][收到 Mail Account 刷新信息]");
+ mailAccountService.initLocalCache();
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java
index 4b02f760e..3cff145b8 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java
@@ -2,17 +2,29 @@ package cn.iocoder.yudao.module.system.mq.consumer.mail;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
+import cn.iocoder.yudao.module.system.service.mail.MailSendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
-// TODO 芋艿:这个暂未实现
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link MailSendMessage} 的消费者
+ *
+ * @author 芋道源码
+ */
@Component
@Slf4j
public class MailSendConsumer extends AbstractStreamMessageListener {
+ @Resource
+ private MailSendService mailSendService;
+
@Override
public void onMessage(MailSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
+ mailSendService.doSendMail(message);
}
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java
new file mode 100644
index 000000000..35da1edec
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailTemplateRefreshConsumer.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.system.mq.consumer.mail;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
+import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 针对 {@link MailTemplateRefreshMessage} 的消费者
+ *
+ * @author wangjingyi
+ */
+@Component
+@Slf4j
+public class MailTemplateRefreshConsumer extends AbstractChannelMessageListener {
+
+ @Resource
+ private MailTemplateService mailTemplateService;
+
+ @Override
+ public void onMessage(MailTemplateRefreshMessage message) {
+ log.info("[onMessage][收到 Mail Template 刷新信息]");
+ mailTemplateService.initLocalCache();
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java
new file mode 100644
index 000000000..d6e9d08a9
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailAccountRefreshMessage.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.mq.message.mail;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 邮箱账号的数据刷新 Message
+ *
+ * @author wangjingyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MailAccountRefreshMessage extends AbstractChannelMessage {
+
+ @Override
+ public String getChannel() {
+ return "system.mail-account.refresh";
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
index aee02c76e..0adafa409 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
-import java.util.Map;
/**
* 邮箱发送消息
@@ -17,28 +17,35 @@ import java.util.Map;
public class MailSendMessage extends AbstractStreamMessage {
/**
- * 邮箱地址
+ * 邮件日志编号
*/
- @NotNull(message = "邮箱地址不能为空")
- private String address;
+ @NotNull(message = "邮件日志编号不能为空")
+ private Long logId;
/**
- * 短信模板编号
+ * 接收邮件地址
*/
- @NotNull(message = "短信模板编号不能为空")
- private String templateCode;
+ @NotNull(message = "接收邮件地址不能为空")
+ private String mail;
/**
- * 短信模板参数
+ * 邮件账号编号
*/
- private Map templateParams;
+ @NotNull(message = "邮件账号编号不能为空")
+ private Long accountId;
/**
- * 用户编号,允许空
+ * 邮件发件人
*/
- private Integer userId;
+ private String nickname;
/**
- * 用户类型,允许空
+ * 邮件标题
*/
- private Integer userType;
+ @NotEmpty(message = "邮件标题不能为空")
+ private String title;
+ /**
+ * 邮件内容
+ */
+ @NotEmpty(message = "邮件内容不能为空")
+ private String content;
@Override
public String getStreamKey() {
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java
new file mode 100644
index 000000000..f6ff0925a
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailTemplateRefreshMessage.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.system.mq.message.mail;
+
+import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 邮箱模板的数据刷新 Message
+ *
+ * @author wangjingyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MailTemplateRefreshMessage extends AbstractChannelMessage {
+
+ @Override
+ public String getChannel() {
+ return "system.mail-template.refresh";
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
new file mode 100644
index 000000000..afa958cd7
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.system.mq.producer.mail;
+
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * Mail 邮件相关消息的 Producer
+ *
+ * @author wangjingyi
+ * @since 2021/4/19 13:33
+ */
+@Slf4j
+@Component
+public class MailProducer {
+
+ @Resource
+ private RedisMQTemplate redisMQTemplate;
+
+ /**
+ * 发送 {@link MailTemplateRefreshMessage} 消息
+ */
+ public void sendMailTemplateRefreshMessage() {
+ MailTemplateRefreshMessage message = new MailTemplateRefreshMessage();
+ redisMQTemplate.send(message);
+ }
+
+ /**
+ * 发送 {@link MailAccountRefreshMessage} 消息
+ */
+ public void sendMailAccountRefreshMessage() {
+ MailAccountRefreshMessage message = new MailAccountRefreshMessage();
+ redisMQTemplate.send(message);
+ }
+
+ /**
+ * 发送 {@link MailSendMessage} 消息
+ *
+ * @param sendLogId 发送日志编码
+ * @param mail 接收邮件地址
+ * @param accountId 邮件账号编号
+ * @param nickname 邮件发件人
+ * @param title 邮件标题
+ * @param content 邮件内容
+ */
+ public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,
+ String nickname, String title, String content) {
+ MailSendMessage message = new MailSendMessage()
+ .setLogId(sendLogId).setMail(mail).setAccountId(accountId)
+ .setNickname(nickname).setTitle(title).setContent(content);
+ redisMQTemplate.send(message);
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
index 049a6415a..6262eaf70 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java
@@ -17,6 +17,7 @@ import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@@ -40,19 +41,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class DeptServiceImpl implements DeptService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 部门缓存
* key:部门编号 {@link DeptDO#getId()}
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
- @SuppressWarnings("FieldCanBeLocal")
+ @Getter
private volatile Map deptCache;
/**
* 父部门缓存
@@ -61,11 +56,8 @@ public class DeptServiceImpl implements DeptService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
+ @Getter
private volatile Multimap parentDeptCache;
- /**
- * 缓存部门的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- private volatile LocalDateTime maxUpdateTime;
@Resource
private DeptMapper deptMapper;
@@ -79,48 +71,21 @@ public class DeptServiceImpl implements DeptService {
@Override
@PostConstruct
public synchronized void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List depts = deptMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存部门,数量为:{}]", depts.size());
+ log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
- // 第二步:构建缓存。创建或更新支付 Client
- // 构建缓存
+ // 第二步:构建缓存
ImmutableMap.Builder builder = ImmutableMap.builder();
ImmutableMultimap.Builder parentBuilder = ImmutableMultimap.builder();
depts.forEach(sysRoleDO -> {
builder.put(sysRoleDO.getId(), sysRoleDO);
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
});
- // 设置缓存
deptCache = builder.build();
parentDeptCache = parentBuilder.build();
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(depts, DeptDO::getUpdateTime);
});
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java
new file mode 100644
index 000000000..b0e02cb4b
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 邮箱账号 Service 接口
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+public interface MailAccountService {
+
+ /**
+ * 初始化邮箱账号的本地缓存
+ */
+ void initLocalCache();
+
+ /**
+ * 从缓存中获取邮箱账号
+ *
+ * @param id 编号
+ * @return 邮箱账号
+ */
+ MailAccountDO getMailAccountFromCache(Long id);
+
+ /**
+ * 创建邮箱账号
+ *
+ * @param createReqVO 邮箱账号信息
+ * @return 编号
+ */
+ Long createMailAccount(@Valid MailAccountCreateReqVO createReqVO);
+
+ /**
+ * 修改邮箱账号
+ *
+ * @param updateReqVO 邮箱账号信息
+ */
+ void updateMailAccount(@Valid MailAccountUpdateReqVO updateReqVO);
+
+ /**
+ * 删除邮箱账号
+ *
+ * @param id 编号
+ */
+ void deleteMailAccount(Long id);
+
+ /**
+ * 获取邮箱账号信息
+ *
+ * @param id 编号
+ * @return 邮箱账号信息
+ */
+ MailAccountDO getMailAccount(Long id);
+
+ /**
+ * 获取邮箱账号分页信息
+ *
+ * @param pageReqVO 邮箱账号分页参数
+ * @return 邮箱账号分页信息
+ */
+ PageResult getMailAccountPage(MailAccountPageReqVO pageReqVO);
+
+ /**
+ * 获取邮箱数组信息
+ *
+ * @return 邮箱账号信息数组
+ */
+ List getMailAccountList();
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java
new file mode 100644
index 000000000..4da029e47
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImpl.java
@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+
+/**
+ * 邮箱账号 Service 实现类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@Service
+@Validated
+@Slf4j
+public class MailAccountServiceImpl implements MailAccountService {
+
+ @Resource
+ private MailAccountMapper mailAccountMapper;
+
+ @Resource
+ private MailTemplateService mailTemplateService;
+
+ @Resource
+ private MailProducer mailProducer;
+
+ /**
+ * 邮箱账号缓存
+ * key:邮箱账号编码 {@link MailAccountDO#getId()}
+ *
+ * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
+ */
+ @Getter
+ private volatile Map mailAccountCache;
+
+ @Override
+ @PostConstruct
+ public void initLocalCache() {
+ // 第一步:查询数据
+ List accounts = mailAccountMapper.selectList();
+ log.info("[initLocalCache][缓存邮箱账号,数量:{}]", accounts.size());
+
+ // 第二步:构建缓存
+ mailAccountCache = convertMap(accounts, MailAccountDO::getId);
+ }
+
+ @Override
+ public MailAccountDO getMailAccountFromCache(Long id) {
+ return mailAccountCache.get(id);
+ }
+
+ @Override
+ public Long createMailAccount(MailAccountCreateReqVO createReqVO) {
+ // 插入
+ MailAccountDO account = MailAccountConvert.INSTANCE.convert(createReqVO);
+ mailAccountMapper.insert(account);
+
+ // 发送刷新消息
+ mailProducer.sendMailAccountRefreshMessage();
+ return account.getId();
+ }
+
+ @Override
+ public void updateMailAccount(MailAccountUpdateReqVO updateReqVO) {
+ // 校验是否存在
+ validateMailAccountExists(updateReqVO.getId());
+
+ // 更新
+ MailAccountDO updateObj = MailAccountConvert.INSTANCE.convert(updateReqVO);
+ mailAccountMapper.updateById(updateObj);
+ // 发送刷新消息
+ mailProducer.sendMailAccountRefreshMessage();
+ }
+
+ @Override
+ public void deleteMailAccount(Long id) {
+ // 校验是否存在账号
+ validateMailAccountExists(id);
+ // 校验是否存在关联模版
+ if (mailTemplateService.countByAccountId(id) > 0) {
+ throw exception(MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS);
+ }
+
+ // 删除
+ mailAccountMapper.deleteById(id);
+ // 发送刷新消息
+ mailProducer.sendMailAccountRefreshMessage();
+ }
+
+ private void validateMailAccountExists(Long id) {
+ if (mailAccountMapper.selectById(id) == null) {
+ throw exception(MAIL_ACCOUNT_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public MailAccountDO getMailAccount(Long id) {
+ return mailAccountMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getMailAccountPage(MailAccountPageReqVO pageReqVO) {
+ return mailAccountMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getMailAccountList() {
+ return mailAccountMapper.selectList();
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
new file mode 100644
index 000000000..703467141
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+
+import java.util.Map;
+
+/**
+ * 邮件日志 Service 接口
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+public interface MailLogService {
+
+ /**
+ * 邮件日志分页
+ *
+ * @param pageVO 分页参数
+ * @return 分页结果
+ */
+ PageResult getMailLogPage(MailLogPageReqVO pageVO);
+
+ /**
+ * 获得指定编号的邮件日志
+ *
+ * @param id 日志编号
+ * @return 邮件日志
+ */
+ MailLogDO getMailLog(Long id);
+
+ /**
+ * 创建邮件日志
+ *
+ * @param userId 用户编码
+ * @param userType 用户类型
+ * @param toMail 收件人邮件
+ * @param account 邮件账号信息
+ * @param template 模版信息
+ * @param templateContent 模版内容
+ * @param templateParams 模版参数
+ * @param isSend 是否发送成功
+ * @return 日志编号
+ */
+ Long createMailLog(Long userId,Integer userType, String toMail,
+ MailAccountDO account, MailTemplateDO template ,
+ String templateContent, Map templateParams, Boolean isSend);
+
+ /**
+ * 更新邮件发送结果
+ *
+ * @param logId 日志编号
+ * @param messageId 发送后的消息编号
+ * @param exception 发送异常
+ */
+ void updateMailSendResult(Long logId, String messageId, Exception exception);
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java
new file mode 100644
index 000000000..743c3a4c7
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImpl.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper;
+import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+
+import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
+
+/**
+ * 邮件日志 Service 实现类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@Service
+@Validated
+public class MailLogServiceImpl implements MailLogService {
+
+ @Resource
+ private MailLogMapper mailLogMapper;
+
+ @Override
+ public PageResult getMailLogPage(MailLogPageReqVO pageVO) {
+ return mailLogMapper.selectPage(pageVO);
+ }
+
+ @Override
+ public MailLogDO getMailLog(Long id) {
+ return mailLogMapper.selectById(id);
+ }
+
+ @Override
+ public Long createMailLog(Long userId, Integer userType, String toMail,
+ MailAccountDO account, MailTemplateDO template,
+ String templateContent, Map templateParams, Boolean isSend) {
+ MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();
+ // 根据是否要发送,设置状态
+ logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()
+ : MailSendStatusEnum.IGNORE.getStatus())
+ // 用户信息
+ .userId(userId).userType(userType).toMail(toMail)
+ .accountId(account.getId()).fromMail(account.getMail())
+ // 模板相关字段
+ .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())
+ .templateTitle(template.getTitle()).templateContent(templateContent).templateParams(templateParams);
+
+ // 插入数据库
+ MailLogDO logDO = logDOBuilder.build();
+ mailLogMapper.insert(logDO);
+ return logDO.getId();
+ }
+
+ @Override
+ public void updateMailSendResult(Long logId, String messageId, Exception exception) {
+ // 1. 成功
+ if (exception == null) {
+ mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(new Date())
+ .setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()).setSendMessageId(messageId));
+ return;
+ }
+ // 2. 失败
+ mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(new Date())
+ .setSendStatus(MailSendStatusEnum.FAILURE.getStatus()).setSendException(getRootCauseMessage(exception)));
+
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
new file mode 100644
index 000000000..898816868
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+
+import java.util.Map;
+
+/**
+ * 邮件发送 Service 接口
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+public interface MailSendService {
+
+ /**
+ * 发送单条邮件给管理后台的用户
+ *
+ * @param mail 邮箱
+ * @param userId 用户编码
+ * @param templateCode 邮件模版编码
+ * @param templateParams 邮件模版参数
+ * @return 发送日志编号
+ */
+ Long sendSingleMailToAdmin(String mail, Long userId,
+ String templateCode, Map templateParams);
+
+ /**
+ * 发送单条邮件给用户 APP 的用户
+ *
+ * @param mail 邮箱
+ * @param userId 用户编码
+ * @param templateCode 邮件模版编码
+ * @param templateParams 邮件模版参数
+ * @return 发送日志编号
+ */
+ Long sendSingleMailToMember(String mail, Long userId,
+ String templateCode, Map templateParams);
+
+ /**
+ * 发送单条邮件给用户
+ *
+ * @param mail 邮箱
+ * @param userId 用户编码
+ * @param userType 用户类型
+ * @param templateCode 邮件模版编码
+ * @param templateParams 邮件模版参数
+ * @return 发送日志编号
+ */
+ Long sendSingleMail(String mail, Long userId, Integer userType,
+ String templateCode, Map templateParams);
+
+ /**
+ * 执行真正的邮件发送
+ * 注意,该方法仅仅提供给 MQ Consumer 使用
+ *
+ * @param message 邮件
+ */
+ void doSendMail(MailSendMessage message);
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
new file mode 100644
index 000000000..57fbf5c29
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImpl.java
@@ -0,0 +1,172 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.mail.MailAccount;
+import cn.hutool.extra.mail.MailUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import cn.iocoder.yudao.module.system.service.member.MemberService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import com.google.common.annotations.VisibleForTesting;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+
+/**
+ * 邮箱模版 服务实现类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@Service
+@Validated
+@Slf4j
+public class MailSendServiceImpl implements MailSendService {
+
+ @Resource
+ private AdminUserService adminUserService;
+ @Resource
+ private MemberService memberService;
+
+ @Resource
+ private MailAccountService mailAccountService;
+ @Resource
+ private MailTemplateService mailTemplateService;
+
+ @Resource
+ private MailLogService mailLogService;
+ @Resource
+ private MailProducer mailProducer;
+
+ @Override
+ public Long sendSingleMailToAdmin(String mail, Long userId,
+ String templateCode, Map templateParams) {
+ // 如果 mail 为空,则加载用户编号对应的邮箱
+ if (StrUtil.isEmpty(mail)) {
+ AdminUserDO user = adminUserService.getUser(userId);
+ if (user != null) {
+ mail = user.getEmail();
+ }
+ }
+ // 执行发送
+ return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);
+ }
+
+ @Override
+ public Long sendSingleMailToMember(String mail, Long userId,
+ String templateCode, Map templateParams) {
+ // 如果 mail 为空,则加载用户编号对应的邮箱
+ if (StrUtil.isEmpty(mail)) {
+ mail = memberService.getMemberUserEmail(userId);
+ }
+ // 执行发送
+ return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);
+ }
+
+ @Override
+ public Long sendSingleMail(String mail, Long userId, Integer userType,
+ String templateCode, Map templateParams) {
+ // 校验邮箱模版是否合法
+ MailTemplateDO template = checkMailTemplateValid(templateCode);
+ // 校验邮箱账号是否合法
+ MailAccountDO account = checkMailAccountValid(template.getAccountId());
+
+ // 校验邮箱是否存在
+ mail = checkMail(mail);
+ // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
+ List> newTemplateParams = buildTemplateParams(template, templateParams);
+
+ // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志
+ Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());
+ String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);
+ Long sendLogId = mailLogService.createMailLog(userId, userType, mail,
+ account, template, content, templateParams, isSend);
+ // 发送 MQ 消息,异步执行发送短信
+ if (isSend) {
+ mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(),
+ template.getNickname(), template.getTitle(), content);
+ }
+ return sendLogId;
+ }
+
+ @Override
+ public void doSendMail(MailSendMessage message) {
+ // 1. 创建发送账号
+ MailAccountDO account = checkMailAccountValid(message.getAccountId());
+ MailAccount mailAccount = MailAccountConvert.INSTANCE.convert(account, message.getNickname());
+ // 2. 发送邮件
+ try {
+ String messageId = MailUtil.send(mailAccount, message.getMail(),
+ message.getTitle(), message.getContent(),true);
+ // 3. 更新结果(成功)
+ mailLogService.updateMailSendResult(message.getLogId(), messageId, null);
+ } catch (Exception e) {
+ // 3. 更新结果(异常)
+ mailLogService.updateMailSendResult(message.getLogId(), null, e);
+ }
+ }
+
+ @VisibleForTesting
+ public MailTemplateDO checkMailTemplateValid(String templateCode) {
+ // 获得邮件模板。考虑到效率,从缓存中获取
+ MailTemplateDO template = mailTemplateService.getMailTemplateByCodeFromCache(templateCode);
+ // 邮件模板不存在
+ if (template == null) {
+ throw exception(MAIL_TEMPLATE_NOT_EXISTS);
+ }
+ return template;
+ }
+
+ @VisibleForTesting
+ public MailAccountDO checkMailAccountValid(Long accountId) {
+ // 获得邮箱账号。考虑到效率,从缓存中获取
+ MailAccountDO account = mailAccountService.getMailAccountFromCache(accountId);
+ // 邮箱账号不存在
+ if (account == null) {
+ throw exception(MAIL_ACCOUNT_NOT_EXISTS);
+ }
+ return account;
+ }
+
+ @VisibleForTesting
+ public String checkMail(String mail) {
+ if (StrUtil.isEmpty(mail)) {
+ throw exception(MAIL_SEND_MAIL_NOT_EXISTS);
+ }
+ return mail;
+ }
+
+ /**
+ * 将参数模板,处理成有序的 KeyValue 数组
+ *
+ * @param template 邮箱模板
+ * @param templateParams 原始参数
+ * @return 处理后的参数
+ */
+ @VisibleForTesting
+ public List> buildTemplateParams(MailTemplateDO template, Map templateParams) {
+ return template.getParams().stream().map(key -> {
+ Object value = templateParams.get(key);
+ if (value == null) {
+ throw exception(MAIL_SEND_TEMPLATE_PARAM_MISS, key);
+ }
+ return new KeyValue<>(key, value);
+ }).collect(Collectors.toList());
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java
new file mode 100644
index 000000000..cb9dc61ef
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 邮件模版 Service 接口
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+public interface MailTemplateService {
+
+ /**
+ * 初始化邮件模版的本地缓存
+ */
+ void initLocalCache();
+
+ /**
+ * 邮件模版创建
+ *
+ * @param createReqVO 邮件信息
+ * @return 编号
+ */
+ Long createMailTemplate(@Valid MailTemplateCreateReqVO createReqVO);
+
+ /**
+ * 邮件模版修改
+ *
+ * @param updateReqVO 邮件信息
+ */
+ void updateMailTemplate(@Valid MailTemplateUpdateReqVO updateReqVO);
+
+ /**
+ * 邮件模版删除
+ *
+ * @param id 编号
+ */
+ void deleteMailTemplate(Long id);
+
+ /**
+ * 获取邮件模版
+ *
+ * @param id 编号
+ * @return 邮件模版
+ */
+ MailTemplateDO getMailTemplate(Long id);
+
+ /**
+ * 获取邮件模版分页
+ *
+ * @param pageReqVO 模版信息
+ * @return 邮件模版分页信息
+ */
+ PageResult getMailTemplatePage(MailTemplatePageReqVO pageReqVO);
+
+ /**
+ * 获取邮件模板数组
+ *
+ * @return 模版数组
+ */
+ List getMailTemplateList();
+
+ /**
+ * 从缓存中获取邮件模版
+ *
+ * @param code 模板编码
+ * @return 邮件模板
+ */
+ MailTemplateDO getMailTemplateByCodeFromCache(String code);
+
+ /**
+ * 邮件模版内容合成
+ *
+ * @param content 邮件模版
+ * @param params 合成参数
+ * @return 格式化后的内容
+ */
+ String formatMailTemplateContent(String content, Map params);
+
+ /**
+ * 获得指定邮件账号下的邮件模板数量
+ *
+ * @param accountId 账号编号
+ * @return 数量
+ */
+ long countByAccountId(Long accountId);
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
new file mode 100644
index 000000000..62baa7e83
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImpl.java
@@ -0,0 +1,163 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import com.google.common.annotations.VisibleForTesting;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+
+/**
+ * 邮箱模版 Service 实现类
+ *
+ * @author wangjingyi
+ * @since 2022-03-21
+ */
+@Service
+@Validated
+@Slf4j
+public class MailTemplateServiceImpl implements MailTemplateService {
+
+ /**
+ * 正则表达式,匹配 {} 中的变量
+ */
+ private static final Pattern PATTERN_PARAMS = Pattern.compile("\\{(.*?)}");
+
+ @Resource
+ private MailTemplateMapper mailTemplateMapper;
+
+ @Resource
+ private MailProducer mailProducer;
+
+ /**
+ * 邮件模板缓存
+ * key:邮件模版标识 {@link MailTemplateDO#getCode()}
+ *
+ * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
+ */
+ @Getter
+ private volatile Map mailTemplateCache;
+
+ @Override
+ @PostConstruct
+ public void initLocalCache() {
+ // 第一步:查询数据
+ List templates = mailTemplateMapper.selectList();
+ log.info("[initLocalCache][缓存邮件模版,数量:{}]", templates.size());
+
+ // 第二步:构建缓存
+ mailTemplateCache = convertMap(templates, MailTemplateDO::getCode);
+ }
+
+ @Override
+ public Long createMailTemplate(MailTemplateCreateReqVO createReqVO) {
+ // 校验 code 是否唯一
+ validateCodeUnique(null, createReqVO.getCode());
+
+ // 插入
+ MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO)
+ .setParams(parseTemplateContentParams(createReqVO.getContent()));
+ mailTemplateMapper.insert(template);
+ // 发送刷新消息
+ mailProducer.sendMailTemplateRefreshMessage();
+ return template.getId();
+ }
+
+ @Override
+ public void updateMailTemplate(@Valid MailTemplateUpdateReqVO updateReqVO) {
+ // 校验是否存在
+ validateMailTemplateExists(updateReqVO.getId());
+ // 校验 code 是否唯一
+ validateCodeUnique(updateReqVO.getId(),updateReqVO.getCode());
+
+ // 更新
+ MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO)
+ .setParams(parseTemplateContentParams(updateReqVO.getContent()));
+ mailTemplateMapper.updateById(updateObj);
+ // 发送刷新消息
+ mailProducer.sendMailTemplateRefreshMessage();
+ }
+
+ @VisibleForTesting
+ public void validateCodeUnique(Long id, String code) {
+ MailTemplateDO template = mailTemplateMapper.selectByCode(code);
+ if (template == null) {
+ return;
+ }
+ // 存在 template 记录的情况下
+ if (id == null // 新增时,说明重复
+ || ObjUtil.notEqual(id, template.getId())) { // 更新时,如果 id 不一致,说明重复
+ throw exception(MAIL_TEMPLATE_CODE_EXISTS);
+ }
+ }
+
+ @Override
+ public void deleteMailTemplate(Long id) {
+ // 校验是否存在
+ validateMailTemplateExists(id);
+
+ // 删除
+ mailTemplateMapper.deleteById(id);
+ // 发送刷新消息
+ mailProducer.sendMailTemplateRefreshMessage();
+ }
+
+ private void validateMailTemplateExists(Long id) {
+ if (mailTemplateMapper.selectById(id) == null) {
+ throw exception(MAIL_TEMPLATE_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}
+
+ @Override
+ public PageResult getMailTemplatePage(MailTemplatePageReqVO pageReqVO) {
+ return mailTemplateMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getMailTemplateList() {return mailTemplateMapper.selectList();}
+
+ @Override
+ public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
+ return mailTemplateCache.get(code);
+ }
+
+ @Override
+ public String formatMailTemplateContent(String content, Map params) {
+ return StrUtil.format(content, params);
+ }
+
+ @VisibleForTesting
+ public List parseTemplateContentParams(String content) {
+ return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
+ }
+
+ @Override
+ public long countByAccountId(Long accountId) {
+ return mailTemplateMapper.selectCountByAccountId(accountId);
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberService.java
index d96352b15..933e5bfc0 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberService.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberService.java
@@ -15,4 +15,12 @@ public interface MemberService {
*/
String getMemberUserMobile(Long id);
+ /**
+ * 获得会员用户的邮箱
+ *
+ * @param id 会员用户编号
+ * @return 邮箱
+ */
+ String getMemberUserEmail(Long id);
+
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberServiceImpl.java
index 676a95a4e..7b31e0456 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/member/MemberServiceImpl.java
@@ -21,16 +21,29 @@ public class MemberServiceImpl implements MemberService {
@Override
public String getMemberUserMobile(Long id) {
- if (id == null) {
- return null;
- }
- Object user = ReflectUtil.invoke(getMemberUserApi(), "getUser", id);
+ Object user = getMemberUser(id);
if (user == null) {
return null;
}
return ReflectUtil.invoke(user, "getMobile");
}
+ @Override
+ public String getMemberUserEmail(Long id) {
+ Object user = getMemberUser(id);
+ if (user == null) {
+ return null;
+ }
+ return ReflectUtil.invoke(user, "getEmail");
+ }
+
+ private Object getMemberUser(Long id) {
+ if (id == null) {
+ return null;
+ }
+ return ReflectUtil.invoke(getMemberUserApi(), "getUser", id);
+ }
+
private Object getMemberUserApi() {
if (memberUserApi == null) {
memberUserApi = SpringUtil.getBean(ClassUtil.loadClass(String.format("%s.module.member.api.user.MemberUserApi", basePackage)));
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
index d9cadd1aa..970387bbb 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
@@ -17,20 +17,17 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
@@ -43,12 +40,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class OAuth2ClientServiceImpl implements OAuth2ClientService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 客户端缓存
* key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
@@ -58,11 +49,6 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
@Getter // 解决单测
@Setter // 解决单测
private volatile Map clientCache;
- /**
- * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime maxUpdateTime;
@Resource
private OAuth2ClientMapper oauth2ClientMapper;
@@ -76,37 +62,12 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List clients = oauth2ClientMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存 OAuth2 客户端,数量为:{}]", clients.size());
+ log.info("[initLocalCache][缓存 OAuth2 客户端,数量为:{}]", clients.size());
// 第二步:构建缓存。
clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = getMaxValue(clients, OAuth2ClientDO::getUpdateTime);
}
@Override
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
index 1f9fee802..32466d592 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
@@ -18,9 +18,9 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -28,7 +28,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -43,18 +42,13 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class MenuServiceImpl implements MenuService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 菜单缓存
* key:菜单编号
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
+ @Getter
private volatile Map menuCache;
/**
* 权限与菜单缓存
@@ -63,11 +57,8 @@ public class MenuServiceImpl implements MenuService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
+ @Getter
private volatile Multimap permissionMenuCache;
- /**
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- private volatile LocalDateTime maxUpdateTime;
@Resource
private MenuMapper menuMapper;
@@ -86,33 +77,11 @@ public class MenuServiceImpl implements MenuService {
@Override
@PostConstruct
public synchronized void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List menuList = menuMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
+ log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
- // 第二步:构建缓存。
+ // 第二步:构建缓存
ImmutableMap.Builder menuCacheBuilder = ImmutableMap.builder();
ImmutableMultimap.Builder permMenuCacheBuilder = ImmutableMultimap.builder();
menuList.forEach(menuDO -> {
@@ -123,9 +92,6 @@ public class MenuServiceImpl implements MenuService {
});
menuCache = menuCacheBuilder.build();
permissionMenuCache = permMenuCacheBuilder.build();
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
}
@Override
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
index bc4d51b8e..c40a8e31c 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java
@@ -16,9 +16,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
-import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
-import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
@@ -32,7 +30,6 @@ import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -40,12 +37,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static java.util.Collections.singleton;
/**
@@ -57,12 +52,6 @@ import static java.util.Collections.singleton;
@Slf4j
public class PermissionServiceImpl implements PermissionService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 角色编号与菜单编号的缓存映射
* key:角色编号
@@ -83,11 +72,6 @@ public class PermissionServiceImpl implements PermissionService {
@Getter
@Setter // 单元测试需要
private volatile Multimap menuRoleCache;
- /**
- * 缓存 RoleMenu 的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime roleMenuMaxUpdateTime;
/**
* 用户编号与角色编号的缓存映射
@@ -99,20 +83,11 @@ public class PermissionServiceImpl implements PermissionService {
@Getter
@Setter // 单元测试需要
private volatile Map> userRoleCache;
- /**
- * 缓存 UserRole 的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime userRoleMaxUpdateTime;
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
- private RoleMenuBatchInsertMapper roleMenuBatchInsertMapper;
- @Resource
private UserRoleMapper userRoleMapper;
- @Resource
- private UserRoleBatchInsertMapper userRoleBatchInsertMapper;
@Resource
private RoleService roleService;
@@ -129,38 +104,22 @@ public class PermissionServiceImpl implements PermissionService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdateForRoleMenu(null);
- initLocalCacheIfUpdateForUserRole(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
- initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
+ initLocalCacheForRoleMenu();
+ initLocalCacheForUserRole();
}
/**
* 刷新 RoleMenu 本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
*/
@VisibleForTesting
- void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
+ void initLocalCacheForRoleMenu() {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List roleMenus = roleMenuMapper.selectList();
- log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
+ log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
- // 第二步:构建缓存。
+ // 第二步:构建缓存
ImmutableMultimap.Builder roleMenuCacheBuilder = ImmutableMultimap.builder();
ImmutableMultimap.Builder menuRoleCacheBuilder = ImmutableMultimap.builder();
roleMenus.forEach(roleMenuDO -> {
@@ -169,40 +128,24 @@ public class PermissionServiceImpl implements PermissionService {
});
roleMenuCache = roleMenuCacheBuilder.build();
menuRoleCache = menuRoleCacheBuilder.build();
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.roleMenuMaxUpdateTime = getMaxValue(roleMenus, RoleMenuDO::getUpdateTime);
});
}
/**
* 刷新 UserRole 本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
*/
@VisibleForTesting
- void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
+ void initLocalCacheForUserRole() {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdateForUserRole][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:加载数据
List userRoles = userRoleMapper.selectList();
- log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
+ log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
// 第二步:构建缓存。
ImmutableMultimap.Builder userRoleCacheBuilder = ImmutableMultimap.builder();
userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.userRoleMaxUpdateTime = getMaxValue(userRoles, UserRoleDO::getUpdateTime);
});
}
@@ -264,7 +207,7 @@ public class PermissionServiceImpl implements PermissionService {
Collection deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
// 执行新增和删除。对于已经授权的菜单,不用做任何处理
if (!CollectionUtil.isEmpty(createMenuIds)) {
- roleMenuBatchInsertMapper.saveBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
+ roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
RoleMenuDO entity = new RoleMenuDO();
entity.setRoleId(roleId);
entity.setMenuId(menuId);
@@ -308,7 +251,7 @@ public class PermissionServiceImpl implements PermissionService {
Collection deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIds);
// 执行新增和删除。对于已经授权的角色,不用做任何处理
if (!CollectionUtil.isEmpty(createRoleIds)) {
- userRoleBatchInsertMapper.saveBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
+ userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
UserRoleDO entity = new UserRoleDO();
entity.setUserId(userId);
entity.setRoleId(roleId);
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
index f5569f855..3f9ee1351 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
@@ -22,7 +22,6 @@ import com.google.common.annotations.VisibleForTesting;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
@@ -31,7 +30,6 @@ import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -47,12 +45,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@Slf4j
public class RoleServiceImpl implements RoleService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 角色缓存
* key:角色编号 {@link RoleDO#getId()}
@@ -61,11 +53,6 @@ public class RoleServiceImpl implements RoleService {
*/
@Getter
private volatile Map roleCache;
- /**
- * 缓存角色的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime maxUpdateTime;
@Resource
private PermissionService permissionService;
@@ -82,39 +69,14 @@ public class RoleServiceImpl implements RoleService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
// 注意:忽略自动多租户,因为要全局初始化缓存
TenantUtils.executeIgnore(() -> {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List roleList = roleMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存角色,数量为:{}]", roleList.size());
+ log.info("[initLocalCache][缓存角色,数量为:{}]", roleList.size());
- // 第二步:构建缓存。
+ // 第二步:构建缓存
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
});
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
index cc7975bfa..53357753f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
@@ -17,13 +17,11 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -40,12 +38,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SENSITIVE_
@Validated
public class SensitiveWordServiceImpl implements SensitiveWordService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 敏感词标签缓存
* key:敏感词编号 {@link SensitiveWordDO#getId()}
@@ -55,12 +47,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Getter
private volatile Set sensitiveWordTagsCache = Collections.emptySet();
- /**
- * 缓存敏感词的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- @Getter
- private volatile LocalDateTime maxUpdateTime;
-
@Resource
private SensitiveWordMapper sensitiveWordMapper;
@@ -84,42 +70,17 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List sensitiveWords = sensitiveWordMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存敏感词,数量为:{}]", sensitiveWords.size());
+ log.info("[initLocalCache][缓存敏感词,数量为:{}]", sensitiveWords.size());
- // 第二步:构建缓存。
+ // 第二步:构建缓存
// 写入 sensitiveWordTagsCache 缓存
Set tags = new HashSet<>();
sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
sensitiveWordTagsCache = tags;
// 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
initSensitiveWordTrie(sensitiveWords);
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWords, SensitiveWordDO::getUpdateTime);
}
private void initSensitiveWordTrie(List wordDOs) {
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
index 50eeb4910..d126fbf0f 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java
@@ -11,17 +11,14 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
@@ -34,17 +31,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
@Slf4j
public class SmsChannelServiceImpl implements SmsChannelService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
- /**
- * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- private volatile LocalDateTime maxUpdateTime;
-
@Resource
private SmsClientFactory smsClientFactory;
@@ -60,38 +46,13 @@ public class SmsChannelServiceImpl implements SmsChannelService {
@Override
@PostConstruct
public void initLocalCache() {
- initLocalCacheIfUpdate(null);
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCacheIfUpdate(this.maxUpdateTime);
- }
-
- /**
- * 刷新本地缓存
- *
- * @param maxUpdateTime 最大更新时间
- * 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
- * 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
- */
- private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步:基于 maxUpdateTime 判断缓存是否刷新。
- // 如果没有增量的数据变化,则不进行本地缓存的刷新
- if (maxUpdateTime != null
- && smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
- return;
- }
+ // 第一步:查询数据
List channels = smsChannelMapper.selectList();
- log.info("[initLocalCacheIfUpdate][缓存短信渠道,数量为:{}]", channels.size());
+ log.info("[initLocalCache][缓存短信渠道,数量为:{}]", channels.size());
- // 第二步:构建缓存。创建或更新短信 Client
+ // 第二步:构建缓存:创建或更新短信 Client
List propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
-
- // 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
- this.maxUpdateTime = getMaxValue(channels, SmsChannelDO::getUpdateTime);
}
@Override
@@ -120,7 +81,7 @@ public class SmsChannelServiceImpl implements SmsChannelService {
public void deleteSmsChannel(Long id) {
// 校验存在
this.validateSmsChannelExists(id);
- // 校验是否有字典数据
+ // 校验是否有在使用该账号的模版
if (smsTemplateService.countByChannelId(id) > 0) {
throw exception(SMS_CHANNEL_HAS_CHILDREN);
}
diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
index 9f3ad2dde..031edb7cc 100644
--- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
+++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceImpl.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.service.sms;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -20,15 +19,17 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import com.google.common.annotations.VisibleForTesting;
+import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.regex.Pattern;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -38,18 +39,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
* 短信模板 Service 实现类
*
* @author zzf
- * @date 2021/1/25 9:25
+ * @since 2021/1/25 9:25
*/
@Service
@Slf4j
public class SmsTemplateServiceImpl implements SmsTemplateService {
- /**
- * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
- * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
- */
- private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
/**
* 正则表达式,匹配 {} 中的变量
*/
@@ -73,51 +68,18 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
*
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
*/
+ @Getter // 为了方便测试,这里提供 getter 方法
private volatile Map smsTemplateCache;
- /**
- * 缓存短信模板的最大更新时间,用于后续的增量轮询,判断是否有更新
- */
- private volatile LocalDateTime maxUpdateTime;
@Override
@PostConstruct
public void initLocalCache() {
- // 获取短信模板列表,如果有更新
- List smsTemplateList = this.loadSmsTemplateIfUpdate(maxUpdateTime);
- if (CollUtil.isEmpty(smsTemplateList)) {
- return;
- }
+ // 第一步:查询数据
+ List smsTemplateList = smsTemplateMapper.selectList();
+ log.info("[initLocalCache][缓存短信模版,数量为:{}]", smsTemplateList.size());
- // 写入缓存
+ // 第二步:构建缓存
smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
- maxUpdateTime = CollectionUtils.getMaxValue(smsTemplateList, SmsTemplateDO::getUpdateTime);
- log.info("[initLocalCache][初始化 SmsTemplate 数量为 {}]", smsTemplateList.size());
- }
-
- /**
- * 如果短信模板发生变化,从数据库中获取最新的全量短信模板。
- * 如果未发生变化,则返回空
- *
- * @param maxUpdateTime 当前短信模板的最大更新时间
- * @return 短信模板列表
- */
- private List loadSmsTemplateIfUpdate(LocalDateTime maxUpdateTime) {
- // 第一步,判断是否要更新。
- if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
- log.info("[loadSmsTemplateIfUpdate][首次加载全量短信模板]");
- } else { // 判断数据库中是否有更新的短信模板
- if (smsTemplateMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
- return null;
- }
- log.info("[loadSmsTemplateIfUpdate][增量加载全量短信模板]");
- }
- // 第二步,如果有更新,则从数据库加载所有短信模板
- return smsTemplateMapper.selectList();
- }
-
- @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
- public void schedulePeriodicRefresh() {
- initLocalCache();
}
@Override
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceTest.java
index e17187ad6..6b4ff1028 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceTest.java
@@ -1,7 +1,10 @@
package cn.iocoder.yudao.module.system.service.dept;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
@@ -9,9 +12,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
-import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import com.google.common.collect.Multimap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -19,17 +19,15 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
-import static cn.hutool.core.bean.BeanUtil.getFieldValue;
import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -56,8 +54,7 @@ public class DeptServiceTest extends BaseDbUnitTest {
}
@Test
- @SuppressWarnings("unchecked")
- void testInitLocalCache() {
+ public void testInitLocalCache() {
// mock 数据
DeptDO deptDO1 = randomDeptDO();
deptMapper.insert(deptDO1);
@@ -67,18 +64,15 @@ public class DeptServiceTest extends BaseDbUnitTest {
// 调用
deptService.initLocalCache();
// 断言 deptCache 缓存
- Map deptCache = (Map) getFieldValue(deptService, "deptCache");
+ Map deptCache = deptService.getDeptCache();
assertEquals(2, deptCache.size());
assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
// 断言 parentDeptCache 缓存
- Multimap parentDeptCache = (Multimap) getFieldValue(deptService, "parentDeptCache");
+ Multimap parentDeptCache = deptService.getParentDeptCache();
assertEquals(2, parentDeptCache.size());
assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
- // 断言 maxUpdateTime 缓存
- LocalDateTime maxUpdateTime = (LocalDateTime) getFieldValue(deptService, "maxUpdateTime");
- assertEquals(ObjectUtils.max(deptDO1.getUpdateTime(), deptDO2.getUpdateTime()), maxUpdateTime);
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java
new file mode 100755
index 000000000..ffca4b1b6
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailAccountServiceImplTest.java
@@ -0,0 +1,154 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.verify;
+
+/**
+* {@link MailAccountServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(MailAccountServiceImpl.class)
+public class MailAccountServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MailAccountServiceImpl mailAccountService;
+
+ @Resource
+ private MailAccountMapper mailAccountMapper;
+
+ @MockBean
+ private MailTemplateService mailTemplateService;
+ @MockBean
+ private MailProducer mailProducer;
+
+ @Test
+ public void testInitLocalCache() {
+ MailAccountDO accountDO1 = randomPojo(MailAccountDO.class);
+ mailAccountMapper.insert(accountDO1);
+ MailAccountDO accountDO02 = randomPojo(MailAccountDO.class);
+ mailAccountMapper.insert(accountDO02);
+
+ // 调用
+ mailAccountService.initLocalCache();
+ // 断言 mailAccountCache 缓存
+ Map mailAccountCache = mailAccountService.getMailAccountCache();
+ assertPojoEquals(accountDO1, mailAccountCache.get(accountDO1.getId()));
+ assertPojoEquals(accountDO02, mailAccountCache.get(accountDO02.getId()));
+ }
+
+ @Test
+ public void testCreateMailAccount_success() {
+ // 准备参数
+ MailAccountCreateReqVO reqVO = randomPojo(MailAccountCreateReqVO.class, o -> o.setMail(randomEmail()));
+
+ // 调用
+ Long mailAccountId = mailAccountService.createMailAccount(reqVO);
+ // 断言
+ assertNotNull(mailAccountId);
+ // 校验记录的属性是否正确
+ MailAccountDO mailAccount = mailAccountMapper.selectById(mailAccountId);
+ assertPojoEquals(reqVO, mailAccount);
+ verify(mailProducer).sendMailAccountRefreshMessage();
+ }
+
+ @Test
+ public void testUpdateMailAccount_success() {
+ // mock 数据
+ MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);
+ mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ MailAccountUpdateReqVO reqVO = randomPojo(MailAccountUpdateReqVO.class, o -> {
+ o.setId(dbMailAccount.getId()); // 设置更新的 ID
+ o.setMail(randomEmail());
+ });
+
+ // 调用
+ mailAccountService.updateMailAccount(reqVO);
+ // 校验是否更新正确
+ MailAccountDO mailAccount = mailAccountMapper.selectById(reqVO.getId()); // 获取最新的
+ assertPojoEquals(reqVO, mailAccount);
+ verify(mailProducer).sendMailAccountRefreshMessage();
+ }
+
+ @Test
+ public void testUpdateMailAccount_notExists() {
+ // 准备参数
+ MailAccountUpdateReqVO reqVO = randomPojo(MailAccountUpdateReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> mailAccountService.updateMailAccount(reqVO), MAIL_ACCOUNT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteMailAccount_success() {
+ // mock 数据
+ MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);
+ mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbMailAccount.getId();
+
+ // 调用
+ mailAccountService.deleteMailAccount(id);
+ // 校验数据不存在了
+ assertNull(mailAccountMapper.selectById(id));
+ verify(mailProducer).sendMailAccountRefreshMessage();
+ }
+
+ @Test
+ public void testDeleteMailAccount_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> mailAccountService.deleteMailAccount(id), MAIL_ACCOUNT_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGetMailAccountPage() {
+ // mock 数据
+ MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
+ o.setMail("768@qq.com");
+ o.setUsername("yunai");
+ });
+ mailAccountMapper.insert(dbMailAccount);
+ // 测试 mail 不匹配
+ mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
+ // 测试 username 不匹配
+ mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
+ // 准备参数
+ MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
+ reqVO.setMail("768");
+ reqVO.setUsername("yu");
+
+ // 调用
+ PageResult pageResult = mailAccountService.getMailAccountPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
new file mode 100755
index 000000000..ccf4ba737
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailLogServiceImplTest.java
@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailLogDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailLogMapper;
+import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link MailLogServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(MailLogServiceImpl.class)
+public class MailLogServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MailLogServiceImpl mailLogService;
+
+ @Resource
+ private MailLogMapper mailLogMapper;
+
+ @Test
+ public void testCreateMailLog() {
+ // 准备参数
+ Long userId = randomLongId();
+ Integer userType = randomEle(UserTypeEnum.values()).getValue();
+ String toMail = randomEmail();
+ MailAccountDO account = randomPojo(MailAccountDO.class);
+ MailTemplateDO template = randomPojo(MailTemplateDO.class);
+ String templateContent = randomString();
+ Map templateParams = randomTemplateParams();
+ Boolean isSend = true;
+ // mock 方法
+
+ // 调用
+ Long logId = mailLogService.createMailLog(userId, userType, toMail, account, template, templateContent, templateParams, isSend);
+ // 断言
+ MailLogDO log = mailLogMapper.selectById(logId);
+ assertNotNull(log);
+ assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus());
+ assertEquals(userId, log.getUserId());
+ assertEquals(userType, log.getUserType());
+ assertEquals(toMail, log.getToMail());
+ assertEquals(account.getId(), log.getAccountId());
+ assertEquals(account.getMail(), log.getFromMail());
+ assertEquals(template.getId(), log.getTemplateId());
+ assertEquals(template.getCode(), log.getTemplateCode());
+ assertEquals(template.getNickname(), log.getTemplateNickname());
+ assertEquals(template.getTitle(), log.getTemplateTitle());
+ assertEquals(templateContent, log.getTemplateContent());
+ assertEquals(templateParams, log.getTemplateParams());
+ }
+
+ @Test
+ public void testUpdateMailSendResult_success() {
+ // mock 数据
+ MailLogDO log = randomPojo(MailLogDO.class, o -> {
+ o.setSendStatus(MailSendStatusEnum.INIT.getStatus());
+ o.setSendTime(null).setSendMessageId(null).setSendException(null)
+ .setTemplateParams(randomTemplateParams());
+ });
+ mailLogMapper.insert(log);
+ // 准备参数
+ Long logId = log.getId();
+ String messageId = randomString();
+
+ // 调用
+ mailLogService.updateMailSendResult(logId, messageId, null);
+ // 断言
+ MailLogDO dbLog = mailLogMapper.selectById(logId);
+ assertEquals(MailSendStatusEnum.SUCCESS.getStatus(), dbLog.getSendStatus());
+ assertNotNull(dbLog.getSendTime());
+ assertEquals(messageId, dbLog.getSendMessageId());
+ assertNull(dbLog.getSendException());
+ }
+
+ @Test
+ public void testUpdateMailSendResult_exception() {
+ // mock 数据
+ MailLogDO log = randomPojo(MailLogDO.class, o -> {
+ o.setSendStatus(MailSendStatusEnum.INIT.getStatus());
+ o.setSendTime(null).setSendMessageId(null).setSendException(null)
+ .setTemplateParams(randomTemplateParams());
+ });
+ mailLogMapper.insert(log);
+ // 准备参数
+ Long logId = log.getId();
+ Exception exception = new NullPointerException("测试异常");
+
+ // 调用
+ mailLogService.updateMailSendResult(logId, null, exception);
+ // 断言
+ MailLogDO dbLog = mailLogMapper.selectById(logId);
+ assertEquals(MailSendStatusEnum.FAILURE.getStatus(), dbLog.getSendStatus());
+ assertNotNull(dbLog.getSendTime());
+ assertNull(dbLog.getSendMessageId());
+ assertEquals("NullPointerException: 测试异常", dbLog.getSendException());
+ }
+
+ @Test
+ public void testGetMailLogPage() {
+ // mock 数据
+ MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到
+ o.setUserId(1L);
+ o.setUserType(UserTypeEnum.ADMIN.getValue());
+ o.setToMail("768@qq.com");
+ 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())));
+ // 测试 toMail 不匹配
+ mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMail("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");
+ 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));
+ }
+
+ private static Map randomTemplateParams() {
+ return MapUtil.builder().put(randomString(), randomString())
+ .put(randomString(), randomString()).build();
+ }
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
new file mode 100644
index 000000000..bd93cd00a
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailSendServiceImplTest.java
@@ -0,0 +1,170 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.mail.MailAccount;
+import cn.hutool.extra.mail.MailUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+class MailSendServiceImplTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ private MailSendServiceImpl mailSendService;
+
+ @Mock
+ private MailAccountService mailAccountService;
+ @Mock
+ private MailTemplateService mailTemplateService;
+ @Mock
+ private MailLogService mailLogService;
+ @Mock
+ private MailProducer mailProducer;
+
+ /**
+ * 用于快速测试你的邮箱账号是否正常
+ */
+ @Test
+ @Disabled
+ public void testDemo() {
+ MailAccount mailAccount = new MailAccount()
+// .setFrom("奥特曼 ")
+ .setFrom("ydym_test@163.com") // 邮箱地址
+ .setHost("smtp.163.com").setPort(465).setSslEnable(true) // SMTP 服务器
+ .setAuth(true).setUser("ydym_test@163.com").setPass("WBZTEINMIFVRYSOE"); // 登录账号密码
+ String messageId = MailUtil.send(mailAccount, "7685413@qq.com", "主题", "内容", false);
+ System.out.println("发送结果:" + messageId);
+ }
+
+ /**
+ * 发送成功,当短信模板开启时
+ */
+ @Test
+ public void testSendSingleMail_successWhenMailTemplateEnable() {
+ // 准备参数
+ String mail = randomEmail();
+ Long userId = randomLongId();
+ Integer userType = randomEle(UserTypeEnum.values()).getValue();
+ String templateCode = randomString();
+ Map templateParams = MapUtil.builder().put("code", "1234")
+ .put("op", "login").build();
+ // mock MailTemplateService 的方法
+ MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
+ o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ o.setContent("验证码为{code}, 操作为{op}");
+ o.setParams(Lists.newArrayList("code", "op"));
+ });
+ when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
+ String content = randomString();
+ when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))
+ .thenReturn(content);
+ // mock MailAccountService 的方法
+ MailAccountDO account = randomPojo(MailAccountDO.class);
+ when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
+ // mock MailLogService 的方法
+ Long mailLogId = randomLongId();
+ when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail),
+ eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);
+
+ // 调用
+ Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);
+ // 断言
+ assertEquals(mailLogId, resultMailLogId);
+ // 断言调用
+ verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail),
+ eq(account.getId()), eq(template.getNickname()), eq(template.getTitle()), eq(content));
+ }
+
+ /**
+ * 发送成功,当短信模板关闭时
+ */
+ @Test
+ public void testSendSingleMail_successWhenSmsTemplateDisable() {
+ // 准备参数
+ String mail = randomEmail();
+ Long userId = randomLongId();
+ Integer userType = randomEle(UserTypeEnum.values()).getValue();
+ String templateCode = randomString();
+ Map templateParams = MapUtil.builder().put("code", "1234")
+ .put("op", "login").build();
+ // mock MailTemplateService 的方法
+ MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {
+ o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+ o.setContent("验证码为{code}, 操作为{op}");
+ o.setParams(Lists.newArrayList("code", "op"));
+ });
+ when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
+ String content = randomString();
+ when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))
+ .thenReturn(content);
+ // mock MailAccountService 的方法
+ MailAccountDO account = randomPojo(MailAccountDO.class);
+ when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);
+ // mock MailLogService 的方法
+ Long mailLogId = randomLongId();
+ when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail),
+ eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId);
+
+ // 调用
+ Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);
+ // 断言
+ assertEquals(mailLogId, resultMailLogId);
+ // 断言调用
+ verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), anyString(),
+ anyLong(), anyString(), anyString(), anyString());
+ }
+
+ @Test
+ public void testCheckMailTemplateValid_notExists() {
+ // 准备参数
+ String templateCode = randomString();
+ // mock 方法
+
+ // 调用,并断言异常
+ assertServiceException(() -> mailSendService.checkMailTemplateValid(templateCode),
+ MAIL_TEMPLATE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testBuildTemplateParams_paramMiss() {
+ // 准备参数
+ MailTemplateDO template = randomPojo(MailTemplateDO.class,
+ o -> o.setParams(Lists.newArrayList("code")));
+ Map templateParams = new HashMap<>();
+ // mock 方法
+
+ // 调用,并断言异常
+ assertServiceException(() -> mailSendService.buildTemplateParams(template, templateParams),
+ MAIL_SEND_TEMPLATE_PARAM_MISS, "code");
+ }
+
+ @Test
+ public void testCheckMail_notExists() {
+ // 准备参数
+ // mock 方法
+
+ // 调用,并断言异常
+ assertServiceException(() -> mailSendService.checkMail(null),
+ MAIL_SEND_MAIL_NOT_EXISTS);
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java
new file mode 100755
index 000000000..ab005822b
--- /dev/null
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateServiceImplTest.java
@@ -0,0 +1,163 @@
+package cn.iocoder.yudao.module.system.service.mail;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
+import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
+import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+* {@link MailTemplateServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(MailTemplateServiceImpl.class)
+public class MailTemplateServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MailTemplateServiceImpl mailTemplateService;
+
+ @Resource
+ private MailTemplateMapper mailTemplateMapper;
+
+ @MockBean
+ private MailProducer mailProducer;
+
+ @Test
+ public void testInitLocalCache() {
+ MailTemplateDO templateDO01 = randomPojo(MailTemplateDO.class);
+ mailTemplateMapper.insert(templateDO01);
+ MailTemplateDO templateDO02 = randomPojo(MailTemplateDO.class);
+ mailTemplateMapper.insert(templateDO02);
+
+ // 调用
+ mailTemplateService.initLocalCache();
+ // 断言 mailTemplateCache 缓存
+ Map mailTemplateCache = mailTemplateService.getMailTemplateCache();
+ assertPojoEquals(templateDO01, mailTemplateCache.get(templateDO01.getCode()));
+ assertPojoEquals(templateDO02, mailTemplateCache.get(templateDO02.getCode()));
+ }
+
+ @Test
+ public void testCreateMailTemplate_success() {
+ // 准备参数
+ MailTemplateCreateReqVO reqVO = randomPojo(MailTemplateCreateReqVO.class);
+
+ // 调用
+ Long mailTemplateId = mailTemplateService.createMailTemplate(reqVO);
+ // 断言
+ assertNotNull(mailTemplateId);
+ // 校验记录的属性是否正确
+ MailTemplateDO mailTemplate = mailTemplateMapper.selectById(mailTemplateId);
+ assertPojoEquals(reqVO, mailTemplate);
+ }
+
+ @Test
+ public void testUpdateMailTemplate_success() {
+ // mock 数据
+ MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);
+ mailTemplateMapper.insert(dbMailTemplate);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ MailTemplateUpdateReqVO reqVO = randomPojo(MailTemplateUpdateReqVO.class, o -> {
+ o.setId(dbMailTemplate.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ mailTemplateService.updateMailTemplate(reqVO);
+ // 校验是否更新正确
+ MailTemplateDO mailTemplate = mailTemplateMapper.selectById(reqVO.getId()); // 获取最新的
+ assertPojoEquals(reqVO, mailTemplate);
+ }
+
+ @Test
+ public void testUpdateMailTemplate_notExists() {
+ // 准备参数
+ MailTemplateUpdateReqVO reqVO = randomPojo(MailTemplateUpdateReqVO.class);
+
+ // 调用, 并断言异常
+ assertServiceException(() -> mailTemplateService.updateMailTemplate(reqVO), MAIL_TEMPLATE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteMailTemplate_success() {
+ // mock 数据
+ MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);
+ mailTemplateMapper.insert(dbMailTemplate);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbMailTemplate.getId();
+
+ // 调用
+ mailTemplateService.deleteMailTemplate(id);
+ // 校验数据不存在了
+ assertNull(mailTemplateMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteMailTemplate_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> mailTemplateService.deleteMailTemplate(id), MAIL_TEMPLATE_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGetMailTemplatePage() {
+ // mock 数据
+ MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
+ o.setName("源码");
+ o.setCode("test_01");
+ o.setAccountId(1L);
+ o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ o.setCreateTime(buildTime(2023, 2, 3));
+ });
+ mailTemplateMapper.insert(dbMailTemplate);
+ // 测试 name 不匹配
+ mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
+ // 测试 code 不匹配
+ mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
+ // 测试 accountId 不匹配
+ mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
+ // 测试 status 不匹配
+ mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+ // 测试 createTime 不匹配
+ mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
+ // 准备参数
+ MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
+ reqVO.setName("源");
+ reqVO.setCode("est_01");
+ reqVO.setAccountId(1L);
+ reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
+
+ // 调用
+ PageResult pageResult = mailTemplateService.getMailTemplatePage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
+ }
+
+}
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java
index c7d4ce578..50401cf97 100755
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImplTest.java
@@ -19,7 +19,6 @@ import java.util.Collections;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@@ -59,8 +58,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
assertEquals(2, clientCache.size());
assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
- // 断言 maxUpdateTime 缓存
- assertEquals(max(clientDO1.getUpdateTime(), clientDO2.getUpdateTime()), oauth2ClientService.getMaxUpdateTime());
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java
index b5d21f031..eae4633b1 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringAopUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
@@ -20,7 +19,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
@@ -51,32 +49,24 @@ public class MenuServiceTest extends BaseDbUnitTest {
private TenantService tenantService;
@Test
- public void testInitLocalCache_success() throws Exception {
- MenuDO menuDO1 = createMenuDO(MenuTypeEnum.MENU, "xxxx", 0L);
+ public void testInitLocalCache_success() {
+ MenuDO menuDO1 = randomPojo(MenuDO.class);
menuMapper.insert(menuDO1);
- MenuDO menuDO2 = createMenuDO(MenuTypeEnum.MENU, "xxxx", 0L);
+ MenuDO menuDO2 = randomPojo(MenuDO.class);
menuMapper.insert(menuDO2);
// 调用
menuService.initLocalCache();
-
- // 获取代理对象
- MenuServiceImpl target = (MenuServiceImpl) SpringAopUtils.getTarget(menuService);
-
- Map menuCache =
- (Map) BeanUtil.getFieldValue(target, "menuCache");
+ // 校验 menuCache 缓存
+ Map menuCache = menuService.getMenuCache();
Assert.isTrue(menuCache.size() == 2);
assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
-
- Multimap permissionMenuCache =
- (Multimap) BeanUtil.getFieldValue(target, "permissionMenuCache");
+ // 校验 permissionMenuCache 缓存
+ Multimap permissionMenuCache = menuService.getPermissionMenuCache();
Assert.isTrue(permissionMenuCache.size() == 2);
assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
-
- LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(target, "maxUpdateTime");
- assertEquals(ObjectUtils.max(menuDO1.getUpdateTime(), menuDO2.getUpdateTime()), maxUpdateTime);
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
index 6352c4593..afc4c0ced 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceTest.java
@@ -3,18 +3,15 @@ package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
-import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
-import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleBatchInsertMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
@@ -27,8 +24,10 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
@@ -42,8 +41,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-@Import({PermissionServiceImpl.class,
- RoleMenuBatchInsertMapper.class, UserRoleBatchInsertMapper.class})
+@Import({PermissionServiceImpl.class})
public class PermissionServiceTest extends BaseDbUnitTest {
@Resource
@@ -52,11 +50,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
- private RoleMenuBatchInsertMapper roleMenuBatchInsertMapper;
- @Resource
private UserRoleMapper userRoleMapper;
- @Resource
- private UserRoleBatchInsertMapper userRoleBatchInsertMapper;
@MockBean
private RoleService roleService;
@@ -71,7 +65,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
private PermissionProducer permissionProducer;
@Test
- public void testInitLocalCacheIfUpdateForRoleMenu() {
+ public void testInitLocalCacheForRoleMenu() {
// mock 数据
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
roleMenuMapper.insert(roleMenuDO01);
@@ -79,7 +73,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
roleMenuMapper.insert(roleMenuDO02);
// 调用
- permissionService.initLocalCacheIfUpdateForRoleMenu(null);
+ permissionService.initLocalCacheForRoleMenu();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
@@ -87,13 +81,10 @@ public class PermissionServiceTest extends BaseDbUnitTest {
assertEquals(2, permissionService.getMenuRoleCache().size());
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
- // 断言 maxUpdateTime 缓存
- LocalDateTime maxUpdateTime = permissionService.getRoleMenuMaxUpdateTime();
- assertEquals(ObjectUtils.max(roleMenuDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test
- public void testInitLocalCacheIfUpdateForUserRole() {
+ public void testInitLocalCacheForUserRole() {
// mock 数据
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
userRoleMapper.insert(userRoleDO01);
@@ -101,13 +92,10 @@ public class PermissionServiceTest extends BaseDbUnitTest {
userRoleMapper.insert(roleMenuDO02);
// 调用
- permissionService.initLocalCacheIfUpdateForUserRole(null);
+ permissionService.initLocalCacheForUserRole();
// 断言 roleMenuCache 缓存
assertEquals(1, permissionService.getUserRoleCache().size());
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
- // 断言 maxUpdateTime 缓存
- LocalDateTime maxUpdateTime = permissionService.getUserRoleMaxUpdateTime();
- assertEquals(ObjectUtils.max(userRoleDO01.getUpdateTime(), roleMenuDO02.getUpdateTime()), maxUpdateTime);
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceTest.java
index 37d3ba44e..a0d6c79a0 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceTest.java
@@ -23,7 +23,6 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
@@ -59,8 +58,6 @@ public class RoleServiceTest extends BaseDbUnitTest {
Map roleCache = roleService.getRoleCache();
assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));
assertPojoEquals(roleDO2, roleCache.get(roleDO2.getId()));
- // 断言 maxUpdateTime 缓存
- assertEquals(max(roleDO1.getUpdateTime(), roleDO2.getUpdateTime()), roleService.getMaxUpdateTime());
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
index 447ea744c..273e74b4a 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImplTest.java
@@ -60,8 +60,6 @@ public class SensitiveWordServiceImplTest extends BaseDbUnitTest {
// 调用
sensitiveWordService.initLocalCache();
- // 断言 maxUpdateTime 缓存
- assertEquals(max(wordDO1.getUpdateTime(), wordDO2.getUpdateTime()), sensitiveWordService.getMaxUpdateTime());
// 断言 sensitiveWordTagsCache 缓存
assertEquals(SetUtils.asSet("论坛", "蔬菜"), sensitiveWordService.getSensitiveWordTags());
// 断言 tagSensitiveWordTries 缓存
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
index 5dea2808f..4fe433d90 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java
@@ -1,18 +1,17 @@
package cn.iocoder.yudao.module.system.service.sms;
-import cn.hutool.core.bean.BeanUtil;
-import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
-import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
-import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
-import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
-import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
-import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelUpdateReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
+import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper;
+import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@@ -22,12 +21,11 @@ import java.time.LocalDateTime;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
-import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@@ -58,9 +56,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
// 调用
smsChannelService.initLocalCache();
- // 校验 maxUpdateTime 属性
- LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
- assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
// 校验调用
verify(smsClientFactory, times(1)).createOrUpdateSmsClient(
argThat(properties -> isPojoEquals(smsChannelDO01, properties)));
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceTest.java
index 3b314b791..5a31892f5 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsLogServiceTest.java
@@ -235,7 +235,6 @@ public class SmsLogServiceTest extends BaseDbUnitTest {
return randomPojo(SmsLogDO.class, ArrayUtils.append(consumer, consumers));
}
-
private static Map randomTemplateParams() {
return MapUtil.builder().put(randomString(), randomString())
.put(randomString(), randomString()).build();
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceTest.java
index 81e03f29c..a89d1b2e1 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceTest.java
+++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsTemplateServiceTest.java
@@ -61,7 +61,6 @@ public class SmsTemplateServiceTest extends BaseDbUnitTest {
private SmsProducer smsProducer;
@Test
- @SuppressWarnings("unchecked")
void testInitLocalCache() {
// mock 数据
SmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
@@ -72,13 +71,10 @@ public class SmsTemplateServiceTest extends BaseDbUnitTest {
// 调用
smsTemplateService.initLocalCache();
// 断言 deptCache 缓存
- Map smsTemplateCache = (Map) getFieldValue(smsTemplateService, "smsTemplateCache");
+ Map smsTemplateCache = smsTemplateService.getSmsTemplateCache();
assertEquals(2, smsTemplateCache.size());
assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
- // 断言 maxUpdateTime 缓存
- LocalDateTime maxUpdateTime = (LocalDateTime) getFieldValue(smsTemplateService, "maxUpdateTime");
- assertEquals(max(smsTemplate01.getUpdateTime(), smsTemplate02.getUpdateTime()), maxUpdateTime);
}
@Test
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
index b13b191db..1fb4cbaf9 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
+++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/clean.sql
@@ -25,3 +25,6 @@ DELETE FROM "system_oauth2_approve";
DELETE FROM "system_oauth2_access_token";
DELETE FROM "system_oauth2_refresh_token";
DELETE FROM "system_oauth2_code";
+DELETE FROM "system_mail_account";
+DELETE FROM "system_mail_template";
+DELETE FROM "system_mail_log";
diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
index 7590e57a3..1a2c41b73 100644
--- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
+++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql
@@ -566,3 +566,63 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_code" (
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 刷新令牌';
+
+CREATE TABLE IF NOT EXISTS "system_mail_account" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "mail" varchar NOT NULL,
+ "username" varchar NOT NULL,
+ "password" varchar NOT NULL,
+ "host" varchar NOT NULL,
+ "port" int NOT NULL,
+ "ssl_enable" bit NOT NULL,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '邮箱账号表';
+
+CREATE TABLE IF NOT EXISTS "system_mail_template" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar NOT NULL,
+ "code" varchar NOT NULL,
+ "account_id" bigint NOT NULL,
+ "nickname" varchar,
+ "title" varchar NOT NULL,
+ "content" varchar NOT NULL,
+ "params" varchar NOT NULL,
+ "status" varchar NOT NULL,
+ "remark" varchar,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '邮件模版表';
+
+CREATE TABLE IF NOT EXISTS "system_mail_log" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "user_id" bigint,
+ "user_type" varchar,
+ "to_mail" varchar NOT NULL,
+ "account_id" bigint NOT NULL,
+ "from_mail" varchar NOT NULL,
+ "template_id" bigint NOT NULL,
+ "template_code" varchar NOT NULL,
+ "template_nickname" varchar,
+ "template_title" varchar NOT NULL,
+ "template_content" varchar NOT NULL,
+ "template_params" varchar NOT NULL,
+ "send_status" varchar NOT NULL,
+ "send_time" datetime,
+ "send_message_id" varchar,
+ "send_exception" varchar,
+ "creator" varchar DEFAULT '',
+ "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar DEFAULT '',
+ "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '邮件日志表';
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index 1e99ba11e..976515700 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -156,6 +156,9 @@ yudao:
- system_sms_log
- system_sensitive_word
- system_oauth2_client
+ - system_mail_account
+ - system_mail_template
+ - system_mail_log
- infra_codegen_column
- infra_codegen_table
- infra_test_demo
diff --git a/yudao-ui-admin-vue3/.env.base b/yudao-ui-admin-vue3/.env.base
index 7a2f2a0ff..fdb26d850 100644
--- a/yudao-ui-admin-vue3/.env.base
+++ b/yudao-ui-admin-vue3/.env.base
@@ -1,6 +1,8 @@
# 本地开发环境
NODE_ENV=development
+VITE_DEV=true
+
# 请求路径
VITE_BASE_URL='http://localhost:48080'
diff --git a/yudao-ui-admin-vue3/.env.dev b/yudao-ui-admin-vue3/.env.dev
index bece32fd3..658a4f62d 100644
--- a/yudao-ui-admin-vue3/.env.dev
+++ b/yudao-ui-admin-vue3/.env.dev
@@ -1,6 +1,8 @@
# 开发环境
NODE_ENV=production
+VITE_DEV=false
+
# 请求路径
VITE_BASE_URL='http://localhost:58080'
diff --git a/yudao-ui-admin-vue3/.env.pro b/yudao-ui-admin-vue3/.env.pro
index 36168fe0e..0c91638b0 100644
--- a/yudao-ui-admin-vue3/.env.pro
+++ b/yudao-ui-admin-vue3/.env.pro
@@ -1,6 +1,8 @@
# 生产环境
NODE_ENV=production
+VITE_DEV=false
+
# 请求路径
VITE_BASE_URL='http://localhost:58080'
diff --git a/yudao-ui-admin-vue3/.env.test b/yudao-ui-admin-vue3/.env.test
index e1a0c33b9..72b1c47cd 100644
--- a/yudao-ui-admin-vue3/.env.test
+++ b/yudao-ui-admin-vue3/.env.test
@@ -1,6 +1,8 @@
# 测试环境
NODE_ENV=production
+VITE_DEV=false
+
# 请求路径
VITE_BASE_URL='http://localhost:58080'
diff --git a/yudao-ui-admin-vue3/src/api/system/mail/account/index.ts b/yudao-ui-admin-vue3/src/api/system/mail/account/index.ts
new file mode 100644
index 000000000..8b662a701
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/system/mail/account/index.ts
@@ -0,0 +1,46 @@
+import request from '@/config/axios'
+
+export interface MailAccountVO {
+ id: number
+ mail: string
+ username: string
+ password: string
+ host: string
+ port: number
+ sslEnable: boolean
+}
+
+export interface MailAccountPageReqVO extends PageParam {
+ mail?: string
+ username?: string
+}
+
+// 查询邮箱账号列表
+export const getMailAccountPageApi = async (params: MailAccountPageReqVO) => {
+ return await request.get({ url: '/system/mail-account/page', params })
+}
+
+// 查询邮箱账号详情
+export const getMailAccountApi = async (id: number) => {
+ return await request.get({ url: '/system/mail-account/get?id=' + id })
+}
+
+// 新增邮箱账号
+export const createMailAccountApi = async (data: MailAccountVO) => {
+ return await request.post({ url: '/system/mail-account/create', data })
+}
+
+// 修改邮箱账号
+export const updateMailAccountApi = async (data: MailAccountVO) => {
+ return await request.put({ url: '/system/mail-account/update', data })
+}
+
+// 删除邮箱账号
+export const deleteMailAccountApi = async (id: number) => {
+ return await request.delete({ url: '/system/mail-account/delete?id=' + id })
+}
+
+// 获得邮箱账号精简列表
+export const getSimpleMailAccounts = async () => {
+ return request.get({ url: '/system/mail-account/list-all-simple' })
+}
diff --git a/yudao-ui-admin-vue3/src/api/system/mail/log/index.ts b/yudao-ui-admin-vue3/src/api/system/mail/log/index.ts
new file mode 100644
index 000000000..9c6c60eb2
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/system/mail/log/index.ts
@@ -0,0 +1,40 @@
+import request from '@/config/axios'
+
+export interface MailLogVO {
+ id: number
+ userId: number
+ userType: number
+ toMail: string
+ accountId: number
+ fromMail: string
+ templateId: number
+ templateCode: string
+ templateNickname: string
+ templateTitle: string
+ templateContent: string
+ templateParams: string
+ sendStatus: number
+ sendTime: Date
+ sendMessageId: string
+ sendException: string
+}
+
+export interface MailLogPageReqVO extends PageParam {
+ userId?: number
+ userType?: number
+ toMail?: string
+ accountId?: number
+ templateId?: number
+ sendStatus?: number
+ sendTime?: Date[]
+}
+
+// 查询邮件日志列表
+export const getMailLogPageApi = async (params: MailLogPageReqVO) => {
+ return await request.get({ url: '/system/mail-log/page', params })
+}
+
+// 查询邮件日志详情
+export const getMailLogApi = async (id: number) => {
+ return await request.get({ url: '/system/mail-log/get?id=' + id })
+}
diff --git a/yudao-ui-admin-vue3/src/api/system/mail/template/index.ts b/yudao-ui-admin-vue3/src/api/system/mail/template/index.ts
new file mode 100644
index 000000000..a0a0faf2f
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/api/system/mail/template/index.ts
@@ -0,0 +1,58 @@
+import request from '@/config/axios'
+
+export interface MailTemplateVO {
+ id: number
+ name: string
+ code: string
+ accountId: number
+ nickname: string
+ title: string
+ content: string
+ params: string
+ status: number
+ remark: string
+}
+
+export interface MailTemplatePageReqVO extends PageParam {
+ name?: string
+ code?: string
+ accountId?: number
+ status?: number
+ createTime?: Date[]
+}
+
+export interface MailSmsReqVO {
+ mail: string
+ templateCode: string
+ templateParams: Map
+}
+
+// 查询邮件模版列表
+export const getMailTemplatePageApi = async (params: MailTemplatePageReqVO) => {
+ return await request.get({ url: '/system/mail-template/page', params })
+}
+
+// 查询邮件模版详情
+export const getMailTemplateApi = async (id: number) => {
+ return await request.get({ url: '/system/mail-template/get?id=' + id })
+}
+
+// 新增邮件模版
+export const createMailTemplateApi = async (data: MailTemplateVO) => {
+ return await request.post({ url: '/system/mail-template/create', data })
+}
+
+// 修改邮件模版
+export const updateMailTemplateApi = async (data: MailTemplateVO) => {
+ return await request.put({ url: '/system/mail-template/update', data })
+}
+
+// 删除邮件模版
+export const deleteMailTemplateApi = async (id: number) => {
+ return await request.delete({ url: '/system/mail-template/delete?id=' + id })
+}
+
+// 发送邮件
+export const sendMailApi = (data: MailSmsReqVO) => {
+ return request.post({ url: '/system/mail-template/send-mail', data })
+}
diff --git a/yudao-ui-admin-vue3/src/components/XTable/src/XTable.vue b/yudao-ui-admin-vue3/src/components/XTable/src/XTable.vue
index d093489db..340497512 100644
--- a/yudao-ui-admin-vue3/src/components/XTable/src/XTable.vue
+++ b/yudao-ui-admin-vue3/src/components/XTable/src/XTable.vue
@@ -255,16 +255,17 @@ const getToolBarConfig = (options: XTableProps) => {
if (toolbarConfig) return
if (toolBar) {
if (!isBoolean(toolBar)) {
+ console.info(2)
options.toolbarConfig = toolBar
return
}
- } else if (!topActionSlots) {
+ } else if (topActionSlots != false) {
options.toolbarConfig = {
- enabled: true
+ slots: { buttons: 'toolbar_buttons' }
}
} else {
options.toolbarConfig = {
- slots: { buttons: 'toolbar_buttons' }
+ enabled: true
}
}
}
diff --git a/yudao-ui-admin-vue3/src/main.ts b/yudao-ui-admin-vue3/src/main.ts
index e4511d9b1..f5e3c024e 100644
--- a/yudao-ui-admin-vue3/src/main.ts
+++ b/yudao-ui-admin-vue3/src/main.ts
@@ -37,6 +37,13 @@ import App from './App.vue'
import './permission'
+import { isDevMode } from '@/utils/env'
+
+if (isDevMode()) {
+ console.info(isDevMode())
+ import('element-plus/dist/index.css')
+}
+
// 创建实例
const setupAll = async () => {
const app = createApp(App)
diff --git a/yudao-ui-admin-vue3/src/types/auto-components.d.ts b/yudao-ui-admin-vue3/src/types/auto-components.d.ts
index 0a56908aa..46a1ae56c 100644
--- a/yudao-ui-admin-vue3/src/types/auto-components.d.ts
+++ b/yudao-ui-admin-vue3/src/types/auto-components.d.ts
@@ -21,6 +21,7 @@ declare module '@vue/runtime-core' {
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
+ ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
@@ -30,6 +31,7 @@ declare module '@vue/runtime-core' {
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
+ ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
@@ -38,6 +40,7 @@ declare module '@vue/runtime-core' {
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
+ ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
@@ -48,10 +51,14 @@ declare module '@vue/runtime-core' {
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
+ ElScroll: typeof import('element-plus/es')['ElScroll']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
+ ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
+ ElTable: typeof import('element-plus/es')['ElTable']
+ ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
diff --git a/yudao-ui-admin-vue3/src/utils/dict.ts b/yudao-ui-admin-vue3/src/utils/dict.ts
index 5e168e301..176ddf92d 100644
--- a/yudao-ui-admin-vue3/src/utils/dict.ts
+++ b/yudao-ui-admin-vue3/src/utils/dict.ts
@@ -90,6 +90,7 @@ export enum DICT_TYPE {
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
+ SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
// ========== INFRA 模块 ==========
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
diff --git a/yudao-ui-admin-vue3/src/utils/env.ts b/yudao-ui-admin-vue3/src/utils/env.ts
new file mode 100644
index 000000000..4a45f6223
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/utils/env.ts
@@ -0,0 +1,8 @@
+export const isDevMode = () => {
+ const dev = import.meta.env.VITE_DEV
+ if (dev && dev === true) {
+ return true
+ } else {
+ return false
+ }
+}
diff --git a/yudao-ui-admin-vue3/src/views/infra/swagger/index.vue b/yudao-ui-admin-vue3/src/views/infra/swagger/index.vue
index 72a826e61..dce8122ee 100644
--- a/yudao-ui-admin-vue3/src/views/infra/swagger/index.vue
+++ b/yudao-ui-admin-vue3/src/views/infra/swagger/index.vue
@@ -5,5 +5,6 @@
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/account/account.data.ts b/yudao-ui-admin-vue3/src/views/system/mail/account/account.data.ts
new file mode 100644
index 000000000..a2e29f852
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/account/account.data.ts
@@ -0,0 +1,65 @@
+import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+
+// 表单校验
+export const rules = reactive({
+ mail: [required],
+ username: [required],
+ password: [required],
+ host: [required],
+ port: [required],
+ sslEnable: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive({
+ primaryKey: 'id', // 默认的主键 ID
+ primaryTitle: '编号',
+ primaryType: 'id',
+ action: true,
+ actionWidth: '200', // 3 个按钮默认 200,如有删减对应增减即可
+ columns: [
+ {
+ title: '邮箱',
+ field: 'mail',
+ isSearch: true
+ },
+ {
+ title: '用户名',
+ field: 'username',
+ isSearch: true
+ },
+ {
+ title: '密码',
+ field: 'password',
+ isTable: false
+ },
+ {
+ title: 'SMTP 服务器域名',
+ field: 'host'
+ },
+ {
+ title: 'SMTP 服务器端口',
+ field: 'port',
+ form: {
+ component: 'InputNumber',
+ value: 465
+ }
+ },
+ {
+ title: '是否开启 SSL',
+ field: 'sslEnable',
+ dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
+ dictClass: 'boolean'
+ },
+ {
+ title: '创建时间',
+ field: 'createTime',
+ isForm: false,
+ formatter: 'formatDate',
+ table: {
+ width: 180
+ }
+ }
+ ]
+})
+export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/account/index.vue b/yudao-ui-admin-vue3/src/views/system/mail/account/index.vue
new file mode 100644
index 000000000..7c4ad0f9d
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/account/index.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/log/index.vue b/yudao-ui-admin-vue3/src/views/system/mail/log/index.vue
new file mode 100644
index 000000000..be9648200
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/log/index.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.toMail }}
+
+ {{ '(' + row.userId + ')' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/log/log.data.ts b/yudao-ui-admin-vue3/src/views/system/mail/log/log.data.ts
new file mode 100644
index 000000000..d389bce58
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/log/log.data.ts
@@ -0,0 +1,121 @@
+import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+
+// CrudSchema
+const crudSchemas = reactive({
+ primaryKey: 'id',
+ primaryTitle: '编号',
+ primaryType: 'id',
+ action: true,
+ actionWidth: '70',
+ columns: [
+ {
+ title: '发送时间',
+ field: 'sendTime',
+ table: {
+ width: 180
+ },
+ formatter: 'formatDate',
+ search: {
+ show: true,
+ itemRender: {
+ name: 'XDataTimePicker'
+ }
+ }
+ },
+ {
+ title: '接收邮箱',
+ field: 'toMail',
+ isSearch: true,
+ table: {
+ width: 180,
+ slots: {
+ default: 'toMail_default'
+ }
+ }
+ },
+ {
+ title: '用户编号',
+ field: 'userId',
+ isSearch: true,
+ isTable: false
+ },
+ {
+ title: '用户类型',
+ field: 'userType',
+ dictType: DICT_TYPE.USER_TYPE,
+ dictClass: 'number',
+ isSearch: true,
+ isTable: false
+ },
+ {
+ title: '邮件标题',
+ field: 'templateTitle'
+ },
+ {
+ title: '邮件内容',
+ field: 'templateContent',
+ isTable: false
+ },
+ {
+ title: '邮箱参数',
+ field: 'templateParams',
+ isTable: false
+ },
+ {
+ title: '发送状态',
+ field: 'sendStatus',
+ dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,
+ dictClass: 'string',
+ isSearch: true
+ },
+ {
+ title: '邮箱账号',
+ field: 'accountId',
+ isSearch: true,
+ isTable: false,
+ search: {
+ slots: {
+ default: 'accountId_search'
+ }
+ }
+ },
+ {
+ title: '发送邮箱地址',
+ field: 'fromMail',
+ table: {
+ title: '邮箱账号'
+ }
+ },
+ {
+ title: '模板编号',
+ field: 'templateId',
+ isSearch: true
+ },
+ {
+ title: '模板编码',
+ field: 'templateCode',
+ isTable: false
+ },
+ {
+ title: '模版发送人名称',
+ field: 'templateNickname',
+ isTable: false
+ },
+ {
+ title: '发送返回的消息编号',
+ field: 'sendMessageId',
+ isTable: false
+ },
+ {
+ title: '发送异常',
+ field: 'sendException',
+ isTable: false
+ },
+ {
+ title: '创建时间',
+ field: 'createTime',
+ isTable: false
+ }
+ ]
+})
+export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/template/index.vue b/yudao-ui-admin-vue3/src/views/system/mail/template/index.vue
new file mode 100644
index 000000000..e7ddd2396
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/template/index.vue
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ accountOptions.find((account) => account.id === row.accountId)?.mail }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-ui-admin-vue3/src/views/system/mail/template/template.data.ts b/yudao-ui-admin-vue3/src/views/system/mail/template/template.data.ts
new file mode 100644
index 000000000..32522e33c
--- /dev/null
+++ b/yudao-ui-admin-vue3/src/views/system/mail/template/template.data.ts
@@ -0,0 +1,98 @@
+import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+
+// 表单校验
+export const rules = reactive({
+ name: [required],
+ code: [required],
+ accountId: [required],
+ title: [required],
+ content: [required],
+ params: [required],
+ status: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive({
+ primaryKey: 'id', // 默认的主键ID
+ primaryTitle: '编号', // 默认显示的值
+ primaryType: null,
+ action: true,
+ actionWidth: '260',
+ columns: [
+ {
+ title: '模板编码',
+ field: 'code',
+ isSearch: true
+ },
+ {
+ title: '模板名称',
+ field: 'name',
+ isSearch: true
+ },
+ {
+ title: '模板标题',
+ field: 'title'
+ },
+ {
+ title: '模板内容',
+ field: 'content',
+ form: {
+ component: 'Editor',
+ colProps: {
+ span: 24
+ },
+ componentProps: {
+ valueHtml: ''
+ }
+ }
+ },
+ {
+ title: '邮箱账号',
+ field: 'accountId',
+ isSearch: true,
+ table: {
+ width: 200,
+ slots: {
+ default: 'accountId_default'
+ }
+ },
+ search: {
+ slots: {
+ default: 'accountId_search'
+ }
+ }
+ },
+ {
+ title: '发送人名称',
+ field: 'nickname'
+ },
+ {
+ title: '开启状态',
+ field: 'status',
+ isSearch: true,
+ dictType: DICT_TYPE.COMMON_STATUS,
+ dictClass: 'number'
+ },
+ {
+ title: '备注',
+ field: 'remark',
+ isTable: false
+ },
+ {
+ title: '创建时间',
+ field: 'createTime',
+ isForm: false,
+ formatter: 'formatDate',
+ table: {
+ width: 180
+ },
+ search: {
+ show: true,
+ itemRender: {
+ name: 'XDataTimePicker'
+ }
+ }
+ }
+ ]
+})
+export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
diff --git a/yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/index.vue b/yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/index.vue
index 0bc40c8d8..bbc7c8636 100644
--- a/yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/index.vue
+++ b/yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/index.vue
@@ -197,7 +197,7 @@ const sendSmsForm = ref({
})
const sendSmsRules = ref({
mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }],
- templateCode: [{ required: true, message: '手机不能为空', trigger: 'blur' }],
+ templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
templateParams: {}
})
const sendVisible = ref(false)
@@ -225,7 +225,7 @@ const sendSmsTest = async () => {
}
const res = await SmsTemplateApi.sendSmsApi(data)
if (res) {
- message.success('发送成功')
+ message.success('提交发送成功!发送结果,见发送日志编号:' + res)
}
sendVisible.value = false
}
diff --git a/yudao-ui-admin-vue3/src/views/system/user/index.vue b/yudao-ui-admin-vue3/src/views/system/user/index.vue
index 971db66dc..d125112de 100644
--- a/yudao-ui-admin-vue3/src/views/system/user/index.vue
+++ b/yudao-ui-admin-vue3/src/views/system/user/index.vue
@@ -8,17 +8,19 @@
-
+
+
+
@@ -534,8 +536,8 @@ onMounted(async () => {