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);
}
}
}
spring web flux 记录用户日志及异常日志
于 2024-07-09 11:01:41 首次发布
1460

被折叠的 条评论
为什么被折叠?



