仓库地址
MailListen: 监听邮箱未读文件,对满足条件的邮件处理转发给webhook地址 (gitee.com)
欢迎一起交流学习
业务背景
当服务器的告警日志信息发送到邮箱时,我们无法及时查收,这可能导致问题扩大,需要一个全天候的服务监听邮箱的邮件
服务流程
定时执行邮件列表过滤流程,当检测到是未读的告警日志邮件时,将邮件富文本转为普通文本,经过post json方式推送至webhook地址(这里使用飞书机器人)
这样的做法可以保证日志信息的完整,并且在服务宕机时,还可以在重启时继续无损运行
邮件列表过滤
将符合条件的邮件(主题包含yml文件中配置的filterTitle字符串)的部件组合为一个String字符串,注意这里无法处理附件
public void fetchMail() throws InterruptedException {
if (OPEN) {
System.out.println("邮件转发已被关闭");
return;
}
if(LOCK.tryLock(1, TimeUnit.SECONDS)){
try {
openFolder();
int size = folder.getMessageCount();
System.out.println("邮件个数:" + size);
if (skip != null && skip && messageLastSize != null && messageLastSize == size) {
System.out.println("跳过未读邮件检测");
} else {
messageLastSize = size;
for (int i = 0; i < size; i++) {
Message message = folder.getMessage(size - i);
String subject = message.getSubject();
//判断主题包含WGCLOUD
if (!subject.contains(filterTitle)) {
continue;
}
if (message.getFlags().contains(Flags.Flag.SEEN)) {
continue;
}
System.out.println("未读邮件为:");
String from = message.getFrom()[0].toString();
Date date = message.getSentDate();
System.out.println("\tFrom: \t" + from.replaceAll("<.+?>", ""));
System.out.println("\tSubject: \t" + subject);
System.out.println("\tDate: \t" + date);
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
StringBuilder text = new StringBuilder();
// 遍历部件列表
for (int j = 0; j < mimeMultipart.getCount(); j++) {
BodyPart bodyPart = mimeMultipart.getBodyPart(j);
// 检查部件的内容类型
if (bodyPart.isMimeType("text/plain")) {
text.append((String) bodyPart.getContent());
} else if (bodyPart.isMimeType("text/html")) {
text.append((String) bodyPart.getContent());
}
}
System.out.println(sendFeiShuBot.sendMessageToBot(text.toString()));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("接收完毕!");
System.out.println("释放ReentrantLock锁");
LOCK.unlock();
}
}else{
System.out.println("ReentrantLock锁被占用,跳过此次定时任务");
}
}
发送webhook
需要发送的文本经过正则过滤处理后,post给webHookUrl地址
regexStr在yml文件中配置
public String sendMessageToBot(String text){
System.out.println("接收到发送任务:" + text);
text = text.replaceAll("[\\\\]", " ");
text = text.replaceAll("[\b\r\n\t\"]", " ");
text = text.replaceAll("<[^>]+>"," ");
text = text.replaceAll(regexStr,"");
System.out.println("处理后的文本:" + text);
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
String json =
"{\n" +
" \"msg_type\": \"text\",\n" +
" \"content\":{\n" +
" \"text\":\"" + text +"\"\n" +
" }\n" +
"}";
RequestBody body = RequestBody.create(mediaType, json);
Request request = new Request.Builder()
.url(HOST)
.post(body)
.build();
try {
System.out.println("已发送请求:" + json);
Response response = client.newCall(request).execute();
return Objects.requireNonNull(response.body()).string().contains("success") ?"success": Objects.requireNonNull(response.body()).string();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
定时与接口
Appilcation的内容包含下面的接口和定时任务
需要更复杂处理的可以自己按照需求修改
@Bean
public TaskScheduler scheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.setThreadNamePrefix("MailServer");
return taskScheduler;
}
@Resource
private ListenMail listenMail;
@Scheduled(cron = "0/10 * * * * ? ")
public void mailHandle(){
listenMail.fetchMail();
}
@RequestMapping("/openListen")
public String openMailListen(){
return listenMail.startListen()?"邮件转发已开启":"邮件转发开启失败";
}
@RequestMapping("/closeListen")
public String closeMailListen(){
return listenMail.stopListen()?"邮件转发已关闭":"邮件转发关闭失败";
}
@RequestMapping("/statusListen")
public String getMailStatus(){
return !listenMail.getOpen()?"邮箱监听中":"邮箱静默中";
}
@RequestMapping("/mailConfig")
public String configMail(){
return listenMail.getConfig();
}
@RequestMapping("/checkPassword")
public String checkPassword(String password){
return listenMail.checkPassword(password)?"密码一致":"密码不一致";
}
@RequestMapping("/checkWebHook")
public String checkWebHook(String url){
return listenMail.checkWebHook(url)?"webhook地址一致":"webhook地址不一致";
}
yml 配置
服务运行在3333端口,下面host为你邮箱的接受邮件服务器地址
这里以qq邮箱的imap和飞书机器人为例
注意授权码需要在邮箱设置中开启,详细可以百度一下
skip参数用于判断是否在邮件数量不变时跳过检测,这一步可以在邮件数量大的情况下大大节省时间
filterTitle参数用于判断邮箱是否是所需邮件,它会用于与邮件的主题对比,如果包含则是
server:
port: 3333
spring:
application:
name: listenMailServer
listen:
mail:
host: imap.qq.com
port: 993
usermail: axinfree@qq.com
password: xxxx #邮箱授权码,不是账号密码
protocol: imap
folder: INBOX #获取的邮箱文件夹,如未对邮箱做特别设置,默认INBOX即可
filterTitle: "WGCLOUD" #邮件检索条件,包含这段字符串的未读邮箱才会被提取数据并发送
skip: true #ture表示开启,当开启时,若检测到邮件数量没有变化,会跳过检测未读邮件的步骤
send:
webHookUrl: https://open.feishu.cn/open-apis/bot/v2/hook/xxxx
regexStr: "WGCLOUD 敬上" #发送webhook之前被替换摘除的文本 如: regexStr = “2” ‘123465’-> ’13465‘