基于事件驱动发送email、短信

基于事件驱动发送email、短信
发送邮件就发送邮件呗,为什么说是基于事件驱动呢?因为发送邮件是一个相对耗时的操作,我们基于事件驱动可以异步解耦,

同时通过@Async做一步操作降低接口的耦合性和响应时间。

spring事件驱动组成
spring事件驱动由3个部分组成

1、ApplicationEvent:表示事件本身,自定义事件需要继承该类。用来定义事件

2、ApplicationEventPublisherAware:事件发送器,需要实现该接口。主要用来发布事件.ApplicationContext 也实现了该接口,可以用于发布事件.

Spring4.2之后,ApplicationEventPublisher自动被注入到容器中,采用Autowired即可获取。

3、ApplicationListener:事件监听器接口。监听类实现ApplicationListener 里onApplicationEvent方法即可.

在spring4.2以后我们可以以更加简洁的方式来监听event的发布,监听事件我们不必再实现ApplicationListener接口了,只要在方法上添加注解@EventListener即可.

如果要监听多个事件类型的发布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring会多次调用此方法来处理多个事件。但是注意此时,方法参数不能有多个,否则会发生转换异常,可以将使用多个事件的父类作为唯一的方法参数来接收处理事件,但除非必要否则并不推荐监听多个事件的发布。

如果有多个监听器监听同一事件,我们可以在方法上使用spring的@order注解来定义多个监听器的顺序,order越小,优先级越高.

@EventListener还有一个属性,condition()里可以使用SPEL表达式来过滤监听到事件,即只有符合某种条件的才进行接收处理。比如:

1
@EventListener(condition = "event.message == 'message'")

监听多个事件:

1
2
3
4
5
6
7
8
9
10
@EventListener({FaceEvent.class,ArmEvent.class})
public void onApplicationEvent3(Object event) {

if(event instanceof FaceEvent){
LOGGER.info("===> B 收到人脸事件: {}",((FaceEvent) event).getEventData());
}else if(event instanceof ArmEvent){
ArmEvent armEvent = (ArmEvent) event;
LOGGER.info("===> B 收到臂膀事件: {}",armEvent.getEventData());
}
}

注意事项

  • 事件没要处理的监听器,就会被抛弃。

  • 一个事件可以同时被多个监听处理类监听处理。

  • 默认情况下事件是同步的,即事件被publish后会等待Listener的处理。如果发布事件处的业务存在事务,监听器处理也会在相同的事务中。

  • 如果对于事件的处理不想受到影响,可以onApplicationEvent方法上加@Async支持异步或者在有@EventListener的注解方法上加上@Async。注:启动类上同时要加上@EnableAsync

利用@TransactionalEventListener实现监听事件时的事务隔离
很多时候,只有事务提交之后我们才会发布相应的事件处理其他逻辑,比如用户注册之后,发送邮件或者短信。这时候就可以用注解@TransactionalEventListener。

@TransactionalEventListener和@EventListener都可以监听事件,但前者可以对发布事件和监听事件进行一些事务上的隔离。

@TransactionalEventListener是对@EventListener的一个扩展,允许将事件的监听器绑定到事务的某个阶段。可以绑定到以下事务阶段:

  • AFTER_COMMIT (默认),事务提交后
  • AFTER_ROLLBACK ,事务回滚后
  • AFTER_COMPLETION ,事务完成,包括提交后和回滚后
  • BEFORE_COMMIT ,事务提交前****
    @TransactionalEventListener指不和发布事件的方法在同一个事务内,发布事件的方法事务结束后才会执行本监听方法,监听逻辑内发生异常不会回滚发布事件方法的事务。

@TransactionalEventListener有一个属性为fallbackExecution,默认为false,指发布事件的方法没有事务控制时,监听器不进行监听事件,此为默认情况!fallbackExecution=true,则指发布事件的方法没有事务控制时,监听方法仍可以监听事件进行处理。

整合邮件功能

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import lombok.Data;

