spring web flux 记录用户日志及异常日志

package cn.finopen.boot.autoconfigure.aop;



@Configuration
@EnableAspectJAutoProxy
@Order
public class EndpointLogAopConfiguration {
    /**
     * 请求方法白名单
     */
    private static final String[] METHOD_WHITE_LIST = {"get", "unreadCount", "find", "findAll"};

    /**
     * 防止表单重复提交
     */
    @Aspect
    @Service
    @Slf4j
    public static class EndpointLogAspect {
        @Pointcut("execution(* *.*.boot.autoconfigure..*.endpoint.*.*(..))")
        public void endpointPointcut() {
        }

        @Resource
        private SaleGroupUserLogApiService logApiService;
        @Resource
        private WechatService wechatService;

        @Resource
        private StorageDataProperties properties;


        @Around("endpointPointcut()")
        public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                Object[] args = pjp.getArgs();
                Class<?> targetCls = pjp.getTarget().getClass();
                MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
                String simpleName = targetCls.getSimpleName();
                String method = methodSignature.getName();
                if (method.startsWith("find")) {
                    return proceed(pjp);
                }
                for (String i : METHOD_WHITE_LIST) {
                    if (method.equals(i)) {
                        return proceed(pjp);
                    }
                }
                String targetObjectMethodName = simpleName + "." + method;
                String targetMethodParams = Arrays.toString(args);
                if (targetMethodParams.startsWith("[{") && targetMethodParams.endsWith("}]")) {
                    JSONObject jsonObject;
                    try {
                        jsonObject = JSON.parseObject(targetMethodParams.substring(targetMethodParams.indexOf("{"), targetMethodParams.lastIndexOf("}") + 1));
                        String loginUserId = jsonObject.getString("loginUserId");
                        String loginGroupId = jsonObject.getString("loginGroupId");
                        String loginGroupName = jsonObject.getString("loginGroupName");
                        String requestIp = jsonObject.getString("requestIp");
                        String body = JSONObject.toJSONString(jsonObject, SerializerFeature.WriteNullListAsEmpty);
                        if (body.length() > 1024) {//内容过长截断
                            body = body.substring(0, 1024);
                        }
                        if (loginGroupId != null && loginUserId != null) {
                            logApiService.create(SaleGroupUserLogCreateReq.newBuilder()
                                    .setLoginGroupId(Long.parseLong(loginGroupId))
                                    .setLoginUserId(loginUserId)
                                    .setLoginGroupName(loginGroupName)
                                    .setReqUrl(targetObjectMethodName)
                                    .setReqIp(requestIp)
                                    .setReqBody(body)
                                    .build());
                        }
                    } catch (Exception ignore) {
                        return proceed(pjp);
                    }
                }
                return proceed(pjp);
            } catch (Throwable e) {
                throw print(e, pjp);
            } finally {
                long take = System.currentTimeMillis() - start;
                if (take >= 1000) {
                    if (log.isWarnEnabled()) {
                        log.warn("around endpoint [{}] take {}ms", pjp.getTarget().getClass().getSimpleName(), (System.currentTimeMillis() - start));
                    }
                } else {
                    if (log.isInfoEnabled()) {
                        log.info("around endpoint [{}] take {}ms", pjp.getTarget().getClass().getSimpleName(), (System.currentTimeMillis() - start));
                    }
                }
            }
        }

        /**
         * 为了捕获异步异常
         *
         * @param pjp
         * @return
         * @throws Throwable
         */
        private Object proceed(ProceedingJoinPoint pjp) throws Throwable {
            String url = properties.getUrl();
// 测试环境屏蔽
            boolean isTest = url.startsWith("https://test") || url.startsWith("http://test");
            Object result = pjp.proceed();
            if (isTest) {
                return result;
            }

//这里是异步请求,所以直接用try cache 是无效的
            if (result instanceof Flux) {
                Flux<?> r = (Flux<?>) (result);
                return r.onErrorMap(Throwable.class, throwable -> print(throwable, pjp));
            } else if (result instanceof Mono) {
                Mono<?> r = (Mono<?>) (result);
                return r.onErrorMap(Throwable.class, throwable -> print(throwable, pjp));
            }
            return result;
        }

        private final static String WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=*";

        /**
         * 发送异常信息到企业微信
         *
         * @param e
         * @param pjp
         */
        private Throwable print(Throwable e, ProceedingJoinPoint pjp) {
//            if (e instanceof FibException) {
//                return e;
//            }
            ThreadPoolUtils.execute(() -> {
                Object[] args = pjp.getArgs();
                Class<?> targetCls = pjp.getTarget().getClass();
                MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
                String simpleName = targetCls.getSimpleName();
                String method = methodSignature.getName();
                String targetObjectMethodName = simpleName + "." + method;
                String targetMethodParams = format(Arrays.toString(args));
                if (targetMethodParams.length() >= 512) {
                    targetMethodParams = targetMethodParams.substring(0, 512);
                }
                String errorMessage = getStackTrace(e);
                String content = "<font color=\"info\">接口地址:</font>" + targetObjectMethodName + "\n<font color=\"info\">请求报文:</font>" + targetMethodParams + "\n<font color=\"warning\">异常消息:</font>" + errorMessage;
                //markdown内容,最长不超过4096个字节,必须是utf8编码
                if (content.length() >= 2048) {
                    content = truncateUtf8(content);
                }
                WechatWebhookReq webhookReq = WechatWebhookReq.newBuilder().setUrl(WEBHOOK_URL).setMsgType("markdown").setContent(content).build();
                wechatService.send(webhookReq);
            });
            return e;
        }

        private String format(String targetMethodParams) {
            if (targetMethodParams.startsWith("[{") && targetMethodParams.endsWith("}]")) {
                try {
                    JSONObject jsonObject = JSON.parseObject(targetMethodParams.substring(targetMethodParams.indexOf("{"), targetMethodParams.lastIndexOf("}") + 1));
                    return JSONObject.toJSONString(jsonObject, SerializerFeature.WriteNullListAsEmpty);
                } catch (Exception ignore) {
                    return targetMethodParams;
                }
            } else {
                return targetMethodParams;
            }
        }


/**打印完整的日志信息**/
        String getStackTrace(Throwable e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw, true);
            // 打印当前异常的堆栈信息
            e.printStackTrace(pw);
            // 遍历并打印所有被抑制的异常(如果有)
            for (Throwable suppressed : e.getSuppressed()) {
                suppressed.printStackTrace(pw);
            }
            // 递归打印异常的原因链(如果存在)
            Throwable cause = e.getCause();
            while (cause != null) {
                cause.printStackTrace(pw);
                cause = cause.getCause();
            }
            return sw.toString();
        }

        /**
         * 截取UTF-8编码的字符串,确保字节长度不超过指定的最大值。
         *
         * @param str 待截取的字符串
         * @return 截取后的字符串
         */
        private final static int MARK_DOWN_MAX = 4096;

        /**
         * 简单地根据字节数截断UTF-8编码的字符串,可能会导致多字节字符被截断。
         *
         * @param str 待截取的字符串
         * @return 截取后的字符串
         */
        public static String truncateUtf8(String str) {
            byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
            if (bytes.length <= MARK_DOWN_MAX) {
                return str;
            }
            // 直接截取到指定字节数对应的字符位置
            return new String(bytes, 0, MARK_DOWN_MAX, StandardCharsets.UTF_8);
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值