Java项目启动成功、失败信息实时反馈提醒问题(邮件或者短信)
作者:程序猿杨鲍
在开发的流程中必须涉及的项目的部署发布,这个过程也肯定会有成功失败,在不同的公司里采用的提醒方式不同。
在说明下面的方案前,需要首先说明一下基本应用的场景。
场景说明
1、纯后台应用,现在项目基本都是前后端分离,因为一个前端服务可能会对应多个后端服务的支持,随着整个开发大环境的完善和技术的成熟,之前那种前后端耦合的应用很少,这里不在做具体的考虑。
2、项目的基本架构是Spring+Spring MVC+Mybatis,其实主要是Spring+Spring MVC,因为下面的实现方式在这种模式下模拟,其他环境可以根据这个模式仿照,但是照搬可能存在问题,因为现在很多公司都开始使用SpringBoot相关的前沿技术。
3、环境说明
- 本地测试环境,就是IDEA的启动测试,不多说;
- 开发测试环境,这个环境基本用来联调,开发人员发布项目使用的;
- 测试环境,这个是测试人员来用,将开发的代码拉到测试环境,进行各种姿势的测试;
- 演示环境,这个环境看各个公司的定义可能不同,也就是上线前的最后一个环境,基本模拟线上环境,最终验证项目的完整性,有时候所说的灰度、冒烟测试都会在这个环境执行;
- 线上环境,不解释。
4、下面的内容数据自己歪歪,如果存在问题,欢迎提建议。
简单实现的几种方式
在项目中添加一个主页,当发布完成后,访问该页面是OK就表示发布成功,反之就是失败,但是这样存在很多问题,比如在线上环境,这个页面可能就访问不到(纯后台应用可能不会提供这个访问功能)。在本地测试和开发环境可以凑合使用。
定时任务请求项目,这个不仅能监控到项目启动是否成功,也可以监控服务器宕机问题,但是也存在问题,那就是要重新开一个项目,用于发送请求,另外当项目启动的时候,刚好定时任务发起,此时是请求不通的,系统会误报启动失败或者宕机,其实不是这样。
日志扫描,通过对日志的分析查看日志中的异常,并做分析,给予开发或者维护人员一个通知,可以通过Python脚本等方式执行,设计到整体项目架构的问题,不做太多的讨论,也不是这个博客的主要讨论范畴,不做太多赘述。
项目中添加逻辑代码,用于捕获项目启动加载是否正常,从而判断项目是否启动成功。(主要讨论)
项目启动反馈提醒实现
web.xml文件中的配置
<servlet> <servlet-name>spring-dispatcher</servlet-name> <servlet-class>com.minuor.service.notice.LocalDispatcherServletDemo</servlet-class> <!-- 配置SpringMVC需要加载的配置文件 spring-xxx.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-web.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-dispatcher</servlet-name> <!--默认匹配所有的请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
正常应该配置spring默认的DispatcherServlet,这里需要改为加载重新后的LocalDispatcherServletDemo。
LocalDispatcherServletDemo类代码
package com.minuor.service.notice; import com.minuor.common.utils.JavaMailSendUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationContext; import org.springframework.context.i18n.LocaleContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.*; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.net.InetAddress; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; /** * @Author: Joker * @Date: 2018/3/19 * @Desc: 重写DispatcherServlet,做系统启动成功失败监控并通知 */ @Slf4j public class LocalDispatcherServletDemo extends DispatcherServlet { public LocalDispatcherServletDemo() { super(); } public LocalDispatcherServletDemo(WebApplicationContext webApplicationContext) { super(webApplicationContext); } @Override protected WebApplicationContext initWebApplicationContext() { WebApplicationContext webApplicationContext; try { webApplicationContext = super.initWebApplicationContext(); sendMsg(Boolean.TRUE); log.info(">>>>>>>webApplicationContext初始化成功~"); } catch (Exception e) { sendMsg(Boolean.FALSE); webApplicationContext = new XmlWebApplicationContext();//设置临时值,避免重复初始化 log.info(">>>>>>>webApplicationContext初始化失败~"); } return webApplicationContext; } /** * 发送邮件 * * @param flag */ private void sendMsg(boolean flag) { try { JavaMailSendUtil sender = new JavaMailSendUtil(); //成功失败信息 String result = "FAIL"; if (flag) result = "SUCCESS"; //模块名 String ip = InetAddress.getLocalHost().getHostAddress(); String userDir = System.getProperty("user.dir"); String tempStr = userDir.substring(0, userDir.indexOf(File.separator + "bin")); String userDirModel = tempStr.substring(tempStr.lastIndexOf(File.separator) + 1); //组装并推送信息 String mailTest = "发布系统:" + "Minuor个人博客系统" + "<br/>" + "发布环境:" + ip + "<br/>" + "模块名称:" + userDirModel + "<br/>" + "发布时间:" + new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()) + "<br/>" + "发布结果:" + result + "<br/>" + "温馨提示:" + "此邮件仅为系统发布通知邮件,请勿回复!"; sender.sendEmail("个人博客系统发布结果提醒", mailTest, "xxxx@163.com"); } catch (Exception e) { log.info(">>>>>个人博客系统发布结果预警邮件推送失败!异常信息:{}", e); } } @Override public void setDetectAllHandlerMappings(boolean detectAllHandlerMappings) { super.setDetectAllHandlerMappings(detectAllHandlerMappings); } @Override protected void initFrameworkServlet() throws ServletException { super.initFrameworkServlet(); } @Override public void setDetectAllHandlerAdapters(boolean detectAllHandlerAdapters) { super.setDetectAllHandlerAdapters(detectAllHandlerAdapters); } @Override public void setDetectAllHandlerExceptionResolvers(boolean detectAllHandlerExceptionResolvers) { super.setDetectAllHandlerExceptionResolvers(detectAllHandlerExceptionResolvers); } @Override public void setDetectAllViewResolvers(boolean detectAllViewResolvers) { super.setDetectAllViewResolvers(detectAllViewResolvers); } @Override public void setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) { super.setThrowExceptionIfNoHandlerFound(throwExceptionIfNoHandlerFound); } @Override public void setCleanupAfterInclude(boolean cleanupAfterInclude) { super.setCleanupAfterInclude(cleanupAfterInclude); } @Override protected void onRefresh(ApplicationContext context) { super.onRefresh(context); } @Override protected void initStrategies(ApplicationContext context) { super.initStrategies(context); } @Override protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) { return super.getDefaultStrategy(context, strategyInterface); } @Override protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { return super.getDefaultStrategies(context, strategyInterface); } @Override protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) { return super.createDefaultStrategy(context, clazz); } @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { super.doService(request, response); } @Override protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { super.doDispatch(request, response); } @Override protected LocaleContext buildLocaleContext(HttpServletRequest request) { return super.buildLocaleContext(request); } @Override protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { return super.checkMultipart(request); } @Override protected void cleanupMultipart(HttpServletRequest request) { super.cleanupMultipart(request); } @Override protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { return super.getHandler(request); } @Override protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception { super.noHandlerFound(request, response); } @Override protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { return super.getHandlerAdapter(handler); } @Override protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { return super.processHandlerException(request, response, handler, ex); } @Override protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { super.render(mv, request, response); } @Override protected String getDefaultViewName(HttpServletRequest request) throws Exception { return super.getDefaultViewName(request); } @Override protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { return super.resolveViewName(viewName, model, locale, request); } }
重新所有的方法,直接调用父类DispatcherServlet内的逻辑,实际LocalDispatcherServletDemo中没有具体的逻辑,只有构造方法、initWebApplicationContext、sendMsg短信发送逻辑的修改和创建。
JavaMailSendUtil邮件发送逻辑代码
package com.minuor.common.mail; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.util.Properties; /** * @Author: Joker * @Date: 2018/3/19 * @Desc: 发送邮件服务 */ @Slf4j public class JavaMailSendUtil { /** * 发送邮件 */ public void sendEmail(String subject, String mailText, String rStr) { try { // 1.创建一个程序与邮件服务器会话对象 Session Properties props = new Properties(); props.setProperty("mail.smtp.host", "smtp.163.com"); props.setProperty("mail.smtp.port", "25"); // 指定验证为true props.setProperty("mail.smtp.auth", "true"); // 验证账号及密码,密码需要是第三方授权码 Authenticator auth = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( "xxxx@163.com", "123456"); } }; Session session = Session.getInstance(props, auth); // 2.创建一个Message,它相当于是邮件内容 Message message = new MimeMessage(session); // 设置发送者 message.setFrom(new InternetAddress("xxxxx@163.com")); // 设置发送方式与接收者 if (StringUtils.isBlank(rStr)) return; String[] rStrs = rStr.split(","); InternetAddress[] address = new InternetAddress[rStrs.length]; int index = 0; for (String str : rStrs) { address[index] = new InternetAddress(str); index++; } message.setRecipients(MimeMessage.RecipientType.TO, address); // 设置主题 message.setSubject(subject); // 设置内容 message.setContent(mailText, "text/html;charset=utf-8"); // 3.创建 Transport用于将邮件发送 Transport.send(message); log.info(">>>>>>>发送邮件成功<<<<<<<"); } catch (Exception e) { log.error(">>>>>>>发送邮件异常:{}", e); } } }
封装的最简单的发送方式,只为实现简单的邮件推送功能。
具体说明
在重新initWebApplicationContext内,对调用父类的initWebApplicationContext方法加了异常捕捉,在catch捕捉中添加了邮件处理逻辑,也将异常吃点不再打印出来;
邮件推送分为两个部分,一个是项目启动成功,一个是项目启动失败,两个部分都会给开发或者维护人员提供相应的邮件提醒;
最主要的一点,这里涉及到的邮件发送工具类,重写类LocalDispatcherServletDemo都不能交给spring管理,因为在spring加载失败的时候,这些类对应的bean是不能创建成功的,更不用说对象的注入以及具体逻辑的实现。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。