定时邮件发送汇总——思路

使用quartz任务调度框架,首先流程分为2个部分
第一部分为:用户手动新增任务
第二部分为:tomcat重启问题

从10月开始,到现在实现,无数大坑,写了改,改了写,期间也做了其他事情。不知为过。

第一部分为:用户手动新增任务

第二部分为:tomcat重启问题

tomcat重启之后,继续跑上面用户手动已经新增的任务流程,不需要用户在此进行新增任务,实际原理,就是新增。只不过代替人,程序自动新增,其中任务相关信息,已经在数据库中存储了,这时候,就需要监听器了。

实现思路

1. 定时任务管理Java类

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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
public class QuartzManager {
private static SchedulerFactory gSchedulerFactory = new StdSchedulerFactory(); //创建一个SchedulerFactory工厂实例
private static String JOB_GROUP_NAME = "FLYME_JOBGROUP_NAME"; //任务组
private static String TRIGGER_GROUP_NAME = "FLYME_TRIGGERGROUP_NAME"; //触发器组

/**
* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
*
* @param jobName 任务名
* @param cls 任务
* @param time 时间设置,参考quartz说明文档
*/
public static void addJob(String jobName, Class<? extends Job> cls, String time) {
try {
//通过SchedulerFactory构建Scheduler对象
Scheduler sched = gSchedulerFactory.getScheduler();
//用于描叙Job实现类及其他的一些静态信息,构建一个作业实例
JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();
CronTrigger trigger = (CronTrigger) TriggerBuilder
//创建一个新的TriggerBuilder来规范一个触发器
.newTrigger()
//给触发器起一个名字和组名
.withIdentity(jobName, TRIGGER_GROUP_NAME)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
// 启动
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名 (带参数)
*
* @param jobName 任务名
* @param cls 任务
* @param time 时间设置,参考quartz说明文档
*/
public static void addJob(String jobName, Class<? extends Job> cls, String time, Map<String, Object> parameter) {
try {
//通过SchedulerFactory构建Scheduler对象
Scheduler sched = gSchedulerFactory.getScheduler();
//用于描叙Job实现类及其他的一些静态信息,构建一个作业实例
JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(jobName, JOB_GROUP_NAME).build();
//传参数
jobDetail.getJobDataMap().put("parameterList", parameter);
CronTrigger trigger = (CronTrigger) TriggerBuilder
//创建一个新的TriggerBuilder来规范一个触发器
.newTrigger()
//给触发器起一个名字和组名
.withIdentity(jobName, TRIGGER_GROUP_NAME)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
// 启动
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 添加一个定时任务
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass 任务
* @param time 时间设置,参考quartz说明文档
*/
public static void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class<? extends Job> jobClass,
String time) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
// 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
// 触发器
CronTrigger trigger = (CronTrigger) TriggerBuilder
.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
// 启动
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 添加一个定时任务 (带参数)
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass 任务
* @param time 时间设置,参考quartz说明文档
*/
public static void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class<? extends Job> jobClass,
String time, Map<String, Object> parameter) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
// 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
//传参数
jobDetail.getJobDataMap().put("parameterList", parameter);
// 触发器
CronTrigger trigger = (CronTrigger) TriggerBuilder
.newTrigger()
.withIdentity(triggerName, triggerGroupName)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
sched.scheduleJob(jobDetail, trigger);
if (!sched.isShutdown()) {
// 启动
sched.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
*
* @param jobName 任务名
* @param time 新的时间设置
*/
public static void modifyJobTime(String jobName, String time) {
try {
//通过SchedulerFactory构建Scheduler对象
Scheduler sched = gSchedulerFactory.getScheduler();
//通过触发器名和组名获取TriggerKey
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
//通过TriggerKey获取CronTrigger
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
//通过任务名和组名获取JobKey
JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
JobDetail jobDetail = sched.getJobDetail(jobKey);
Class<? extends Job> objJobClass = jobDetail.getJobClass();
removeJob(jobName);
addJob(jobName, objJobClass, time);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 修改一个任务的触发时间
*
* @param triggerName 任务名称
* @param triggerGroupName 传过来的任务名称
* @param time 更新后的时间规则
*/
public static void modifyJobTime(String triggerName, String triggerGroupName, String time) {
try {
//通过SchedulerFactory构建Scheduler对象
Scheduler sched = gSchedulerFactory.getScheduler();
//通过触发器名和组名获取TriggerKey
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
//通过TriggerKey获取CronTrigger
CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
if (trigger == null) return;
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(trigger.getCronExpression());
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(time)) {
//重新构建trigger
trigger = (CronTrigger) trigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(scheduleBuilder)
.withSchedule(CronScheduleBuilder.cronSchedule(time))
.build();
//按新的trigger重新设置job执行
sched.rescheduleJob(triggerKey, trigger);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
*
* @param jobName 任务名称
*/
public static void removeJob(String jobName) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
//通过触发器名和组名获取TriggerKey
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, TRIGGER_GROUP_NAME);
//通过任务名和组名获取JobKey
JobKey jobKey = JobKey.jobKey(jobName, JOB_GROUP_NAME);
// 停止触发器
sched.pauseTrigger(triggerKey);
// 移除触发器
sched.unscheduleJob(triggerKey);
// 删除任务
sched.deleteJob(jobKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 移除一个任务
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
*/
public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
//通过触发器名和组名获取TriggerKey
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
//通过任务名和组名获取JobKey
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
// 停止触发器
sched.pauseTrigger(triggerKey);
// 移除触发器
sched.unscheduleJob(triggerKey);
// 删除任务
sched.deleteJob(jobKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 启动所有定时任务
*/
public static void startJobs() {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
sched.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 关闭所有定时任务
*/
public static void shutdownJobs() {
try {
Scheduler sched = gSchedulerFactory.getScheduler();
if (!sched.isShutdown()) {
sched.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

2.监听器 SchedulerInitListener

当启动tomcat,spring容器会创建监听器

大概思路,

  1. 首先新建一个类,集成ApplicationListener,并且重写onApplicationEvent()方法。
  2. 查询job表所有的数据,返回结果集。
  3. 根据返回的数据,进行for循环,对其中的status字段进行判断,如果是Y,那就说明任务需要跑起来,否则。
  4. 在此对返回的数据中的job_type字段,也就是job类型进行判断,判断的意义在于,判断该任务是否需要参数,并且调用的方法会随之改变,也就是addJob()中的参数不同。
  5. 随后,调用addJob方法中,进行对job类调用,该job类是需要继承org.quartz.Job,并且重写execute()方法。

applicationContext-dao.xml添加

1
2
3
<!-- 当Spring容器启动完成后执行下面的这个Bean -->
<bean class="com.asiainfo.intf.listener.SchedulerInitListener">
</bean>

监听器

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
public class SchedulerInitListener implements ApplicationListener<ContextRefreshedEvent> {

// 任务表中 任务状态 字段,Y为有效
private static final String STATUS_USABLE = "Y";
// 任务表中 任务类型 根据类型进行 调用不同的 addJob方法,也就是带不带传参方法
private static final String JOB_TYPE = "sendEmail";
// 进行类名称的构造
private static final String PACKAGE = "com.flyme..job.";
// 打印日志log
private static final Logger log = LoggerFactory.getLogger(SchedulerInitListener.class);

//
@Autowired
private ISendEmailScheduleJobService sendEmailScheduleService;

@Autowired
private EmailAccountsMapper emailAccountsMapper;

/**
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass class
* @param cron 时间设置,参考quartz说明文档
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//只执行一次
if (event.getApplicationContext().getParent() == null) {
List<Map<String, Object>> scheduleList = sendEmailScheduleService.queryAuditSchedule();
if (scheduleList.size() > 0) {
for (Map<String, Object> map : scheduleList) {
//启动所有可用的定时调度任务
//获取其中的状态字段进行比较,
//如果状态为Y,就执行if中语句
if (STATUS_USABLE.equals(map.get("STATUS"))) {
// 如果JOB_TYPE的字段是sendEmail,需要执行addjob()传参数方法
// 传参数的目的,让job类,读取数据库的邮箱收件人和对应模板配置
// 否则,执行addJob()无参数方法
if (JOB_TYPE.equals(map.get("JOB_TYPE"))) {
AuditEmailAccounts auditEmailAccounts = new AuditEmailAccounts();
auditEmailAccounts.setScheduleInfoJobName((String) map.get("JOB_NAME"));
List<Map<String, Object>> mapList = emailAccountsMapper.selectByConditions(auditEmailAccounts);
try {
//反射获取指定要运作的class
Class<String> schedulerClass = (Class<String>) Class.forName(PACKAGE + (String) map.get("JOB_CLASS"));
// 构造job需求参数
Map<String, Object> parameter = new HashMap<>(16);
parameter.put("JOB_NAME", map.get("JOB_NAME"));
SchedulerManager.addJob((String) map.get("JOB_NAME"),
(String) map.get("JOB_GROUP_NAME"), (String) map.get("TRIGGER_NAME"),
(String) map.get("TRIGGER_GROUP_NAME"), schedulerClass, (String) map.get("CRON"), parameter);
} catch (Exception e) {
log.error("任务:" + map.get("JOB_NAME") + "启动失败,失败原因是:" + e.getMessage(), e);
}
} else {
try {
//反射获取指定要运作的class
Class<String> schedulerClass = (Class<String>) Class.forName(PACKAGE + (String) map.get("JOB_CLASS"));
SchedulerManager.addJob((String) map.get("JOB_NAME"),
(String) map.get("JOB_GROUP_NAME"), (String) map.get("TRIGGER_NAME"),
(String) map.get("TRIGGER_GROUP_NAME"), schedulerClass, (String) map.get("CRON"));
} catch (Exception e) {
log.error("任务:" + map.get("JOB_NAME") + "启动失败,失败原因是:" + e.getMessage(), e);
}
}

}
}
}
}
}

}

3.编写job类

大概思路

说白了,还是addJob方法调用的时候,传递参数大小的不同,进行job类的编写,这样才能应对下面2个问题。

  1. 当用户新增任务
  2. tomcat自启后,进行读取数据库中存入的信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class SendEmailJob implements Job {
private static final Logger log = LoggerFactory.getLogger(SendEmailJob.class);

@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取addJob()方法,传输过来的参数
Map<String, Object> pd = (Map<String, Object>) context.getJobDetail().getJobDataMap().get("parameterList");

// 用户不重新配置邮件时,读取数据库中存入的信息
// 如果传输过来的parmer参数的大小为1,说明需要读取数据库中的配置,反之,是用户新增任务
if (1 == pd.size()) {
// 进行查询该任务所关联的所有信息
// 并且依次定时发送邮件
} else {
// 说明是用户提交过来的信息,包括邮件人的信息
// 依次定时发送邮件
}
}
}

4.job类中注入对象,SpringContextUtil编写

操作spring容器,获取bean对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SpringContextUtil implements ApplicationContextAware  {

private static ApplicationContext context;

@Override
@SuppressWarnings("static-access" )
public void setApplicationContext(ApplicationContext contex)
throws BeansException {
context = contex;
}
public static Object getBean(String beanName){
return context.getBean(beanName);
}

public static String getMessage(String key){
return context.getMessage(key, null, Locale.getDefault());
}
}

余有不足,望指出。