一、需求分析
在分布式环境下,需要使用webservice发送邮件,然后实现每隔一段时间定时发送待发邮件的功能。
二、基本的配置信息
1. 需要配置邮件服务器的相关信息, 配置WebService地址等。
# 邮件基础数据配置
fec-peripheral-service.webserviceaddress=
fec-peripheral-service.mailSysId=
fec-peripheral-service.mailUid=
fec-peripheral-service.mailServiceName=
#fec-peripheral-service.mailFrom=
fec-peripheral-service.mailFrom=
fec-peripheral-service.mailNoName=
需要的依赖:
<!--webservice 调用需要的jar -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>axis</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>axis-jaxrpc</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-discovery</groupId>
<artifactId>commons-discovery</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>axis-saaj</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.4</version>
</dependency>
<!--webservice 调用需要的jar -->
2. 实现发邮件
将邮件的内容以XML的形式来发送一个HTTP请求:
@Override
public processresult sendMail(sysInstMailLog sysInstMailLog) {
org.apache.axis.client.Service service = new org.apache.axis.client.Service();
Call call = null;
try {
call = (Call) service.createCall();
call.setTargetEndpointAddress(""+sysInstMailLog.getWebServiceAddress()+"");
call.setOperationName(QName.valueOf("execute"));
} catch (ServiceException e1) {
e1.printStackTrace();
}
//发送的XML不能包括多余的空格,否则WSP平台会系统错误 参数值不能有null,否则邮件发送会失败
String request =
"<?xml version='1.0' encoding='GBK'?>" +
"<request version='1.0'>" +
"<system>" +
"<sysid>"+sysInstMailLog.getMailSysId()+"</sysid>" +
"<uid>"+sysInstMailLog.getMailUid()+"</uid>" +
"<servicename>"+sysInstMailLog.getMailServiceName()+"</servicename>" +
"</system>" +
"<query>" +
"<filed name='MAILNO' type='String'>" + sysInstMailLog.getMailNo() + "</filed>" +
"<filed name='SUBJECT' type='String'>" + sysInstMailLog.getMailSubject() + "</filed>" +
"<filed name='CONTENT' type='String'>" + sysInstMailLog.getMailContent() + "</filed>" +
"<filed name='FROM' type='String'>" + sysInstMailLog.getMailFrom() + "</filed>" +
//<!--邮件接收人,多个邮件接收人以分号分隔 -->
"<filed name='TO' type='String'>" + sysInstMailLog.getMailTo() + "</filed>" +
"<filed name='CC' type='String'>" + sysInstMailLog.getMailCc() + "</filed>" +
"<filed name='BCC' type='String'>" + sysInstMailLog.getMailBcc() + "</filed>" +
//文件路径需要包括文件名
"<filed name='FILEPATH' type='String'>" + sysInstMailLog.getMailFilePath() + "</filed>" +
"<filed name='FILENAME' type='String'>" + sysInstMailLog.getMailFileName() + "</filed>" +
"<filed name='MEMO' type='String'>" + sysInstMailLog.getMailMemo() + "</filed>" +
"<filed name='IMMEDIATE' type='String'>" + sysInstMailLog.getMailImmediate() + "</filed>" +
"<filed name=\"POLICYNUMBER\" type=\"String\">" + sysInstMailLog.getPolicyNumber() + "</filed>" +
"</query>" +
"</request>";
try {
String response = (String) call.invoke(new Object[]{request});
if (null != response) {
System.out.println("WebService Response:"+response);
processResult=ParaseXml.GetXmlResultCodeNodeText(response,sysInstMailLog);
}
} catch (Exception e) {
//写log
e.printStackTrace();
}
return processResult;
}
三、核心实现
将要发送邮件的内容以及信息以根据平台提供的一个xml文件的形式包起来,然后以object[]数组的形式来调用,然后通过call调用即可将内容以xml报文的发送给webservice平台, 具体的方式为是通过 axis的客户端的service调用createCall()方法创建一个call,然后使用设置设置targetEndpointAddress()属性值,最后将xml报文的内容放在call.invoke(new Object[]{报文内容}):
import javax.xml.rpc.Call;
org.apache.axis.client.Service service = new org.apache.axis.client.Service();
Call call = null;
try {
call = (Call) service.createCall();
call.setTargetEndpointAddress(""+sysInstMailLog.getWebServiceAddress()+"");
call.setOperationName(QName.valueOf("execute"));
String response = (String) call.invoke(new Object[]{request});
远程调用webservice服务器发送邮件消息的完整代码:
package com.hand.hcf.app.peripheral.mail.service.Impl;
import com.hand.hcf.app.mdata.client.workflow.WorkflowInterface;
import com.hand.hcf.app.mdata.client.workflow.dto.ApprovalErrorDataCO;
import com.hand.hcf.app.peripheral.mail.domain.sysInstMailLog;
import com.hand.hcf.app.peripheral.mail.dto.processresult;
import com.hand.hcf.app.peripheral.mail.persistence.SysInstMailLogMapper;
import com.hand.hcf.app.peripheral.mail.service.MailService;
import com.hand.hcf.app.peripheral.mail.utils.ParaseXml;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.ServiceException;
@Service
public class MailServiceImpl implements MailService {
/*自动装配 数据源事务管理
* 前提是容器中有可装配的事务管理器 如果没有需要注入
* */
private final DataSourceTransactionManager transactionManager;
private processresult processResult;
@Autowired
private WorkflowInterface workflowInterface;
public MailServiceImpl(DataSourceTransactionManager transactionManager) {
this.transactionManager=transactionManager;
}
@Autowired
private SysInstMailLogMapper sysInstMailLogMapper;
ParaseXml ParaseXml=new ParaseXml();
@Override
public int addSysInstMailLogRecord(sysInstMailLog sysInstMailLog) {
StringBuffer sb=new StringBuffer();
ApprovalErrorDataCO c0=new ApprovalErrorDataCO();
c0.setApplicationName("fec-peripheral");
sb.append("系统Id:"+sysInstMailLog.getMailSysId());
try{
sysInstMailLogMapper.add_sysInstMailLog_record(sysInstMailLog);
}catch (Exception e){
sb.append("错误:"+e.toString()+"报账单号为:"+sysInstMailLog.getMail_reportnumber());
}
String str=sb.toString();
c0.setErrorMsg(str);
if (str.contains("错误")){
workflowInterface.createApprovalErrorData(c0);
}
return sysInstMailLog.getId();
}
@Override
public processresult sendMail(sysInstMailLog sysInstMailLog) {
org.apache.axis.client.Service service = new org.apache.axis.client.Service();
Call call = null;
try {
call = (Call) service.createCall();
call.setTargetEndpointAddress(""+sysInstMailLog.getWebServiceAddress()+"");
call.setOperationName(QName.valueOf("execute"));
} catch (ServiceException e1) {
e1.printStackTrace();
}
//发送的XML不能包括多余的空格,否则WSP平台会系统错误 参数值不能有null,否则邮件发送会失败
String request =
"<?xml version='1.0' encoding='GBK'?>" +
"<request version='1.0'>" +
"<system>" +
"<sysid>"+sysInstMailLog.getMailSysId()+"</sysid>" +
"<uid>"+sysInstMailLog.getMailUid()+"</uid>" +
"<servicename>"+sysInstMailLog.getMailServiceName()+"</servicename>" +
"</system>" +
"<query>" +
"<filed name='MAILNO' type='String'>" + sysInstMailLog.getMailNo() + "</filed>" +
"<filed name='SUBJECT' type='String'>" + sysInstMailLog.getMailSubject() + "</filed>" +
"<filed name='CONTENT' type='String'>" + sysInstMailLog.getMailContent() + "</filed>" +
"<filed name='FROM' type='String'>" + sysInstMailLog.getMailFrom() + "</filed>" +
//<!--邮件接收人,多个邮件接收人以分号分隔 -->
"<filed name='TO' type='String'>" + sysInstMailLog.getMailTo() + "</filed>" +
"<filed name='CC' type='String'>" + sysInstMailLog.getMailCc() + "</filed>" +
"<filed name='BCC' type='String'>" + sysInstMailLog.getMailBcc() + "</filed>" +
//文件路径需要包括文件名
"<filed name='FILEPATH' type='String'>" + sysInstMailLog.getMailFilePath() + "</filed>" +
"<filed name='FILENAME' type='String'>" + sysInstMailLog.getMailFileName() + "</filed>" +
"<filed name='MEMO' type='String'>" + sysInstMailLog.getMailMemo() + "</filed>" +
"<filed name='IMMEDIATE' type='String'>" + sysInstMailLog.getMailImmediate() + "</filed>" +
"<filed name=\"POLICYNUMBER\" type=\"String\">" + sysInstMailLog.getPolicyNumber() + "</filed>" +
"</query>" +
"</request>";
try {
String response = (String) call.invoke(new Object[]{request});
if (null != response) {
System.out.println("WebService Response:"+response);
processResult=ParaseXml.GetXmlResultCodeNodeText(response,sysInstMailLog);
}
} catch (Exception e) {
//写log
e.printStackTrace();
}
return processResult;
}
@Override
public void HandlingMailDeliveryResults(processresult processresult) {
/*配置事务策略*/
DefaultTransactionDefinition def=new DefaultTransactionDefinition();
def.setName("planeOne-transation");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*设置状态点*/
TransactionStatus transactionStatus=transactionManager.getTransaction(def);
String Mail_RESULTCODE=processresult.getMailResultCode();
try {
//do something
/*
* SN01 邮件发送成功
* FN01 邮件发送失败
* B01 收件人被拉黑名单, 本次不作发送处理
* E01 邮件发送服务异常
* OTHER 未知错误
* */
sysInstMailLogMapper.updateSysInstMaillogByID(processresult);
//手动提交
transactionManager.commit(transactionStatus);
}catch (Exception ex){
//手动回滚 写log
transactionManager.rollback(transactionStatus);
}
}
}
邮件记录表(sys_init_log)的结构:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_inst_maillog
-- ----------------------------
DROP TABLE IF EXISTS `sys_inst_maillog`;
CREATE TABLE `sys_inst_maillog` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`mail_no` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮件编号',
`mail_sysid` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '应用系统ID',
`mail_uid` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '业务系统ID',
`mail_servicename` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '服务名',
`mail_subject` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '邮件主题',
`mail_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '邮件正文',
`mail_from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '发件人',
`mail_to` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '收件人',
`mail_cc` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '抄送地址',
`mail_bcc` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '密送地址',
`mail_filepath` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '附件路径',
`mail_filename` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '附件名称',
`mail_memo` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '备注信息',
`mail_tm_create` datetime(0) NULL DEFAULT NULL COMMENT '邮件创建时间',
`mail_immediate` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '1' COMMENT '是否立即发送(1立即发送)',
`mail_status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '处理标识 1正常|2异常|0系统异常',
`mail_errormassage` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '错误信息正常(无)异常(有)',
`mail_resultcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '处理结果SN01 邮件发送成功|FN01 邮件发送失败|B01 收件人被拉黑名单, 本次不作发送处理|E01 邮件发送服务异常|OTHER 未知错误',
`mail_resultdesc` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '处理结果 DESC',
`mailservice_tm_return` datetime(0) NULL DEFAULT NULL COMMENT '邮件服务返回结果时间',
`mail_reportnumber` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '报账单号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `mail_reportnumber`(`mail_reportnumber`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1671 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '新费控系统邮件日志表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
然后调用发送接口,成功后会将结果回写到表里,成功或者失败回写到表里的mail_resultdesc字段里。
使用XxlJob设置时间段,每隔一段时间遍历该表sys_init_log。每次只查出mail_resultdesc为null的记录:
select id,mail_no,mail_sysid,mail_uid,mail_servicename,mail_subject,mail_content,mail_from,mail_to,
mail_cc,mail_bcc,mail_filepath,mail_filename,mail_memo,mail_immediate,mail_tm_create from sys_inst_maillog where mail_resultdesc is null
Job定时任务:
package com.hand.hcf.app.peripheral.job;
import com.hand.hcf.app.peripheral.mail.domain.sysInstMailLog;
import com.hand.hcf.app.peripheral.mail.service.EmailLogQueryService;
import com.hand.hcf.app.peripheral.mail.service.EmailSendService;
import com.hand.hcf.app.peripheral.mail.service.Impl.MailServiceImpl;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Component
@JobHandler("sendEmail")
public class FecSendEmail extends IJobHandler {
@Autowired
private EmailLogQueryService emailLogQueryService;
@Autowired
private EmailSendService emailSendService;
/**
* glcs 定时任务
* @param strings 参数
* @return 结果
*/
@Override
@Transactional
public ReturnT<String> execute(String... strings) throws Exception {
List<sysInstMailLog> sysInstMailLogList=emailLogQueryService.queryEmailLog();
for(sysInstMailLog sysInstMailLog:sysInstMailLogList){
emailSendService.sendEmail(sysInstMailLog);
}
return ReturnT.SUCCESS;
}
}
执行的sendEmail方法就是贴出来的第一个代码。
由于我们的job的admin单独打包成了一个服务,每个服务需要调用时需要额外添加配置:
job配置类:
package com.hand.hcf.app.mdata.job;
import com.xxl.job.core.executor.XxlJobExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobExecutor xxlJobExecutor = new XxlJobExecutor();
xxlJobExecutor.setAdminAddresses(adminAddresses);
xxlJobExecutor.setAppName(appName);
xxlJobExecutor.setIp(ip);
xxlJobExecutor.setPort(port);
xxlJobExecutor.setAccessToken(accessToken);
xxlJobExecutor.setLogPath(logPath);
return xxlJobExecutor;
}
}
xxl.job.admin.addresses = http://127.0.0.1:8081/xxl-job
#xxl.executor.logpath = /home/job/logs
xxl.job.executor.appname = mdata
xxl.job.executor.ip=
xxl.job.executor.port=9991
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler/
xxl.job.accessToken=
xxl.job.executor.logretentiondays= -1
主要是Job下的admin服务,在Xxl官网可以拿到全部的开放源码:
四、自己动手搭建XxlJob
我们也可以自己从Xxl的官网下载源码,自己搭建一个分布式的定时任务平台,搭出来看到的效果就是这样:
官网源码地址: GitHub - xuxueli/xxl-job: A distributed task scheduling framework.(分布式任务调度平台XXL-JOB)
按照给的文档,搭建完毕后,直接访问如下地址即可出现主页,http://localhost:8080/xxl-job-admin/ , 默认用户名为: admin, 密码为:123456。
在任务管理里,可以定义自己的定时器,配置好参数后,即可一键执行。