@Data
public class EmailInfo {

/**
* 邮件标题
*/
private String title;

/**
* 邮件内容
*/
private String content;

/**
* 邮件主题
*/
private String subject;

/**
* 收件者账号
*/
private String destination;

/**
* 附件地址
*/
private String attachmentPath;

/**
* 附件描述
*/
private String attachmentDescription;


/**
* 附件名称
*/
private String attachmentName;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class EmailServer implements Serializable {
private Long id;

/**
* smtp地址
*/
private String smtpAddr;

/**
* 服务器端口号
*/
private Integer mailPort;

/**
* 邮箱登陆用户名
*/
private String userName;

/**
* 第三方登陆授权码
*/
private String password;

/**
* https是否开启(0:开启 1:关闭)
*/
private Integer sslFlag;

/**
* 邮件发送时间的限制,单位毫秒
*/
private Long sendTime;

/**
* 连接时间,单位毫秒
*/
private Long connectTime;

/**
* 邮件接收时间限制,单位毫秒
*/
private Long receiveTime;

/**
* 发送者账号
*/
private String senderAccount;

/**
* 邮件最大发送值
*/
private Integer maxQuantity;

/**
* 创建时间
*/
private Date createTime;

/**
* 修改时间
*/
private Date modifyTime;

/**
* 账户状态(0:启用,1:禁用)
*/
private Integer status;

/**
* 描述
*/
private String remark;

private static final long serialVersionUID = 1L;
}

定义Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import com.ruoyi.aiot.domain.email.EmailInfo;
import com.ruoyi.aiot.domain.email.EmailServer;
import org.springframework.context.ApplicationEvent;

public class SendSimpleEmailEvent extends ApplicationEvent {

private EmailServer emailServer;

private EmailInfo emailInfo;

public SendSimpleEmailEvent(Object source) {
super(source);
}

public SendSimpleEmailEvent(Object source, EmailServer emailServer, EmailInfo emailInfo) {
super(source);
this.emailServer = emailServer;
this.emailInfo = emailInfo;
}

public EmailServer getEmailServer() {
return emailServer;
}

public void setEmailServer(EmailServer emailServer) {
this.emailServer = emailServer;
}

public EmailInfo getEmailInfo() {
return emailInfo;
}

public void setEmailInfo(EmailInfo emailInfo) {
this.emailInfo = emailInfo;
}

}

发送Event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import cn.hutool.json.JSONObject;
import com.ruoyi.aiot.domain.email.EmailInfo;
import com.ruoyi.aiot.domain.email.EmailServer;
import com.ruoyi.aiot.domain.event.SendAttachmentEmailEvent;
import com.ruoyi.aiot.domain.event.SendSimpleEmailEvent;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class EmailHandler implements NotifyHandler{

//通知类型
private final String type = "Email";
//通知供应商
private final String provider = "EmailProvider";

@Resource
private ApplicationEventPublisher applicationEventPublisher;

@Override
public void initClient(JSONObject initMessage) throws Exception {
}

@Override
public void SendMessage(JSONObject sendMessage) {
EmailServer emailServer = sendMessage.get("emailServer", EmailServer.class);
EmailInfo emailInfo = sendMessage.get("emailInfo", EmailInfo.class);

if (StringUtils.isEmpty(emailInfo.getAttachmentPath())){
SendSimpleEmailEvent sendSimpleEmailEvent = new SendSimpleEmailEvent(this, emailServer, emailInfo);
applicationEventPublisher.publishEvent(sendSimpleEmailEvent);
}
SendAttachmentEmailEvent sendAttachmentEmailEvent = new SendAttachmentEmailEvent(this, emailServer, emailInfo);
applicationEventPublisher.publishEvent(sendAttachmentEmailEvent);
}
}

监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import com.alibaba.fastjson2.JSON;
import com.ruoyi.aiot.domain.email.EmailInfo;
import com.ruoyi.aiot.domain.email.EmailServer;
import com.ruoyi.aiot.domain.event.SendAttachmentEmailEvent;
import com.ruoyi.aiot.domain.event.SendSimpleEmailEvent;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.mail.*;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class MyEmilUtils {

private static final String CHARSET_UTF8 = "UTF-8";

@Async
@EventListener
public void pushSimpleEmail(SendSimpleEmailEvent sendSimpleEmailEvent) {
EmailServer server = sendSimpleEmailEvent.getEmailServer();
EmailInfo emailInfo = sendSimpleEmailEvent.getEmailInfo();

log.info("发送邮件:{},至邮件服务器:{}", JSON.toJSONString(emailInfo), JSON.toJSONString(server));
Email email = new SimpleEmail();
email.setHostName(server.getSmtpAddr());
email.setSmtpPort(server.getMailPort());
//用户名称和授权码
email.setAuthentication(server.getUserName(), server.getPassword());
email.setCharset(CHARSET_UTF8);
try {
email.addTo(emailInfo.getDestination());
email.setFrom(server.getUserName(), server.getSenderAccount());
email.setSubject(emailInfo.getSubject());
email.setMsg(emailInfo.getContent());
email.setSSLOnConnect(server.getSslFlag() == 0);
Map<String, String> map = new HashMap<>();
if (!StringUtils.isEmpty(emailInfo.getTitle())) {
map.put("主题", emailInfo.getTitle());
} else {
map.put("主题", emailInfo.getSubject());
}
email.setHeaders(map);
email.setSentDate(new Date());
email.send();
log.info("邮件:{},发送成功", JSON.toJSONString(emailInfo));
} catch (EmailException e) {
log.info("邮件发送失败message: {}", e.getMessage());
}
}

@Async
@EventListener
public void pushAttachmentEmail(SendAttachmentEmailEvent sendAttachmentEmailEvent) {
EmailServer server = sendAttachmentEmailEvent.getEmailServer();
EmailInfo emailInfo = sendAttachmentEmailEvent.getEmailInfo();

log.info("发送带附件的邮件:{},至邮件服务器:{}", JSON.toJSONString(emailInfo), JSON.toJSONString(server));
EmailAttachment attachment = new EmailAttachment();
attachment.setDescription(emailInfo.getAttachmentDescription());
attachment.setDisposition(EmailAttachment.ATTACHMENT);
attachment.setName(emailInfo.getAttachmentName());
attachment.setPath(emailInfo.getAttachmentPath());

MultiPartEmail email = new MultiPartEmail();
email.setHostName(server.getSmtpAddr());
email.setSmtpPort(server.getMailPort());
//用户名称和授权码
email.setAuthentication(server.getUserName(), server.getPassword());
email.setCharset(CHARSET_UTF8);
try {
email.addTo(emailInfo.getDestination());
if (server.getSslFlag() == 0) {
email.setSSLOnConnect(true);
} else {
email.setSSLOnConnect(false);
}
email.attach(attachment);
email.setFrom(server.getUserName(), server.getSenderAccount());
email.setSubject(emailInfo.getSubject());
email.setMsg(emailInfo.getContent());
Map<String, String> map = new HashMap<>();
map.put("主题", emailInfo.getTitle());
email.send();
email.setHeaders(map);
log.info("带附件的邮件发送成功:{}", JSON.toJSONString(attachment));
} catch (EmailException e) {
log.info("邮件发送失败:{},message:{}", e, JSON.toJSONString(attachment));
}
}
}

集成阿里云发送短信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- aliyun sms SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.2</version>
</dependency>

<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.2.6</version>
</dependency>

<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.21</version>
</dependency>

定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.Data;

import java.util.Map;

@Data
public class AliyunSmsInfo {

private String signName;

private String phoneNumbers;

private String templateCode;

}
1
2
3
4
5
6
7
8
9
10
11
import lombok.Data;

@Data
public class AliyunSmsServer {

private String accessKeyId;

private String accessKeySecret;

private String orginId;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsInfo;
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsServer;
import lombok.Data;
import org.springframework.context.ApplicationEvent;

import java.time.Clock;
import java.util.Map;

/**
* 定义监听事件
* 通过ApplicationContextAware我们可以把系统中所有ApplicationEvent传播给系统中所有的ApplicationListener。
* 因此,我们只需要构造好我们自己的ApplicationEvent和ApplicationListener,就可以在系统中实现相应的监听器。
*/

public class SendAliyunSmsEvent extends ApplicationEvent {

private AliyunSmsInfo aliyunSmsInfo;

private AliyunSmsServer aliyunSmsServer;

private Map aliyunSmsParams;

public SendAliyunSmsEvent(Object source, AliyunSmsServer aliyunSmsServer, AliyunSmsInfo aliyunSmsInfo ,Map map) {
super(source);
this.aliyunSmsInfo = aliyunSmsInfo;
this.aliyunSmsServer = aliyunSmsServer;
this.aliyunSmsParams = map;
}

public Map getAliyunSmsParams() {
return aliyunSmsParams;
}

public void setAliyunSmsParams(Map aliyunSmsParams) {
this.aliyunSmsParams = aliyunSmsParams;
}

public SendAliyunSmsEvent(Object source, Clock clock) {
super(source, clock);
}

public AliyunSmsInfo getAliyunSmsInfo() {
return aliyunSmsInfo;
}

public void setAliyunSmsInfo(AliyunSmsInfo aliyunSmsInfo) {
this.aliyunSmsInfo = aliyunSmsInfo;
}

public AliyunSmsServer getAliyunSmsServer() {
return aliyunSmsServer;
}

public void setAliyunSmsServer(AliyunSmsServer aliyunSmsServer) {
this.aliyunSmsServer = aliyunSmsServer;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import cn.hutool.json.JSONObject;
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsInfo;
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsServer;
import com.ruoyi.aiot.domain.email.EmailServer;
import com.ruoyi.aiot.domain.event.SendAliyunSmsEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.jws.Oneway;
import java.util.Map;
@Component
public class AliyunSmsHandler implements NotifyHandler{
//通知类型
private final String type = "ShortMessage";
//通知供应商
private final String provider = "ShortMessageAlibabaServer";

@Resource
private ApplicationEventPublisher applicationEventPublisher;

@Override
public void initClient(JSONObject initMessage) throws Exception {
}
@Override
public void SendMessage(JSONObject sendMessage) {
AliyunSmsServer server = sendMessage.getBean("configuration", AliyunSmsServer.class);
AliyunSmsInfo info = sendMessage.getBean("template",AliyunSmsInfo.class);
Object templateParam = sendMessage.get("templateParam");
Map<String,String> map = new JSONObject(templateParam).toBean(Map.class);
SendAliyunSmsEvent sendAliyunSmsEvent = new SendAliyunSmsEvent(this, server, info, map);
applicationEventPublisher.publishEvent(sendAliyunSmsEvent);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import cn.hutool.json.JSONObject;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.tea.TeaException;
import com.ruoyi.common.exception.ServiceException;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* 阿里云异步短信工具类
* @author yecao
*/
public class AliyunSmsUtils {

public static final Logger log = LoggerFactory.getLogger(AliyunSmsUtils.class);

private static volatile com.aliyun.dysmsapi20170525.Client instance;

public static void initClient(@NonNull String accessKeyId, @NonNull String accessKeySecret) throws Exception {
if (null == instance){
synchronized (AliyunSmsUtils.class){
if (null == instance){
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
// 您的 AccessKey ID
.setAccessKeyId(accessKeyId)
// 您的 AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// 访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
instance = new com.aliyun.dysmsapi20170525.Client(config);
}
}
}
}


public static void sendSms(@NonNull String signName, @NonNull String phoneNumbers, @NonNull String templateCode,
@NonNull Map templateParam) {
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest();
sendSmsRequest.setPhoneNumbers(phoneNumbers);
sendSmsRequest.setTemplateCode(templateCode);
sendSmsRequest.setTemplateParam(new JSONObject(templateParam).toString());
sendSmsRequest.setSignName(signName);
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
try {
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse sendSmsResponse = instance.sendSmsWithOptions(sendSmsRequest, runtime);
log.info("消息已发送至:{},响应信息:{}",phoneNumbers,sendSmsResponse.getBody().getMessage());
if (!"OK".equals(sendSmsResponse.getBody().getMessage())){
throw new ServiceException(sendSmsResponse.getBody().getMessage());
}
} catch (TeaException error) {
// 如有需要,请打印 error
com.aliyun.teautil.Common.assertAsString(error.message);
} catch (Exception errors) {
TeaException error = new TeaException(errors.getMessage(), errors);
// 如有需要,请打印 error
com.aliyun.teautil.Common.assertAsString(error.message);
throw new ServiceException(error.message);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsInfo;
import com.ruoyi.aiot.domain.aliyunSms.AliyunSmsServer;
import com.ruoyi.aiot.domain.event.SendAliyunSmsEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Map;
@Slf4j
@Component
public class MyAliyunSmsUtils {
@Async
@EventListener
public void sendAliyunSms(SendAliyunSmsEvent sendAliyunSmsEvent){
AliyunSmsServer aliyunSmsServer = sendAliyunSmsEvent.getAliyunSmsServer();
AliyunSmsInfo aliyunSmsInfo = sendAliyunSmsEvent.getAliyunSmsInfo();
Map aliyunSmsParams = sendAliyunSmsEvent.getAliyunSmsParams();
try {
AliyunSmsUtils.initClient(aliyunSmsServer.getAccessKeyId(),aliyunSmsServer.getAccessKeySecret());
AliyunSmsUtils.sendSms(aliyunSmsInfo.getSignName(),
aliyunSmsInfo.getPhoneNumbers(),
aliyunSmsInfo.getTemplateCode(),
aliyunSmsParams);
} catch (Exception e) {
e.printStackTrace();
}
}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!