1.问题起因
有时候新项目上线,系统在实际生产环境下难免会产生那么些大大小小的问题,因为这些问题时常遭受到客户的消息轰炸。
开发人员角度:看到客户的消息轰炸又不得不与之battle几句,完事了还得去翻日志定位问题所在,一顿操作下来问题定位到了,但是时间也已过半了。
项目经理角度:可能项目经理也头疼这个问题,又要与客户battle还要与开发battle,时间成本高暂且不说,主要是烦啊。
2.解决方案
为了解决这一问题,一开始考虑到实时的去监控每一个接口的数据,但是后面想到没有实际的意义,起不到快速反应快速定位的效果;后面想到去监控系统后台所有的异常信息,将系统捕捉到的异常信息推送到企业微信中,这样只要系统后台代码出现异常报错,都能通过企业微信将产生的异常信息推送到开发的群聊中,开发人员能在第一时间获取到报错的异常信息并处理。
3.具体实现
3.1企业微信群添加群机器人获取webhook地址
1.首先进入群聊点击群机器人,点击添加机器人,这样就能获取到机器人的webhook地址了。
2.向企业微信机器人发送消息
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Component
@Slf4j
public class ExceptionMsgBot {
/**
* 企微机器人webhook地址
*/
private String url = "这里放你的企微机器人webhook地址";
/**
* 是否启用
*/
private Boolean isEnable = true;
public void send(Exception e, HttpServletRequest request) {
if (!isEnable) {
return;
}
String title = "业务报警";
//请求url
String api = request.getRequestURL().toString();
//请求参数
String params = JSONUtil.toJsonStr(request.getAttribute("params"));
if (!StringUtils.isEmpty(params)) {
//将json转成普通string
params = params.replace("\"","\\\"");
}
String textMsg = "{\n" +
" \"msgtype\": \"markdown\",\n" +
" \"markdown\": {\n" +
" \"content\": \"" + title + "\\n\n" +
" 【接口路径url】: " + api + " \n" +
" 【接口参数】: " + params + " \n" +
" 【报错信息】: " + e.getMessage() + " \n" +
" 【根本异常】: " + e.getCause() + " \n" +
" 【异常信息】: " + e + " \"\n" +
" }\n" +
"}\n";
//发送消息
HttpResponse response = HttpRequest
.post(url)
.header(Header.CONTENT_TYPE, "application/json; charset=UTF-8")
.body(textMsg)
.execute();
log.info("【异常消息发送结果】 {}", response.body());
}
}
3.设置全局异常处理类(处理所有由@RequestMapping注解修饰的类和方法中的异常,相当于监控了我们controller层中的所有接口),在全局异常处理类该类中调用ExceptionMsgBot类中的send()方法,将捕捉到的异常信息发送至企微机器人
package com.ruoyi.framework.web.exception;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.DemoModeException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
/**
* 全局异常处理器
*
* @author ruoyi
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
@Autowired
private ExceptionMsgBot bot;
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
bot.send(e,request);
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request)
{
String requestURI = request.getRequestURI();
bot.send(e,request);
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
bot.send(e,request);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
bot.send(e,request);
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
bot.send(e,request);
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e,HttpServletRequest request)
{
log.error(e.getMessage(), e);
bot.send(e,request);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult handleDemoModeException(DemoModeException e)
{
return AjaxResult.error("演示模式,不允许操作");
}
}
注:该全局异常处理类我使用的是若依框架的,使用时注意,你可以按它这个结构自己定义属于你的全局异常处理类(@RestControllerAdvice或@ControllerAdvice+@ExceptionHandler)
4.效果展示
当有异常被全局异常处理类捕捉到时会调用ExceptionMsgBot类中的send()方法,将捕捉到的异常信息发送至企微机器人,效果如下: