feat: 新增钉钉机器人异常告警

11111
winter 1 year ago
parent 46fa7bf45e
commit f0a9ba9243

@ -6,3 +6,5 @@
## 迁移目录: ## 迁移目录:
- Azure OIDC(SSO) 迁移 - Azure OIDC(SSO) 迁移
- id-token(jwt) 验证迁移 (出现语言框架之间的签名算法规范问题,解决见: [输出复盘](https://juejin.cn/post/7300036605099163702)) - id-token(jwt) 验证迁移 (出现语言框架之间的签名算法规范问题,解决见: [输出复盘](https://juejin.cn/post/7300036605099163702))
- 钉钉告警: 异常通知
- 异常文件记录

@ -0,0 +1,37 @@
package cn.teammodel.common;
public enum ErrorCode {
SUCCESS(0, "ok"),
PARAMS_ERROR(40000, "请求参数错误"),
NOT_LOGIN_ERROR(40100, "未登录"),
NO_AUTH_ERROR(40101, "无权限"),
NOT_FOUND_ERROR(40400, "请求数据不存在"),
FORBIDDEN_ERROR(40300, "禁止访问"),
SYSTEM_ERROR(50000, "系统内部异常"),
OPERATION_ERROR(50001, "操作失败");
/**
*
*/
private final int code;
/**
*
*/
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

@ -1,37 +1,48 @@
package cn.teammodel.common; package cn.teammodel.common;
public class R { import lombok.Data;
private Integer code;
import java.io.Serializable;
@Data
public class R<T> implements Serializable {
private int code;
private T data;
private String message; private String message;
private String data;
public R(Integer code, String message, String data) { public R(int code, T data, String message) {
this.code = code; this.code = code;
this.message = message;
this.data = data; this.data = data;
this.message = message;
} }
public Integer getCode() { public R(int code, T data) {
return code; this(code, data, "");
} }
public void setCode(Integer code) { public R(ErrorCode errorCode) {
this.code = code; this(errorCode.getCode(), null, errorCode.getMessage());
}
public R(ErrorCode errorCode, T data) {
this(errorCode.getCode(), data, errorCode.getMessage());
} }
public String getMessage() { public static <T> R<T> success(T data) {
return message; return new R<>(ErrorCode.SUCCESS, data);
} }
public void setMessage(String message) { public static <T> R<T> error(String msg) {
this.message = message; return new R<>(ErrorCode.SYSTEM_ERROR);
} }
public String getData() { public static <T> R<T> error(Integer code, String msg) {
return data; return new R<>(code, null, msg);
} }
public void setData(String data) { public static <T> R<T> error(ErrorCode errorCode) {
this.data = data; return new R<>(errorCode);
} }
} }

@ -0,0 +1,110 @@
package cn.teammodel.config.exception;
import cn.teammodel.common.R;
import cn.teammodel.manager.NotificationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
*
*
* @author ruoyi
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@Resource
private NotificationService notificationService;
/**
*
*/
@ExceptionHandler(ServiceException.class)
public R<?> handleServiceException(ServiceException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
Integer code = e.getCode();
return ObjectUtils.isNotEmpty(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
}
/**
*
*/
@ExceptionHandler(MissingPathVariableException.class)
public R<?> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return R.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
*
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return R.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
}
/**
*
*/
@ExceptionHandler(RuntimeException.class)
public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
notificationService.send("RuntimeException 告警: " + e.getMessage());
return R.error(e.getMessage());
}
/**
*
*/
@ExceptionHandler(Exception.class)
public R<?> handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
notificationService.send("Exception 告警: " + e.getMessage());
return R.error(e.getMessage());
}
/**
*
*/
@ExceptionHandler(BindException.class)
public R<?> handleBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return R.error(message);
}
/**
*
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
return R.error(message);
}
}

@ -0,0 +1,44 @@
package cn.teammodel.manager;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
*
* @author winter
* @create 2023-11-14 10:11
*/
@Component
@Slf4j
public class DingAlertNotifier implements NotificationService{
private final DingTalkClient client;
@Autowired
public DingAlertNotifier(@Value("${ding.server-url}") String dingServerUrl) {
this.client = new DefaultDingTalkClient(dingServerUrl);
}
@Override
public void send(String message) {
OapiRobotSendRequest request = new OapiRobotSendRequest();
// 文本消息
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(message);
request.setText(text);
// at 管理员提醒异常
OapiRobotSendRequest.At atAll = new OapiRobotSendRequest.At();
atAll.setIsAtAll(true);
request.setAt(atAll);
try {
client.execute(request);
} catch (ApiException e) {
log.error("钉钉 robot 推送消息渠道异常: {}", e.getMessage());
}
}
}

