在日常生活中,邮件已经被聊天软件、短信等更便捷的信息传送方式代替。但在日常工作中,我们的重要的信息通知等非常有必要去归档追溯,那么邮件就是不可或缺的信息传送渠道。对于我们工作中经常用到的系统,里面也基本都集成了邮件发送功能。
SpringBoot提供了基于JavaMail的starter,我们只要按照官方的说明配置邮件服务器信息,即可使我们的系统拥有发送电子邮件的功能。但是,在我们GitEgg开发框架的实际业务开发过程中,有两个问题需要解决:一个是SpringBoot邮箱服务器的配置是配置在配置文件中的,不支持灵活的界面配置。另外一个是我们的开发框架需要支持多租户,那么此时需要对SpringBoot提供的邮件发送功能进行扩展,以满足我们的需求。
那么,基于以上需求和问题,我们对GitEgg框架进行扩展,增加以下功能:
同一个租户可以配置多个电子邮件服务器,但只可以设置一个服务器为启用状态。默认情况下,系统通知类的功能只使用启用状态的服务器进行邮件发送。在有定制化需求的情况下,比如从页面直接指定某个服务器进行邮件发送,那么提供可以选择的接口,指定某个服务器进行邮件发送。
com.gitegg.platform
gitegg-platform-boot
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-starter-mail
org.springframework.boot
spring-boot-starter-logging
@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;
}
@Data
public class GitEggJavaMailSenderImpl extends JavaMailSenderImpl {
/**
* 配置id
*/
private Long id;
/**
* 租户id
*/
private Long tenantId;
/**
* 渠道编码
*/
private String channelCode;
/**
* 配置的md5值
*/
private String md5;
}
@Slf4j
public class JavaMailSenderFactory {
private RedisTemplate redisTemplate;
private JavaMailSenderImpl javaMailSenderImpl;
/**
* 是否开启租户模式
*/
private Boolean enable;
/**
* JavaMailSender 缓存
* 尽管存在多个微服务,但是只需要在每个微服务初始化一次即可
*/
private final static Map 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
@Configuration
public 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;
}
}
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;
}
}
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:";
}
@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代码即可。
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;
邮件模板数据库表设计:
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;
上面的基本功能开发完成之后,那么我们就需要进行测试,这里选择两种类型的邮箱进行测试,一种是QQ邮箱,还有一种是阿里云企业邮箱。
QQ邮箱在配置的时候不能使用QQ的登录密码,需要单独设置QQ邮箱的授权码,下面是操作步骤:
阿里云企业邮箱的配置相比较而言就简单一些,配置的密码就是企业邮箱登录的密码。
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-
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
欢迎感兴趣的小伙伴Star支持一下。