AOP+SPEL监控接口返回数据

📅 发布时间:2026/7/3 11:44:51 👁️ 浏览次数:
AOP+SPEL监控接口返回数据
引入依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency自定义注解importjava.lang.annotation.*;Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)DocumentedpublicinterfaceMonitorApi{// 模块名称Stringmodule()defaultDefault;// 成功的条件表达式 (SpEL)例如: #result.code 200 或 #result.success 或 #result[status] OKStringsuccessCondition();// 提取错误信息的表达式 (SpEL)用于报警展示。例如: #result.message 或 #result.errorMsgStringerrorMsg()default;}注解实现类importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.expression.EvaluationContext;importorg.springframework.expression.Expression;importorg.springframework.expression.spel.standard.SpelExpressionParser;importorg.springframework.expression.spel.support.StandardEvaluationContext;importorg.springframework.stereotype.Component;importjava.lang.reflect.Method;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;AspectComponentSlf4jpublicclassDynamicApiMonitorAspect{// SpEL 表达式解析器privatefinalSpelExpressionParserparsernewSpelExpressionParser();// 缓存解析过的表达式提升性能 (极其重要)privatefinalMapString,ExpressionexpressionCachenewConcurrentHashMap();Around(annotation(com.vivo.ai.data.crawl.noresult.MonitorApi))// TODO: 替换为你的包路径publicObjectdoMonitor(ProceedingJoinPointjoinPoint)throwsThrowable{longstartTimeSystem.currentTimeMillis();MethodSignaturesignature(MethodSignature)joinPoint.getSignature();Methodmethodsignature.getMethod();MonitorApimonitorApimethod.getAnnotation(MonitorApi.class);StringinvokeMethodjoinPoint.getTarget().getClass().getSimpleName().method.getName();Objectresultnull;try{// 1. 放行执行真实的业务接口逻辑resultjoinPoint.proceed();// 2. 使用 SpEL 动态校验返回值checkResultBySpEL(invokeMethod,monitorApi,result,startTime);}catch(Throwablee){// 系统异常原样捕获报警 (代码略参考前一个回答)log.error([API系统异常] 接口: [{}] ,invokeMethod,e);throwe;}returnresult;}privatevoidcheckResultBySpEL(StringinvokeMethod,MonitorApiconfig,Objectresult,longstartTime){if(resultnull){log.warn([API告警] 接口: [{}] 返回值为 null,invokeMethod);return;}try{// 构建 SpEL 上下文将接口的返回值放入上下文中命名为 resultEvaluationContextcontextnewStandardEvaluationContext();context.setVariable(result,result);// 1. 解析并执行【成功条件表达式】ExpressionsuccessExpgetExpression(config.successCondition());BooleanisSuccesssuccessExp.getValue(context,Boolean.class);// 2. 如果判断为失败 (不符合成功条件)if(Boolean.FALSE.equals(isSuccess)){StringerrorMsg未知错误;// 3. 动态提取错误信息 (如果配置了 errorMsg 表达式)if(!config.errorMsg().equals()){ExpressionerrorExpgetExpression(config.errorMsg());StringparsedMsgerrorExp.getValue(context,String.class);if(parsedMsg!null){errorMsgparsedMsg;}}longcostTimeSystem.currentTimeMillis()-startTime;log.warn([业务告警拦截] 模块: [{}], 接口: [{}], 耗时: {}ms, 失败原因: {},config.module(),invokeMethod,costTime,errorMsg);// TODO: 触发真实报警动作...}else{log.info([API正常] 接口: [{}] 返回值: {},invokeMethod,result);}}catch(Exceptione){log.error([监控框架错误] 接口 [{}] SpEL 表达式解析失败请检查 MonitorApi 配置。返回值: {},invokeMethod,result,e);}}/** * 获取 SpEL 表达式 (带本地缓存避免高并发下每次都 Parse) */privateExpressiongetExpression(StringelString){returnexpressionCache.computeIfAbsent(elString,parser::parseExpression);}自定义的返回结果类importlombok.Data;Data// 注意必须有 Getter 方法SpEL 才能读取到属性Lombok 的 Data 完美契合。publicclassThirdPartyResponse{privateStringerrCode;// 假设对应 JSON的 err_codeprivateStringerrMsg;// 假设对应 JSON的 err_msgprivateOrderDatadata;DatapublicstaticclassOrderData{privateStringorderNo;privateStringstatus;}// 你甚至可以在对象内部封装好判断逻辑publicbooleanisSuccess(){return0000.equals(this.errCode);}}测试类importcom.alibaba.fastjson.JSONObject;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Service;ServiceSlf4jpublicclassNoResultTest{MonitorApi(modulejsontest,successCondition#result[code] 0 ,errorMsg#result[msg])publicJSONObjecttest(){JSONObjectJSONObjectnewJSONObject();JSONObject.put(code,0);JSONObject.put(msg,success);JSONObject.put(data,sadasd);returnJSONObject;}MonitorApi(modulejsontest,successCondition#result.isSuccess(),errorMsg#result[msg])publicThirdPartyResponsetest2(){ThirdPartyResponseresponsenewThirdPartyResponse();response.setErrCode(0000);response.setErrMsg(success);ThirdPartyResponse.OrderDataorderDatanewThirdPartyResponse.OrderData();orderData.setOrderNo(123456789);orderData.setStatus(11);response.setData(orderData);returnresponse;}}