@ -0,0 +1,16 @@
package cn.teammodel.manager;
/**
*
* @author winter
* @create 2023-11-14 10:08
*/
public interface NotificationService {
/**
*
* @author: winter
* @date: 2023/11/14 10:09
*/
void send(String message);
}

@ -8,14 +8,10 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;

@ -1,14 +1,11 @@
package cn.teammodel.security.filter; package cn.teammodel.security.filter;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.model.entity.TmdUserDetail; import cn.teammodel.model.entity.TmdUserDetail;
import cn.teammodel.security.utils.JwtTokenUtil; import cn.teammodel.security.utils.JwtTokenUtil;
import cn.teammodel.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -20,6 +17,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.util.Collection; import java.util.Collection;
/** /**
@ -43,7 +41,7 @@ public class AuthInnerTokenFilter extends OncePerRequestFilter {
TmdUserDetail tmdUserDetail = jwtTokenUtil.getValidUserDetail(request); TmdUserDetail tmdUserDetail = jwtTokenUtil.getValidUserDetail(request);
if (tmdUserDetail == null) { if (tmdUserDetail == null) {
log.error("authToken authentication failed"); log.error("authToken authentication failed");
throw new ServiceException("无权限"); throw new AccessDeniedException("无权限");
} }
System.out.println(tmdUserDetail.getUser()); System.out.println(tmdUserDetail.getUser());
// 组装 authToken 的 jwt 进 authentication // 组装 authToken 的 jwt 进 authentication

@ -16,5 +16,6 @@ spring:
jwt: jwt:
secret: fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA= secret: fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=
dingding: # 钉钉 webhook
ding:
server-url: https://oapi.dingtalk.com/robot/send?access_token=32d9b24f69c2c4fd7c2dab43268b6258a7214d2620e0805d7b6d1429003b64b6 server-url: https://oapi.dingtalk.com/robot/send?access_token=32d9b24f69c2c4fd7c2dab43268b6258a7214d2620e0805d7b6d1429003b64b6

@ -1,13 +1,19 @@
package cn.teammodel; package cn.teammodel;
import cn.teammodel.manager.DingAlertNotifier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest @SpringBootTest
class TeamModelExtensionApplicationTests { class TeamModelExtensionApplicationTests {
@Autowired
private DingAlertNotifier notifier;
@Test @Test
void contextLoads() { void contextLoads() {
notifier.send("告警: 测试消息推送封装模块");
} }
} }

@ -1,10 +1,13 @@
package cn.teammodel; package cn.teammodel;
import cn.teammodel.common.R;
import cn.teammodel.model.entity.User;
import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest; import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse; import com.dingtalk.api.response.OapiRobotSendResponse;
import com.taobao.api.ApiException; import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Arrays; import java.util.Arrays;
@ -13,6 +16,7 @@ import java.util.Arrays;
* @author winter * @author winter
* @create 2023-11-10 10:42 * @create 2023-11-10 10:42
*/ */
@Slf4j
public class TestWithoutSpring { public class TestWithoutSpring {
@Test @Test
public void testJwtDecode() { public void testJwtDecode() {
@ -63,4 +67,29 @@ public class TestWithoutSpring {
request.setMarkdown(markdown); request.setMarkdown(markdown);
OapiRobotSendResponse response = client.execute(request); OapiRobotSendResponse response = client.execute(request);
} }
@Test
public void testException() {
try {
throw new Exception("出现异常");
} catch (Exception e) {
//e.printStackTrace();
log.error("error: {}", e);
//System.out.println(e.getMessage());
//System.out.println(e.getCause());
//System.out.println(e.getStackTrace());
}
}
@Test
public void testResult() {
User user = new User();
user.setId("ds");
user.setName("ds");
user.setPicture("s");
user.setStandard("s");
user.setScope("ad");
user.setWebsite("dsa");
user.setArea("");
System.out.println(R.success(user));
}
} }

Loading…
Cancel
Save