# MailListen **Repository Path**: dftdla/mail-listen ## Basic Information - **Project Name**: MailListen - **Description**: 监听邮箱未读文件,对满足条件的邮件处理转发给webhook地址 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-08-22 - **Last Updated**: 2024-11-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: mail, Java, webhook ## README # 业务背景 ​ 当服务器的告警日志信息发送到邮箱时,我们无法及时查收,这可能导致问题扩大,需要一个全天候的服务监听邮箱的邮件 # 服务流程 ​ 定时执行邮件列表过滤流程,当检测到是未读的告警日志邮件时,将邮件富文本转为普通文本,经过post json方式推送至webhook地址(这里使用飞书机器人) ​ 这样的做法可以保证日志信息的完整,并且在服务宕机时,还可以在重启时继续无损运行 ## 邮件列表过滤 ​ 将符合条件的邮件(主题包含yml文件中配置的filterTitle字符串)的部件组合为一个String字符串,注意这里无法处理附件 ```java public void fetchMail() { if(OPEN){ System.out.println("邮件转发已被关闭"); return; } synchronized (OPEN){ try { if(!(folder!=null&&folder.isOpen())){ 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","")); 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 { try { if (folder != null) { folder.close(false); } if (store != null) { store.close(); } } catch (MessagingException e) { e.printStackTrace(); } } System.out.println("接收完毕!"); } } ``` ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) ## 发送webhook ​ 需要发送的文本经过正则过滤处理后,post给webHookUrl地址 ​ regexStr在yml文件中配置 ```java 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; } } ``` ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) ## 定时与接口 ​ Appilcation的内容包含下面的接口和定时任务 ​ 需要更复杂处理的可以自己按照需求修改 ```java @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地址不一致"; } ``` ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) ## yml 配置 ​ 服务运行在3333端口,下面host为你邮箱的接受邮件服务器地址 ​ 这里以qq邮箱的imap和飞书机器人为例 ​ 注意授权码需要在邮箱设置中开启,详细可以百度一下 ​ skip参数用于判断是否在邮件数量不变时跳过检测,这一步可以在邮件数量大的情况下大大节省时间 ​ filterTitle参数用于判断邮箱是否是所需邮件,它会用于与邮件的主题对比,如果包含则是 ```javascript 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‘ ``` ![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==) # 效果图 ![输入图片说明](deea9109e2ed45d3991866308b9f03fb.png)