1. 场景说明
在开发的流程中必须涉及的项目的部署发布,这个过程也肯定会有成功、失败,作为开发能够实时的得到反馈是很有必要的,在不同的公司里采用的提醒方式不同。在说明下面的方案前,需要首先说明一下基本应用的场景。
纯后台应用,现在项目基本都是前后端分离,因为一个前端服务可能会对应多个后端服务的支持,随着整个开发大环境的完善和技术的成熟,之前那种前后端耦合的应用很少,这里不在做具体的考虑。
项目的基本架构是Spring+Spring MVC+Mybatis,其实主要是Spring+Spring MVC,因为下面的实现方式在这种模式下模拟,其他环境可以根据这个模式仿照,但是照搬可能存在问题,因为现在很多公司都开始使用SpringBoot相关的前沿技术。
环境说明
①、本地测试环境,就是IDEA的启动测试,不多说;
②、开发测试环境,这个环境基本用来联调,开发人员发布项目使用的;
③、测试环境,这个是测试人员来用,将开发的代码拉到测试环境,进行各种姿势的测试;
④、演示环境,这个环境看各个公司的定义可能不同,也就是上线前的最后一个环境,基本模拟线上环境,最终验证项目的完整性,有时候所说的灰度、冒烟测试都会在这个环境执行;
⑤、线上环境,不解释。下面的内容数据自己歪歪,如果存在问题,欢迎提建议。
2. 简单实现的几种方式
- 在项目中添加一个主页(又称健康页),当发布完成后,访问该页面是OK就表示发布成功,反之就是失败,但是这样存在很多问题,比如在线上环境,这个页面可能就访问不到(纯后台应用可能不会提供这个访问功能)。在本地测试和开发环境可以凑合使用。
- 定时任务请求项目,这个不仅能监控到项目启动是否成功,也可以监控服务器宕机问题,但是也存在问题,那就是要重新开一个项目,用于发送请求,另外当项目启动的时候,刚好定时任务发起,此时是请求不通的,系统会误报启动失败或者宕机,其实不是这样。
- 日志扫描,通过对日志的分析查看日志中的异常,并做分析,给予开发或者维护人员一个通知,可以通过Python脚本等方式执行,设计到整体项目架构的问题,不做太多的讨论,也不是这个博客的主要讨论范畴,不做太多赘述。
项目中添加逻辑代码,用于捕获项目启动加载是否正常,从而判断项目是否启动成功。(主要讨论)
3. 项目启动反馈提醒实现
3.1 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。
3.2 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;
/**
* 重写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;
}
/**
* 发送邮件
*/
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短信发送逻辑的修改和创建。
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;
/**
* 发送邮件服务
*/
@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);
}
}
}
封装的最简单的发送方式,只为实现简单的邮件推送功能。
3.3 具体说明
在重写
initWebApplicationContext
方法内,对调用父类的initWebApplicationContext
方法加了异常捕捉,在catch捕捉中添加了邮件处理逻辑,也将异常吃掉不打印出来;邮件推送分为两个部分,一个是项目启动成功,一个是项目启动失败,两个部分都会给开发或者维护人员提供相应的邮件提醒;
最主要的一点,这里涉及到的邮件发送工具类,重写类
LocalDispatcherServletDemo
都不能交给spring管理,因为在spring加载失败的时候,这些类对应的bean是不能创建成功的,更不用说对象的注入以及具体逻辑的实现。