环绕切面实现日志功能
/**
* 环绕切面 接口调用日志
*/
@Aspect
@Component
public class ApiLogInterceptor {
@Autowired
private ApiLogService apiLogService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<>();
//定义切点
//路径参考自己项目的controller路径
@Pointcut(value = "execution(* com.test.test.test.test.*.*(..))")//切点定义到包下的所有类所有方法
public void aopWebLog() {
}
@Around("aopWebLog()")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
startTime.set(System.currentTimeMillis());
//使用ServletRequestAttributes请求上下文获取方法更多
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//因为HttpServletRequest的inputStream只能被读取一次,所以将其封装成ContentCachingRequestWrapper,body被读取后,会被它缓存
RequestWrapper requestWrapper = new RequestWrapper(request);
HttpServletResponse response = attributes.getResponse();
//定义入参变量
String inparams = null;
String className = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
String name = getName(className, methodName);
Enumeration headerNames = request.getHeaderNames();
//根据环绕切面获取入参
Object[] args = pjp.getArgs();
//区别是那种请求
/*这里其实不用区分,直接用if里面的方法即可,
只是else里面拿不到post方法的入参 ,我比较嫌弃if里的格式不好看,所以会做一个区分*/
if ("POST".equalsIgnoreCase(request.getMethod())){
Object arg = args[args.length - 1];
AppDevFullyVo arg1 = (AppDevFullyVo) arg;
logger.info("切面参数为:{}", arg1.toString());
logger.info("res是:{}",inparams=arg1.toString());
}else {
//当入参是一个对象时,没法拿到
inparams = inparams(request);
logger.info("res是:{}",inparams);
}
//调用整个目标函数执行
Object obj = pjp.proceed();
//返回值
Object ret = pjp.proceed(args);
String outparams = ((Result) ret).getResult().toString();
String message = ((Result) ret).getMessage();
logger.info("返回值为:"+outparams);
logger.info("结果信息为:"+message);
ApiLogPo apiLogPo = new ApiLogPo();
apiLogPo.setCreatetime(new DateTime());
apiLogPo.setName(name);
apiLogPo.setDataorigin(null);
apiLogPo.setRequestheader(JSONObject.toJSONString(headerNames));
apiLogPo.setInparams(inparams);
apiLogPo.setIp(WebLogAspect.getClientIp(request));
apiLogPo.setOutparams(outparams);
apiLogPo.setResult(message);
int elapsedtime = (int) (System.currentTimeMillis() - startTime.get());
apiLogPo.setElapsedtime(elapsedtime);
apiLogService.insert(apiLogPo);
return obj;
}
private String getName(String className,String methodName) throws ClassNotFoundException {
//通过反射获取到类
Class cl1 = Class.forName(className);
//拼接方法名,用作后面的比较
StringBuffer stringBuffer = new StringBuffer("/").append(methodName);
String s = stringBuffer.toString();
//获取类中所有的方法
Method[] methods = cl1.getDeclaredMethods();
//接口中文名称
String apiOperationValue = null;
//接口英文名
String mapping= null;
for (Method method : methods) {
//跳过没有注解发方法(不是接口)
if (method.getDeclaredAnnotations().length == 0) {
break;
}
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
//获取get请求
GetMapping getAnnotation = method.getDeclaredAnnotation(GetMapping.class);
if (getAnnotation != null) {
mapping = getAnnotation.value()[0];
logger.info("GetMapping"+mapping);
//如果接口名和获取到的接口名相等,并且apiOperation不为空,就代表是我们请求的接口。拿到接口中文名后就不需要继续循环了,直接返回
if (s.equals(mapping) && ObjectUtil.isNotEmpty(apiOperation)){
apiOperationValue = apiOperation.value();
logger.info("GetMapping apiOperationValue"+apiOperationValue);
break;
}
continue;
}
//获取post请求
PostMapping postAnnotation = method.getDeclaredAnnotation(PostMapping.class);
if (postAnnotation != null) {
mapping = postAnnotation.value()[0];
logger.info("PostMapping"+mapping);
if (s.equals(mapping) && ObjectUtil.isNotEmpty(apiOperation)){
apiOperationValue = apiOperation.value();
logger.info("PostMapping apiOperationValue"+apiOperationValue);
break;
}
continue;
}
//获取delete请求
DeleteMapping deleteAnnotation = method.getDeclaredAnnotation(DeleteMapping.class);
if (deleteAnnotation != null) {
mapping = deleteAnnotation.value()[0];
logger.info("DeleteMapping"+mapping);
if (s.equals(mapping) && ObjectUtil.isNotEmpty(apiOperation)){
apiOperationValue = apiOperation.value();
logger.info("DeleteMapping apiOperationValue"+apiOperationValue);
break;
}
continue;
}
//因为这里目前只涉及到get、post、delete请求,所以别的请求没有做判断,如有需要,可以自行增加
}
return apiOperationValue;
}
private String inparams(HttpServletRequest request){
String result = null;
Map<String, String> res = new HashMap<String, String>();
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
}
}
result = String.valueOf(res);
return result;
}
}
当时做的时候还有另一种需求,仅供参考:需要知道当前点击的页面属于系统的哪个模块下的
我们拿到request里面的referer,分割后会拿到页面路由,再去路由表中进行匹配,拿到模块名称
路由表测试数据
因为这里拿路由信息去拼装,每次都会请求数据库,会造成没必要的数据库压力。按照公司的一些奇怪的要求,不让存到redis,所以放到了一个全局变量中。下面代码中匹配节点名称应该用递归去拼接,但是这里没有,有兴趣的可以自己尝试一下
/**
* 根据referer(前端页面路由)获取所属模块
*
* @param referer
* @return
*/
private String getModule(String referer) {
if (StrUtil.isBlank(referer)) {
return null;
}
//获取一把锁
Lock lock = new ReentrantLock();
if (CollectionUtil.isEmpty(menuDtoList)) {
try {
lock.lock();
//拿到所有pid是0的
List<MenuDto> result = menuApiService.list(DriConstants.DRI_NAMESPACE, null).getResult();
List<MenuDto> privateMenuList = new ArrayList<>(result);
logger.info("size:{}", menuDtoList.size());
for (int i = 0; i < privateMenuList.size(); i++) {
List<MenuDto> dri1 = menuApiService.list(DriConstants.DRI_NAMESPACE, privateMenuList.get(i).getId()).getResult();
privateMenuList.addAll(dri1);
}
menuDtoList = privateMenuList;
logger.info("if里面的全局变量大小为:" + menuDtoList.size() + "打印时间为:" + new DateTime());
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
System.out.println("释放锁..." + Thread.currentThread().getId());
lock.unlock();
}
}
String[] split = referer.split(DriConstants.DRI_NAMESPACE);
StringBuffer sb = new StringBuffer();
String s = split[(split.length - 1)];
sb.append("/dri").append(s);
//过滤掉componenturi为空的 不然会空指针异常
List<MenuDto> collect1 = menuDtoList.stream()
.filter(item -> StrUtil.isNotBlank(item.getComponenturi())).collect(Collectors.toList());
//过滤出符合referer
List<MenuDto> collect = collect1.stream()
.filter(item -> item.getComponenturi().equals(sb.toString()))
.distinct()
.collect(Collectors.toList());
//如果referer不在表里,直接将referer返回出去
if (CollectionUtil.isEmpty(collect)) {
return sb.toString();
}
StringBuffer module = new StringBuffer();
StringBuffer module1 = new StringBuffer();
StringBuffer module2 = new StringBuffer();
//如果referer的pid是0,直接返回模块名称
for (MenuDto dto : collect) {
module.append(dto.getName());
if (0 == dto.getPid()) {
return module.toString();
}
}
collect.forEach(item -> {
if (ObjectUtil.isEmpty(module)) {
module.append(item.getName());
}
menuDtoList.stream()
.filter(m -> m.getId().equals(item.getPid()))
.collect(Collectors.toList())
.forEach(p -> {
module1.append(p.getName()).append("/").append(module);
if (p.getPid() != 0) {
menuDtoList.stream().filter(o -> o.getId().equals(p.getPid()))
.collect(Collectors.toList())
.forEach(u -> {
module2.append(u.getName()).append("/").append(module1);
});
}
});
});
logger.info("module为:" + (ObjectUtil.isEmpty(module2) ? String.valueOf(module1) : String.valueOf(module2)));
return ObjectUtil.isEmpty(module2) ? String.valueOf(module1) : String.valueOf(module2);
}