✨ 添加全局异常消息通知、实现钉钉发送异常消息通知
This commit is contained in:
@@ -16,6 +16,11 @@
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.hccake</groupId>
|
||||
<artifactId>ballcat-extend-ding-talk</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--mybatis plus-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
@@ -33,6 +38,11 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.hccake.ballcat.common.conf.config;
|
||||
|
||||
import com.hccake.ballcat.common.conf.exception.ExceptionHandleTypeEnum;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author lingting 2020/6/12 0:15
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ballcat.exception")
|
||||
public class ExceptionHandleConfig {
|
||||
/**
|
||||
* 处理类型
|
||||
*/
|
||||
private ExceptionHandleTypeEnum type = ExceptionHandleTypeEnum.NONE;
|
||||
/**
|
||||
* 通知间隔时间 单位秒 默认 5分钟
|
||||
*/
|
||||
private long time = TimeUnit.MINUTES.toSeconds(5);
|
||||
/**
|
||||
* 消息阈值 即便间隔时间没有到达设定的时间, 但是异常发生的数量达到阈值 则立即发送消息
|
||||
*/
|
||||
private long max = 5;
|
||||
/**
|
||||
* 堆栈转string 的长度
|
||||
*/
|
||||
private int length = 3000;
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.hccake.ballcat.common.conf.exception;
|
||||
|
||||
import com.hccake.ballcat.common.core.exception.handler.DefaultGlobalExceptionHandler;
|
||||
import com.hccake.ballcat.common.conf.config.ExceptionHandleConfig;
|
||||
import com.hccake.ballcat.common.conf.exception.handler.DefaultGlobalExceptionHandler;
|
||||
import com.hccake.ballcat.common.conf.exception.handler.DingTalkGlobalExceptionHandler;
|
||||
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
|
||||
import com.hccake.extend.ding.talk.DingTalkSender;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@@ -13,27 +19,45 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
* @date 2019/10/15 18:20
|
||||
*/
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class ExceptionHandleAutoConfiguration {
|
||||
@Value("${spring.application.name}")
|
||||
private String applicationName;
|
||||
|
||||
/**
|
||||
* 默认的日志处理器
|
||||
*
|
||||
* @return DefaultExceptionHandler
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
|
||||
public GlobalExceptionHandler globalExceptionHandler(){
|
||||
@ConditionalOnProperty(prefix = "ballcat.exception", name = "type", havingValue = "NONE")
|
||||
public GlobalExceptionHandler defaultGlobalExceptionHandler() {
|
||||
return new DefaultGlobalExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 钉钉消息通知的日志处理器
|
||||
*
|
||||
* @author lingting 2020-06-12 00:32:40
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
|
||||
@ConditionalOnProperty(prefix = "ballcat.exception", name = "type", havingValue = "DING_TALK")
|
||||
public GlobalExceptionHandler dingTalkGlobalExceptionHandler(ExceptionHandleConfig exceptionHandleConfig, DingTalkSender sender) {
|
||||
return new DingTalkGlobalExceptionHandler(exceptionHandleConfig, sender, applicationName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 默认的日志处理器
|
||||
*
|
||||
* @return DefaultExceptionHandler
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(GlobalExceptionHandlerResolver.class)
|
||||
public GlobalExceptionHandlerResolver globalExceptionHandlerResolver(GlobalExceptionHandler globalExceptionHandler){
|
||||
public GlobalExceptionHandlerResolver globalExceptionHandlerResolver(GlobalExceptionHandler globalExceptionHandler) {
|
||||
return new GlobalExceptionHandlerResolver(globalExceptionHandler);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.hccake.ballcat.common.conf.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 异常处理类型
|
||||
*
|
||||
* @author lingting 2020/6/12 0:18
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ExceptionHandleTypeEnum {
|
||||
/**
|
||||
* 异常处理通知类型 说明
|
||||
*/
|
||||
NONE("不通知"),
|
||||
DING_TALK("通过钉钉通知"),
|
||||
MAIL("邮件通知"),
|
||||
;
|
||||
private final String text;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hccake.ballcat.common.conf.exception;
|
||||
|
||||
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
|
||||
import com.hccake.ballcat.common.core.exception.BusinessException;
|
||||
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
|
||||
import com.hccake.ballcat.common.core.result.R;
|
||||
import com.hccake.ballcat.common.core.result.SystemResultCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*
|
||||
* @author Hccake
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -31,12 +32,13 @@ public class GlobalExceptionHandlerResolver {
|
||||
|
||||
/**
|
||||
* 全局异常捕获
|
||||
*
|
||||
* @param e the e
|
||||
* @return R
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public R handleGlobalException(Exception e) {
|
||||
public R<String> handleGlobalException(Exception e) {
|
||||
log.error("全局异常信息 ex={}", e.getMessage(), e);
|
||||
globalExceptionHandler.handle(e);
|
||||
return R.failed(SystemResultCode.SERVER_ERROR, e.getLocalizedMessage());
|
||||
@@ -47,12 +49,13 @@ public class GlobalExceptionHandlerResolver {
|
||||
* 自定义业务异常捕获
|
||||
* 业务异常响应码推荐使用200
|
||||
* 用 result 结构中的code做为业务错误码标识
|
||||
*
|
||||
* @param e the e
|
||||
* @return R
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
public R handleBallCatException(BusinessException e) {
|
||||
public R<String> handleBallCatException(BusinessException e) {
|
||||
log.error("自定义异常信息 ex={}", e.getMessage(), e);
|
||||
globalExceptionHandler.handle(e);
|
||||
return R.failed(e.getCode(), e.getMsg());
|
||||
@@ -67,7 +70,7 @@ public class GlobalExceptionHandlerResolver {
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public R handleAccessDeniedException(AccessDeniedException e) {
|
||||
public R<String> handleAccessDeniedException(AccessDeniedException e) {
|
||||
String msg = SpringSecurityMessageSource.getAccessor()
|
||||
.getMessage("AbstractAccessDecisionManager.accessDenied"
|
||||
, e.getMessage());
|
||||
@@ -84,13 +87,13 @@ public class GlobalExceptionHandlerResolver {
|
||||
*/
|
||||
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public R handleBodyValidException(Exception exception) {
|
||||
public R<String> handleBodyValidException(Exception exception) {
|
||||
List<FieldError> fieldErrors;
|
||||
if(exception instanceof BindException){
|
||||
BindException bindException = (BindException)exception;
|
||||
if (exception instanceof BindException) {
|
||||
BindException bindException = (BindException) exception;
|
||||
fieldErrors = bindException.getBindingResult().getFieldErrors();
|
||||
}else{
|
||||
MethodArgumentNotValidException e = (MethodArgumentNotValidException)exception;
|
||||
} else {
|
||||
MethodArgumentNotValidException e = (MethodArgumentNotValidException) exception;
|
||||
fieldErrors = e.getBindingResult().getFieldErrors();
|
||||
}
|
||||
|
||||
@@ -105,17 +108,17 @@ public class GlobalExceptionHandlerResolver {
|
||||
/**
|
||||
* 单体参数校验异常
|
||||
* validation Exception
|
||||
*
|
||||
* @param e the e
|
||||
* @return R
|
||||
*/
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public R handleValidationException(Exception e) {
|
||||
public R<String> handleValidationException(Exception e) {
|
||||
log.error("参数绑定异常 ex={}", e.getMessage(), e);
|
||||
globalExceptionHandler.handle(e);
|
||||
return R.failed(SystemResultCode.BAD_REQUEST, e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.hccake.ballcat.common.conf.exception.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 异常通知消息
|
||||
*
|
||||
* @author lingting 2020/6/12 16:07
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class ExceptionMessage {
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private String message;
|
||||
/**
|
||||
* 数量
|
||||
*/
|
||||
private int number;
|
||||
/**
|
||||
* 堆栈
|
||||
*/
|
||||
private String stack;
|
||||
/**
|
||||
* 最新的触发时间
|
||||
*/
|
||||
private String time;
|
||||
/**
|
||||
* 机器地址
|
||||
*/
|
||||
private String mac;
|
||||
/**
|
||||
* 线程id
|
||||
*/
|
||||
private long threadId;
|
||||
/**
|
||||
* 服务名
|
||||
*/
|
||||
private String applicationName;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "服务名称:" + applicationName
|
||||
+ "\n机器地址:" + mac
|
||||
+ "\n触发时间:" + time
|
||||
+ "\n线程id:" + threadId
|
||||
+ "\n数量:" + number
|
||||
+ "\n堆栈:" + stack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hccake.ballcat.common.conf.exception.domain;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 异常消息通知响应
|
||||
*
|
||||
* @author lingting 2020/6/12 19:07
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
public class ExceptionNoticeResponse {
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errMsg;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.hccake.ballcat.common.conf.exception.handler;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import com.hccake.ballcat.common.conf.config.ExceptionHandleConfig;
|
||||
import com.hccake.ballcat.common.conf.exception.domain.ExceptionMessage;
|
||||
import com.hccake.ballcat.common.conf.exception.domain.ExceptionNoticeResponse;
|
||||
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 消息通知顶层类
|
||||
*
|
||||
* @author lingting 2020/6/12 0:35
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractNoticeGlobalExceptionHandler extends Thread implements GlobalExceptionHandler {
|
||||
private final ExceptionHandleConfig config;
|
||||
/**
|
||||
* 通知消息存放 e.message 堆栈信息
|
||||
*/
|
||||
private Map<String, ExceptionMessage> messages = new ConcurrentHashMap<>(10);
|
||||
/**
|
||||
* 异常发生数
|
||||
*/
|
||||
private long number = 0;
|
||||
/**
|
||||
* 用来当做锁
|
||||
*/
|
||||
private final String lock = "";
|
||||
/**
|
||||
* 本地物理地址
|
||||
*/
|
||||
private String mac;
|
||||
private final String applicationName;
|
||||
|
||||
public AbstractNoticeGlobalExceptionHandler(ExceptionHandleConfig config, String applicationName) {
|
||||
this.config = config;
|
||||
this.applicationName = applicationName;
|
||||
try {
|
||||
byte[] mac = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < mac.length; i++) {
|
||||
sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
|
||||
}
|
||||
this.mac = sb.toString();
|
||||
} catch (Exception e) {
|
||||
mac = "获取失败!";
|
||||
}
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Throwable e) {
|
||||
synchronized (lock) {
|
||||
number++;
|
||||
ExceptionMessage message = messages.getOrDefault(
|
||||
e.getMessage(),
|
||||
new ExceptionMessage().setNumber(0).setMac(mac).setApplicationName(applicationName)
|
||||
);
|
||||
message.setNumber(message.getNumber() + 1)
|
||||
.setStack(ExceptionUtil.stacktraceToString(e, config.getLength()).replaceAll("\\r", ""))
|
||||
.setTime(DateUtil.now())
|
||||
.setThreadId(Thread.currentThread().getId());
|
||||
messages.put(e.getMessage(), message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.setName("exception-notice-thread-" + config.getType().name());
|
||||
log.debug("异常消息通知线程启动!");
|
||||
TimeInterval interval = new TimeInterval();
|
||||
while (true) {
|
||||
try {
|
||||
if (interval.intervalSecond() >= config.getTime() || number >= config.getMax()) {
|
||||
if (messages.size() == 0) {
|
||||
interval.restart();
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, ExceptionMessage> sendMessages;
|
||||
synchronized (lock) {
|
||||
sendMessages = messages;
|
||||
messages = new ConcurrentHashMap<>(10);
|
||||
number = 0;
|
||||
interval.restart();
|
||||
}
|
||||
sendMessages.forEach((k, v) -> {
|
||||
ExceptionNoticeResponse response = send(v);
|
||||
if (!response.isSuccess()) {
|
||||
log.error("消息通知发送失败! msg: {}", response.getErrMsg());
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("消息通知异常!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送通知
|
||||
*
|
||||
* @param sendMessage 发送的消息
|
||||
* @return 返回消息发送状态,如果发送失败需要设置失败信息
|
||||
* @author lingting 2020-06-12 00:37:23
|
||||
*/
|
||||
public abstract ExceptionNoticeResponse send(ExceptionMessage sendMessage);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.hccake.ballcat.common.conf.exception.handler;
|
||||
|
||||
import com.hccake.ballcat.common.core.exception.handler.GlobalExceptionHandler;
|
||||
|
||||
/**
|
||||
* @author Hccake
|
||||
* @version 1.0
|
||||
* @date 2019/10/18 17:06
|
||||
* 默认的异常日志处理类
|
||||
*/
|
||||
public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
|
||||
/**
|
||||
* 在此处理日志
|
||||
* 默认什么都不处理
|
||||
*
|
||||
* @param throwable 异常信息
|
||||
*/
|
||||
@Override
|
||||
public void handle(Throwable throwable) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hccake.ballcat.common.conf.exception.handler;
|
||||
|
||||
import com.hccake.ballcat.common.conf.config.ExceptionHandleConfig;
|
||||
import com.hccake.ballcat.common.conf.exception.domain.ExceptionMessage;
|
||||
import com.hccake.ballcat.common.conf.exception.domain.ExceptionNoticeResponse;
|
||||
import com.hccake.extend.ding.talk.DingTalkResponse;
|
||||
import com.hccake.extend.ding.talk.DingTalkSender;
|
||||
import com.hccake.extend.ding.talk.message.DingTalkTextMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 钉钉消息通知
|
||||
*
|
||||
* @author lingting 2020/6/12 0:25
|
||||
*/
|
||||
@Slf4j
|
||||
public class DingTalkGlobalExceptionHandler extends AbstractNoticeGlobalExceptionHandler {
|
||||
private final DingTalkSender sender;
|
||||
|
||||
public DingTalkGlobalExceptionHandler(ExceptionHandleConfig config, DingTalkSender sender, String applicationName) {
|
||||
super(config, applicationName);
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExceptionNoticeResponse send(ExceptionMessage sendMessage) {
|
||||
DingTalkResponse response = sender.sendMessage(new DingTalkTextMessage().setContent(sendMessage.toString()));
|
||||
return new ExceptionNoticeResponse()
|
||||
.setErrMsg(response.getResponse())
|
||||
.setSuccess(response.isSuccess());
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.hccake.ballcat.common.conf.config.JacksonConfig,\
|
||||
com.hccake.ballcat.common.conf.exception.ExceptionHandleAutoConfiguration,\
|
||||
com.hccake.ballcat.common.conf.mybatis.MybatisPlusConfig,\
|
||||
com.hccake.ballcat.common.conf.web.WebMvcConfig
|
||||
com.hccake.ballcat.common.conf.web.WebMvcConfig,\
|
||||
com.hccake.ballcat.common.conf.config.ExceptionHandleConfig
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.hccake.ballcat.common.core.exception.handler;
|
||||
|
||||
/**
|
||||
* @author Hccake
|
||||
* @version 1.0
|
||||
* @date 2019/10/18 17:06
|
||||
* 默认的异常日志处理类
|
||||
*/
|
||||
public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
|
||||
/**
|
||||
* 在此处理日志
|
||||
* 默认什么都不处理
|
||||
* @param throwable 异常信息
|
||||
*/
|
||||
@Override
|
||||
public void handle(Throwable throwable) {}
|
||||
}
|
||||
Reference in New Issue
Block a user