05_项目集成飞书预警
一、切面类及请求上下文信息类
请求上下文信息类:
- /**
- * @desc: 请求上下文信息类
- * @author: sqnugy
- * @date: 2025/5/8
- **/
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class ApiContext {
- private String methodName;
- private String className;
- private String argsJson;
- }
复制代码 请求上下文信息保存类(通过 ThreadLocal 保存)
- /**
- * @desc: ThreadLocal 存储请求上下文信息(ApiContext)
- * @author: sqnugy
- * @date: 2025/5/8
- **/
- public class ApiContextHolder {
- private static final ThreadLocal<ApiContext> contextHolder = new ThreadLocal<>();
- public static void set(ApiContext context) {
- contextHolder.set(context);
- }
- public static ApiContext get() {
- return contextHolder.get();
- }
- public static void clear() {
- contextHolder.remove();
- }
- }
复制代码 ApiOperation 注解切面类:
- import cn.jcs.boot.video.review.util.JsonUtils;
- import io.swagger.annotations.ApiOperation;
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.springframework.stereotype.Component;
- import org.slf4j.MDC;
- import java.lang.reflect.Method;
- import java.util.Arrays;
- import java.util.UUID;
- import java.util.function.Function;
- import java.util.stream.Collectors;
- /**
- * @desc: ApiOperation 注解的切面类,通过 ApiOperaton 注解进行织入切面,并将获取到的请求的类名、方法名、入参 保存到上下文中
- * @author: sqnugy
- * @date: 2025/5/8
- **/
- @Aspect
- @Component
- @Slf4j
- public class ApiOperationAspect {
- /** 以 @ApiOperation 注解为切点,凡是添加 @ApiOperation 的方法,都会执行环绕中的代码 */
- @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
- public void apiOperation() {}
- /**
- * 环绕
- * @param joinPoint
- * @return
- * @throws Throwable
- */
- @Around("apiOperation()")
- public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
- try {
- long startTime = System.currentTimeMillis();
- MDC.put("traceId", UUID.randomUUID().toString());
- String className = joinPoint.getTarget().getClass().getSimpleName();
- String methodName = joinPoint.getSignature().getName();
- Object[] args = joinPoint.getArgs();
- String argsJsonStr = Arrays.stream(args).map(toJsonStr()).collect(Collectors.joining(", "));
- String description = getApiOperationDescription(joinPoint);
- log.info("====== 请求开始: [{}], 入参: {}, 请求类: {}, 请求方法: {} =================================== ",
- description, argsJsonStr, className, methodName);
- // 保存上下文信息
- ApiContext apiContext = new ApiContext();
- apiContext.setClassName(className);
- apiContext.setMethodName(methodName);
- apiContext.setArgsJson(argsJsonStr);
- //将上下文信息保存到 ThreadLocal 内
- ApiContextHolder.set(apiContext);
- Object result = joinPoint.proceed();
- long executionTime = System.currentTimeMillis() - startTime;
- String resultJson = JsonUtils.toJsonString(result);
- log.info("====== 请求结束: [{}], 耗时: {}ms, 出参: {} =================================== ",
- description, executionTime, resultJson);
- return result;
- } finally {
- MDC.clear();
- }
- }
- /**
- * 获取注解的描述信息
- * @param joinPoint
- * @return
- */
- private String getApiOperationDescription(ProceedingJoinPoint joinPoint) {
- // 1. 从 ProceedingJoinPoint 获取 MethodSignature
- MethodSignature signature = (MethodSignature) joinPoint.getSignature();
- // 2. 使用 MethodSignature 获取当前被注解的 Method
- Method method = signature.getMethod();
- // 3. 从 Method 中提取 LogExecution 注解
- ApiOperation apiOperatiog = method.getAnnotation(ApiOperation.class);
- // 4. 从 LogExecution 注解中获取 description 属性
- return apiOperatiog.value();
- }
- /**
- * 转 JSON 字符串
- * @return
- */
- private Function<Object, String> toJsonStr() {
- return arg -> JsonUtils.toJsonString(arg);
- }
- }
复制代码 二、使用小呆板人推送普通消息
使用小呆板人发送普通消息,实际上就是将想要发送的消息封装为字符串进行发送。
2.1 推送信息格式:
2.2 全局非常处理类
- package cn.jcs.boot.video.review.exception;
- import cn.jcs.boot.video.review.common.CommonResult;
- import cn.jcs.boot.video.review.util.FeishuUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.validation.FieldError;
- import org.springframework.validation.ObjectError;
- import org.springframework.web.bind.MethodArgumentNotValidException;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.RestControllerAdvice;
- import org.springframework.web.method.HandlerMethod;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * @desc:
- * @author: sqnugy
- * @date: 2025/5/8
- **/
- @RestControllerAdvice
- @Slf4j
- public class GlobalExceptionHandler {
- @ExceptionHandler(BusinessException.class)
- public CommonResult<?> handlerMallServiceException(BusinessException e) {
- log.error("业务异常", e);
- IErrorCode errorCode = e.getErrorCode();
- if (errorCode != null) {
- return CommonResult.failed(errorCode);
- }
- try {
- FeishuUtil.sendTextMsg("业务异常", e);
- } catch (Exception ignored) {}
- return CommonResult.failed(e.getMessage());
- }
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public CommonResult<?> handlerValidException(MethodArgumentNotValidException e) {
- log.error("表单校验异常", e);
- Map<String, String> message = new HashMap<>();
- for (ObjectError error : e.getBindingResult().getAllErrors()) {
- FieldError fe = (FieldError) error;
- message.put(fe.getField(), error.getDefaultMessage());
- }
- try {
- FeishuUtil.sendTextMsg("参数校验异常", e);
- } catch (Exception ignored) {}
- return CommonResult.validateFailed("表单数据错误", message);
- }
- @ExceptionHandler(Exception.class)
- public CommonResult<?> handlerException(Exception e, HandlerMethod handlerMethod) {
- log.error("报错方法名字 [{}]", handlerMethod.getMethod().getName(), e);
- try {
- FeishuUtil.sendTextMsg("全局异常", e);
- } catch (Exception ignored) {}
- return CommonResult.failed("系统异常,请稍后重试");
- }
- }
复制代码 2.3 飞书关照工具类
[code]import cn.hutool.http.HttpUtil;
import cn.jcs.boot.video.review.common.aspect.ApiContext;
import cn.jcs.boot.video.review.common.aspect.ApiContextHolder;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @desc:
* @author: sqnugy
* @date: 2025/5/8
**/
public class FeishuUtil {
private static final String URL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxx";
/**
* @param title 异常类型,如“业务异常”、“全局异常”
* @param e 异常对象
*/
public static void sendTextMsg(String title, Exception e) {
ApiContext context = ApiContextHolder.get();
String methodName = context != null ? context.getMethodName() : "未知方法";
String className = context != null ? context.getClassName() : "未知类";
String argsJson = context != null ? context.getArgsJson() : "无入参";
// 使用 JsonUtils 格式化入参 JSON 并缩进
String formattedArgs = JsonUtils.toJsonPrettyString(JsonUtils.parseObject(argsJson, Object.class));
// 获取异常堆栈
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString();
String msg = String.format(
"❗ %s 异常告警\n" +
" |