在一样寻常生存中,邮件已经被聊天软件、短信等更便捷的信息传送方式取代。但在一样寻常工作中,我们的紧张的信息关照等非常有须要去归档追溯,那么邮件就是不可或缺的信息传送渠道。对于我们工作中经常用到的体系,内里也根本都集成了邮件发送功能。
SpringBoot提供了基于JavaMail的starter,我们只要按照官方的阐明设置邮件服务器信息,即可使我们的体系拥有发送电子邮件的功能。但是,在我们GitEgg开发框架的实际业务开发过程中,有两个标题须要办理:一个是SpringBoot邮箱服务器的设置是设置在设置文件中的,不支持机动的界面设置。别的一个是我们的开发框架须要支持多租户,那么此时须要对SpringBoot提供的邮件发送功能举行扩展,以满意我们的需求。
那么,基于以上需求和标题,我们对GitEgg框架举行扩展,增长以下功能:
1、扩展体系设置:将邮箱服务器的设置信息长期化到数据库、Redis缓存,和设置文件一起使用,订定读取优先级。
2、扩展多租户设置:假如体系开启了多租户功能,那么在邮件发送时,起首读取租户的当前设置,假如没有设置,那么在读取体系设置。
3、自有选择服务器:用户可在体系界面上选择指定的邮箱服务器举行邮件发送。
4、提供邮件发送模板:用户可选择预先订定的邮件模板举行发送特定邮件。
5、增长发送数目、频率限定:增长设置,限定模板邮件的发送数目和频率。
6、生存邮件发送记载:不愿定把全部附件都生存,只需生存邮件发送关键信息,假如须要生存全部附件等须要自己扩展。
同一个租户可以设置多个电子邮件服务器,但只可以设置一个服务器为启用状态。默认环境下,体系关照类的功能只使用启用状态的服务器举行邮件发送。在有定制化需求的环境下,好比从页面直接指定某个服务器举行邮件发送,那么提供可以选择的接口,指定某个服务器举行邮件发送。
一、集成spring-boot-starter-mail扩展根本邮件发送功能
1、在根本框架gitegg-platform中新建gitegg-platform-mail子项目,引入邮件必须的相干依赖包。
<dependencies> <!-- gitegg Spring Boot自界说及扩展 --> <dependency> <groupId>com.gitegg.platform</groupId> <artifactId>gitegg-platform-boot</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> <!-- 去除springboot默认的logback设置--> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> </dependencies>2、扩展邮件服务器设置类,增长租户等信息,方便从缓存读取到信息之后举行设置转换。
@Data@JsonIgnoreProperties(ignoreUnknown = true)public class GitEggMailProperties extends MailProperties { /** * 设置id */ private Long id; /** * 租户id */ private Long tenantId; /** * 渠道id */ private String channelCode; /** * 状态 */ private Integer channelStatus; /** * 设置的md5值 */ private String md5;}3、扩展邮件发送实现类JavaMailSenderImpl,添加多租户和邮箱服务器编码,便于多租户和渠道选择。
@Datapublic class GitEggJavaMailSenderImpl extends JavaMailSenderImpl { /** * 设置id */ private Long id; /** * 租户id */ private Long tenantId; /** * 渠道编码 */ private String channelCode; /** * 设置的md5值 */ private String md5;}4、新建邮件发送实例工厂类JavaMailSenderFactory,在邮件发送时,根据需求生产须要的邮件发送实例。
@Slf4jpublic class JavaMailSenderFactory { private RedisTemplate redisTemplate; private JavaMailSenderImpl javaMailSenderImpl; /** * 是否开启租户模式 */ private Boolean enable; /** * JavaMailSender 缓存 * 只管存在多个微服务,但是只须要在每个微服务初始化一次即可 */ private final static Map<String, GitEggJavaMailSenderImpl> javaMailSenderMap = new ConcurrentHashMap<>(); public JavaMailSenderFactory(RedisTemplate redisTemplate, JavaMailSenderImpl javaMailSenderImpl, Boolean enable) { this.redisTemplate = redisTemplate; this.javaMailSenderImpl = javaMailSenderImpl; this.enable = enable; } /** * 指定邮件发送渠道 * @return */ public JavaMailSenderImpl getMailSender(String... channelCode){ if (null == channelCode || channelCode.length == GitEggConstant.COUNT_ZERO || null == channelCode[GitEggConstant.Number.ZERO]) { return this.getDefaultMailSender(); } // 起首判定是否开启多租户 String mailConfigKey = JavaMailConstant.MAIL_TENANT_CONFIG_KEY; if (enable) { mailConfigKey += GitEggAuthUtils.getTenantId(); } else { mailConfigKey = JavaMailConstant.MAIL_CONFIG_KEY; } // 从缓存获取邮件设置信息 // 根据channel code获取设置,用channel code时,不区分是否是默认设置 String propertiesStr = (String) redisTemplate.opsForHash().get(mailConfigKey, channelCode[GitEggConstant.Number.ZERO]); if (StringUtils.isEmpty(propertiesStr)) { throw new BusinessException("未获取到[" + channelCode[GitEggConstant.Number.ZERO] + "]的邮件设置信息"); } GitEggMailProperties properties = null; try { properties = JsonUtils.jsonToPojo(propertiesStr, GitEggMailProperties.class); } catch (Exception e) { log.error("转换邮件设置信息非常:{}", e); throw new BusinessException("转换邮件设置信息非常:" + e); } return this.getMailSender(mailConfigKey, properties); } /** * 不指定邮件发送渠道,取默认设置 * @return */ public JavaMailSenderImpl getDefaultMailSender(){ // 起首判定是否开启多租户 String mailConfigKey = JavaMailConstant.MAIL_TENANT_CONFIG_KEY; if (enable) { mailConfigKey += GitEggAuthUtils.getTenantId(); } else { mailConfigKey = JavaMailConstant.MAIL_CONFIG_KEY; } // 获取全部邮件设置列表 Map<Object, Object> propertiesMap = redisTemplate.opsForHash().entries(mailConfigKey); Iterator<Map.Entry<Object, Object>> entries = propertiesMap.entrySet().iterator(); // 假如没有设置取哪个设置,那么获取默认的设置 GitEggMailProperties properties = null; try { while (entries.hasNext()) { Map.Entry<Object, Object> entry = entries.next(); // 转为体系设置对象 GitEggMailProperties propertiesEnable = JsonUtils.jsonToPojo((String) entry.getValue(), GitEggMailProperties.class); if (propertiesEnable.getChannelStatus().intValue() == GitEggConstant.ENABLE) { properties = propertiesEnable; break; } } } catch (Exception e) { e.printStackTrace(); } return this.getMailSender(mailConfigKey, properties); } private JavaMailSenderImpl getMailSender(String mailConfigKey, GitEggMailProperties properties) { // 根据最新设置信息判定是否从当地获取mailSender,在设置生存时,盘算实体设置的md5值,然后举行比力,不要在每次对比的时间举行md5盘算 if (null != properties && !StringUtils.isEmpty(properties.getMd5())) { GitEggJavaMailSenderImpl javaMailSender = javaMailSenderMap.get(mailConfigKey); if (null == javaMailSender || !properties.getMd5().equals(javaMailSender.getMd5())) { // 假如没有设置信息,那么直接返回体系默认设置的mailSender javaMailSender = new GitEggJavaMailSenderImpl(); this.applyProperties(properties, javaMailSender); javaMailSender.setMd5(properties.getMd5()); javaMailSender.setId(properties.getId()); // 将MailSender放入缓存 javaMailSenderMap.put(mailConfigKey, javaMailSender); } return javaMailSender; } else { return this.javaMailSenderImpl; } } private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) { sender.setHost(properties.getHost()); if (properties.getPort() != null) { sender.setPort(properties.getPort()); } sender.setUsername(properties.getUsername()); sender.setPassword(properties.getPassword()); sender.setProtocol(properties.getProtocol()); if (properties.getDefaultEncoding() != null) { sender.setDefaultEncoding(properties.getDefaultEncoding().name()); } if (!properties.getProperties().isEmpty()) { sender.setJavaMailProperties(this.asProperties(properties.getProperties())); } } private Properties asProperties(Map<String, String> source) { Properties properties = new Properties(); properties.putAll(source); return properties; }}5、设置异步邮件发送的线程池,这里需留意异步线程池上下文变量共享标题,有两种方式办理,一个是使用装饰器TaskDecorator将父子线程变量举行复制,另有一种方式是transmittable-thread-local来共享线程上下文,这里不睁开描述,后续会专门针对如安在微服务异步线程池中共享上线文举行阐明。
@Configurationpublic class MailThreadPoolConfig { @Value("${spring.mail-task.execution.pool.core-size}") private int corePoolSize; @Value("${spring.mail-task.execution.pool.max-size}") private int maxPoolSize; @Value("${spring.mail-task.execution.pool.queue-capacity}") private int queueCapacity; @Value("${spring.mail-task.execution.thread-name-prefix}") private String namePrefix; @Value("${spring.mail-task.execution.pool.keep-alive}") private int keepAliveSeconds; /** * 邮件发送的线程池 * @return */ @Bean("mailTaskExecutor") public Executor mailTaskExecutor(){ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //最大线程数 executor.setMaxPoolSize(maxPoolSize); //焦点线程数 executor.setCorePoolSize(corePoolSize); //任务队列的巨细 executor.setQueueCapacity(queueCapacity); //线程前缀名 executor.setThreadNamePrefix(namePrefix); //线程存活时间 executor.setKeepAliveSeconds(keepAliveSeconds); // 设置装饰器,父子线程共享request header变量 executor.setTaskDecorator(new RequestHeaderTaskDecorator()); /** * 拒绝处置惩罚战略 * CallerRunsPolicy():交由调用方线程运行,好比 main 线程。 * AbortPolicy():直接抛出非常。 * DiscardPolicy():直接扬弃。 * DiscardOldestPolicy():扬弃队列中最老的任务。 */ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 线程初始化 executor.initialize(); return executor; }}6、增长邮件发送结果的摆列类MailResultCodeEnum
public enum MailResultCodeEnum { /** * 默认 */ SUCCESS("success", "邮件发送乐成"), /** * 自界说 */ ERROR("error", "邮件发送失败"); public String code; public String message; MailResultCodeEnum(String code, String message) { this.code = code; this.message = message; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }}7、增长邮箱服务器相干默认设置的常量类JavaMailConstant.java
public class JavaMailConstant { /** * Redis JavaMail设置config key */ public static final String MAIL_CONFIG_KEY = "mail:config"; /** * 当开启多租户模式时,Redis JavaMail设置config key */ public static final String MAIL_TENANT_CONFIG_KEY = "mail:tenant:config:";}8、增长GitEggJavaMail主动装配类,根据Nacos大概体系设置举行装配。
@Slf4j@Configuration@RequiredArgsConstructor(onConstructor_ = @Autowired)public class GitEggJavaMailConfiguration { private final JavaMailSenderImpl javaMailSenderImpl; private final RedisTemplate redisTemplate; /** * 是否开启租户模式 */ @Value("${tenant.enable}") private Boolean enable; @Bean public JavaMailSenderFactory gitEggAuthRequestFactory() { return new JavaMailSenderFactory(redisTemplate, javaMailSenderImpl, enable); }}二、增长邮箱服务器设置界面
邮箱服务器的设置,实际就是差别邮箱渠道的设置,这里我们将表和字段设计好,然后使用GitEgg自带代码天生器,天生业务的CRUD代码即可。
1、邮箱渠道设置表设计
CREATE TABLE `t_sys_mail_channel` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id', `channel_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '渠道编码', `channel_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '渠道名称', `host` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'SMTP服务器地点', `port` int(11) NULL DEFAULT NULL COMMENT 'SMTP服务器端口', `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账户名', `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '暗码', `protocol` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'smtp' COMMENT '协议', `default_encoding` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '默认编码', `jndi_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '会话JNDI名称', `properties` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JavaMail 设置', `channel_status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '渠道状态 1有效 0禁用', `md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'MD5', `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者', `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '邮件渠道' ROW_FORMAT = DYNAMIC;SET FOREIGN_KEY_CHECKS = 1;2、根据表设计,然后设置代码天生界面,天生前后端代码。
3、天生代码后,举行相干权限设置,前端界面展示:
三、以同样的方式增长邮箱模板设置界面和邮件发送日志记载
1、邮箱模板和邮件发送日志数据库表设计
邮件模板数据库表设计:
CREATE TABLE `t_sys_mail_template` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id', `template_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模板编码', `template_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模板名称', `sign_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '模板署名', `template_status` tinyint(2) NOT NULL DEFAULT 1 COMMENT '模板状态', `template_type` tinyint(2) NULL DEFAULT NULL COMMENT '模板范例', `template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '模板内容', `cache_code_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '缓存key', `cache_time_out` bigint(20) NULL DEFAULT 0 COMMENT '缓存有效期 值', `cache_time_out_unit` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '缓存有效期 单位', `send_times_limit` bigint(20) NULL DEFAULT 0 COMMENT '发送次数限定', `send_times_limit_period` bigint(20) NULL DEFAULT 0 COMMENT '限定时间间隔', `send_times_limit_period_unit` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '限定时间间隔 单位', `comments` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者', `del_flag` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '邮件模板' ROW_FORMAT = DYNAMIC;SET FOREIGN_KEY_CHECKS = 1;邮件日志数据库表设计:
CREATE TABLE `t_sys_mail_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户id', `channel_id` bigint(20) NULL DEFAULT NULL COMMENT 'mail渠道id', `template_id` bigint(20) NULL DEFAULT NULL COMMENT 'mail模板id', `mail_subject` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮件主题', `mail_from` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送人', `mail_to` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '收件人', `mail_cc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '抄送', `mail_bcc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '密抄送', `mail_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '邮件内容', `attachment_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '附件名称', `attachment_size` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '附件巨细', `send_time` datetime(0) NULL DEFAULT NULL COMMENT '发送时间', `send_result_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '发送结果码', `send_result_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送结果消息', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期', `creator` bigint(20) NULL DEFAULT NULL COMMENT '创建者', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新日期', `operator` bigint(20) NULL DEFAULT NULL COMMENT '更新者', `del_flag` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除 1:删除 0:不删除', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '邮件记载' ROW_FORMAT = DYNAMIC;SET FOREIGN_KEY_CHECKS = 1;2、邮件模板和邮件发送日志界面
四、QQ邮箱设置和阿里云企业邮箱设置测试
上面的根本功能开发完成之后,那么我们就须要举行测试,这里选择两种范例的邮箱举行测试,一种是QQ邮箱,另有一种是阿里云企业邮箱。
1、QQ邮箱设置
QQ邮箱在设置的时间不能使用QQ的登录暗码,须要单独设置QQ邮箱的授权码,下面是利用步调:
- 颠末一系列的验证之后,会获取到一个授权码:
- 体系中设置QQ邮箱相干信息
2、 阿里云企业邮箱设置
阿里云企业邮箱的设置相比力而言就简朴一些,设置的暗码就是企业邮箱登录的暗码。
- 账户设置,开启POP3/SMTP和IMAP/SMTP服务
- 体系中设置阿里云企业邮箱相干信息
3、Nacos中设置默认邮件服务器,同时增长邮件异步线程池设置
mail: username: XXXXXXXXXXX password: XXXXXXXXXX default-encoding: UTF-8 host: smtp.mxhichina.com port: 25 protocol: smtp properties: mail: smtp: auth: true ssl: enable: false # 异步发送邮件,焦点线程池数设置 mail-task: execution: pool: core-size: 5 max-size: 10 queue-capacity: 5 keep-alive: 60 thread-name-prefix: mail-send-task-4、在邮件渠道设置界面举行邮件发送测试,有两种测试方式,一种是选择指定渠道举行发送,别的一种是选择体系默认渠道举行邮件发送。发送完成后查察邮件日志模块,查抄是否有邮件发送乐成的记载。
GitEgg-Cloud是一款基于SpringCloud整合搭建的企业级微服务应用开发框架,开源项目地点:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
接待感爱好的小同伴Star支持一下。 |