网站域名空间一年费用是多少钱,制作h5网页流程及详细步骤,人个做外贸用什么网站好,自己怎么做跨境电商前言 发送邮件功能#xff0c;借鉴 刚果商城#xff0c;根据文档及项目代码实现。整理总结便有了此文#xff0c;文章有不对的点#xff0c;请联系博主指出#xff0c;请多多点赞收藏#xff0c;您的支持是我最大的动力~ 发送邮件功能主要借助 mail、freemarker以及rocke…前言 发送邮件功能借鉴 刚果商城根据文档及项目代码实现。整理总结便有了此文文章有不对的点请联系博主指出请多多点赞收藏您的支持是我最大的动力~ 发送邮件功能主要借助 mail、freemarker以及rocketmq实现。
刚果商城是个分布式项目近看发送消息模块即可。 标准的DDD分层架构。
RocketMQ部署
方便起见使用docker部署环境
RocketMQ 4.5.1 安装部署
安装 NameServer
docker run -d -p 9876:9876 --name rmqnamesrv foxiswho/rocketmq:server-4.5.1安装 Brocker
1新建配置目录
mkdir -p /mydata/rocketmq/conf2新建配置文件 broker.conf
brokerClusterName DefaultCluster
brokerName broker-a
brokerId 0
deleteWhen 04
fileReservedTime 48
brokerRole ASYNC_MASTER
flushDiskType ASYNC_FLUSH
# 此处为本地ip, 如果部署服务器, 需要填写服务器外网ip
brokerIP1 xx.xx.xx.xx3创建容器
docker run -d \
-p 10911:10911 \
-p 10909:10909 \
--name rmqbroker \
--link rmqnamesrv:namesrv \
-v /mydata/rocketmq/conf/broker.conf:/etc/rocketmq/broker.conf \
-e NAMESRV_ADDRnamesrv:9876 \
-e JAVA_OPTS-Duser.home/opt \
-e JAVA_OPT_EXT-server -Xms512m -Xmx512m \
foxiswho/rocketmq:broker-4.5.1安装 rocketmq 控制台
docker pull pangliang/rocketmq-console-ng
docker run -d \
--link rmqnamesrv:namesrv \
-e JAVA_OPTS-Drocketmq.config.namesrvAddrnamesrv:9876 -Drocketmq.config.isVIPChannelfalse \
--name rmqconsole \
-p 8088:8080 \
-t pangliang/rocketmq-console-ng运行成功稍等几秒启动时间浏览器输入 ip:8088 查看。
记得放行上述所有端口最终结果如下 RocketMQ安装成功~
引入主要依赖 !-- 发送邮件主要依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-mail/artifactId/dependency!-- 模板引擎 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-freemarker/artifactId/dependency!-- 消息队列 实现解耦 --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-stream-rocketmq/artifactId/dependency配置文件 主要看application.yaml 和 application-dev.yaml
application.yaml
server:port: 8001spring:profiles:active: devapplication:name: message-servicestream:bindings:# 主要是如下两个通道的配置 消费者通道mailSend:consumer:concurrency: 4max-attempts: 1content-type: application/jsondestination: message-center_topicgroup: message-center_mail-send_cg# 生产者通道 messageOutput:content-type: application/jsondestination: message-center_topicgroup: message-center_general-send_pgrocketmq:bindings:mailSend:consumer:delay-level-when-next-consume: -1tags: common_message-center_mail-send_tag# ... application-dev.yaml
spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848stream:rocketmq:binder:name-server: 127.0.0.1:9876 # rocketmq服务mail:default-encoding: UTF-8host: smtp.163.compassword: xxxport: 25protocol: smtpusername: xxx163.com重要的是mail中参数用的是网易邮箱username网易邮箱账号password登录 SMTP server 的密码
登录 SMTP server 密码
password获取步骤如下
一、登录网页版邮箱https://email.163.com/)进入邮箱首页。
二、点击上方设置选择POP/SMTP/IMAP选项。 三、在客户端协议界面选择开启对应的协议IMAP或者POP3分别为不同的收信协议选择只开启需要的收信协议比如IMAP推荐使用IMAP协议来收发邮件它可以和网页版完全同步。 四、点击开启继续开启手机扫码发送短信后得到的一串密码即为登录密码 真正代码实现
interfaces层 用户接口层入参为CQRS风格参数都在application层
发送邮件入参
Data
ApiModel(邮箱发送)
public class MailSendCommand {ApiModelProperty(value 标题, example 刚果商城邮箱验证码提醒)NotBlank(message 邮箱标题不能为空)private String title;EmailApiModelProperty(value 发送者, example congomall163.com)NotBlank(message 邮箱发送者不能为空)private String sender;EmailApiModelProperty(value 接收者, example 7798432163.com, notes 实际发送时更改为自己邮箱)NotBlank(message 邮箱接收者不能为空)private String receiver;EmailApiModelProperty(抄送者)private String cc;ApiModelProperty(value 消息参数)private ListString paramList;// 与数据库对应ApiModelProperty(value 模板ID, example userRegisterVerification)NotBlank(message 邮箱模板ID不能为空)private String templateId;
}application层 直接调用到application层Service实现类方法该层封装好参数直接调用基础层中消息生产者。
domain层 领域层里面主要是一些常量、实体类接口以及仓储接口具体实现在基础层。
infrastructure层 ☆ 消息通道配置
source - sink
public interface MessageSource {String OUTPUT messageOutput;Output(MessageSource.OUTPUT)MessageChannel messageOutput();
}public interface MessageSink {String MAIL_SEND mailSend;Input(MessageSink.MAIL_SEND)SubscribableChannel mailSend();
}常量与配置文件中通道名称保持一致 消息生产者
Slf4j
Component
AllArgsConstructor
public class MessageSendProduce {// 属性名与配置文件中通道名保持一致private final MessageChannel messageOutput;/*** 邮箱消息发送*/public void mailMessageSend(MailMessageSendEvent mailMessageSendEvent) {String keys UUID.randomUUID().toString();Message? message MessageBuilder.withPayload(JSON.toJSONString(mailMessageSendEvent)).setHeader(MessageConst.PROPERTY_KEYS, keys).setHeader(MessageConst.PROPERTY_TAGS, MessageRocketMQConstants.MESSAGE_MAIL_SEND_TAG).build();long startTime SystemClock.now();boolean sendResult false;try {// 发送消息给mqsendResult messageOutput.send(message, 2000L);} finally {log.info(邮箱消息发送发送状态: {}, Keys: {}, 执行时间: {} ms, 消息内容: {}, sendResult, keys, SystemClock.now() - startTime, JSON.toJSONString(mailMessageSendEvent));}}
}消息消费者
Slf4j
Component
RequiredArgsConstructor
public class MailMessageSendConsume {private final MessageSendFacade messageSendFacade;// 幂等性注解还没研究Idempotent(uniqueKeyPrefix mail_message_send:,key #event.messageSendId_#event.hashCode(),type IdempotentTypeEnum.SPEL,scene IdempotentSceneEnum.MQ,keyTimeout 600L)StreamListener(MessageSink.MAIL_SEND)public void mailMessageSend(Payload MailMessageSendEvent event, Headers Map headers) {long startTime System.currentTimeMillis();try {MessageSend messageSend BeanUtil.toBean(event, MessageSend.class);// 【外观模式】: 抽象消息发送、消息存储以及失败回调业务方等逻辑messageSendFacade.mailMessageSend(messageSend);} finally {log.info(Keys: {}, Msg id: {}, Execute time: {} ms, Message: {}, headers.get(rocketmq_KEYS), headers.get(rocketmq_MESSAGE_ID), System.currentTimeMillis() - startTime,JSON.toJSONString(event));}}
}【外观模式】 直接与外观类交互外观类封装了做某件事的所有操作无需与一个个子操作一一交互降低了复杂性提高了可维护性。 以消息发送为例将发送邮箱以及消息存储和失败回调业务封装为一个方法降低调用处理复杂度。 发送邮箱核心实现类 ☆
Slf4j
Component
AllArgsConstructor
public class MailMessageProduceImpl implements ApplicationListenerApplicationInitializingEvent, MailMessageProduce {private final MailTemplateMapper mailTemplateMapper;private final JavaMailSender javaMailSender;private final Configuration configuration;SneakyThrowsOverridepublic boolean send(MessageSend messageSend) {try {// 根据模板id查询模板 模板iduserRegisterVerificationMailTemplateDO mailTemplateDO mailTemplateMapper.selectOne(Wrappers.lambdaQuery(MailTemplateDO.class).eq(MailTemplateDO::getTemplateId, messageSend.getTemplateId()));MimeMessage mimeMessage javaMailSender.createMimeMessage();MimeMessageHelper helper new MimeMessageHelper(mimeMessage, true);helper.setFrom(messageSend.getSender());helper.setSubject(messageSend.getTitle());if (StrUtil.isNotBlank(messageSend.getCc())) {helper.setCc(messageSend.getCc().split(,));}if (StrUtil.isNotBlank(messageSend.getReceiver())) {helper.setTo(messageSend.getReceiver().split(,));}MapString, Object model Maps.newHashMap();// 模板参数名称与下面freemarker模板中参数一一对应String[] templateParams mailTemplateDO.getTemplateParam().split(,);if (ArrayUtil.isNotEmpty(templateParams)) {for (int i 0; i templateParams.length; i) {model.put(templateParams[i], messageSend.getParamList().get(i));}}// 模板id就是模板名String templateKey messageSend.getTemplateId() .ftl;// 从单例对象容器获取模板Template template Singleton.get(templateKey, () - {try {return configuration.getTemplate(templateKey);} catch (IOException e) {throw new RuntimeException(e);}});String html FreeMarkerTemplateUtils.processTemplateIntoString(template, model);helper.setText(html, true);// freemarker填充参数发送邮箱javaMailSender.send(mimeMessage);} catch (Throwable ex) {log.error(邮件发送失败Request: {}, JSONUtil.toJsonStr(messageSend), ex);return false;}return true;}/*** 初始化邮箱模板 【率先先将所有模板初始化到单例对象容器中】*/SneakyThrowsOverridepublic void onApplicationEvent(ApplicationInitializingEvent event) {Resource[] resources new PathMatchingResourcePatternResolver().getResources(ResourceUtils.CLASSPATH_URL_PREFIX templates/*.ftl);for (Resource resource : resources) {String templateName resource.getFilename();Singleton.put(templateName, configuration.getTemplate(templateName));}}
}模板具体内容
html langen xmlns:thhttp://www.thymeleaf.org
body
divp stylecaret-color: rgb(86, 90, 92); color: rgb(86, 90, 92); font-family: Helvetica, 微软雅黑, 宋体; text-size-adjust: auto; font-size: 20px;亲爱的用户/pp stylecaret-color: rgb(86, 90, 92); color: rgb(86, 90, 92); font-family: Helvetica, 微软雅黑, 宋体; font-size: 18px; text-size-adjust: auto;您好感谢您的使用您本次的验证码为span classApple-converted-spacenbsp;/span/pbstylefont-family: Helvetica, 微软雅黑, 宋体; text-size-adjust: auto; font-size: 32px; color: rgb(45, 123, 255);${validCode}/bp stylecaret-color: rgb(86, 90, 92); color: rgb(86, 90, 92); font-family: Helvetica, 微软雅黑, 宋体; text-size-adjust: auto; font-size: 20px;安全提示/pp stylecaret-color: rgb(86, 90, 92); color: rgb(86, 90, 92); font-family: Helvetica, 微软雅黑, 宋体; font-size: 18px; text-size-adjust: auto;为保障您的帐户安全请在 5 分钟内完成验证否则验证码将自动失效。span classApple-converted-spacenbsp;/span/p
/div
divincludetail!--![endif]--/includetail
/div
/body
/html 最终实现效果
测试结果如下
收件为QQ邮箱 收件为谷歌邮箱 经我测试发现配置的是网易邮箱发送者就只能是网易邮箱接收者可以是任意邮箱。