前言
对于一个已经发布的服务而言,通常我们会记录接口的调用日志,就类似收费接口按调用次数来进行收费,但是它的作用也不仅限于此,比如:在本人的小程序中,通过会通过接口调用次数来分析哪些接口调用次数少,哪些调用的多,进而分析该功能是不是应该下线(此前发布过问题反馈和在线做题的功能,但是上线后使用次数过于低,所以就把这个功能删掉了)。
这个功能可以衍生为链路追踪,但是由于本人小程序接口并不是分布式架构,这个链路追踪也就没有做,所以下一次再带来链路追踪的文章了(用小程序广告费买了几台服务器,代码重构后链路追踪就需要做了,很感谢点广告的小伙伴
)。
正文
本人小程序项目为Maven构建的多模块项目(Maven多模块项目,通过合理的模块拆分,实现代码的复用,便于维护和管理),我拆分模块如下图,其中api就是小程序调用的接口,cache是Redis相关操作,core是基本工具类,mq是RocketMQ的相关操作,orm是操作DB,search是ES搜索的封装(ES还没有完全实现,因为目前用不到ES,只是用于学习)
其中core模块中就实现了关于接口请求记录,实现方式如标题所言,使用切面加上自定义注解来实现。下面了解一下我的实现方式(相信大家也可以有更好的方案)。
首先就是自定义注解了:@Inherited保证能作用到子类上。 关于注解可以看之前的文章
解读Java 注解 (Annotation)
代码语言:javascript复制
package com.xcx.core.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TraceAnnotation {
String desc();
String traceID() default "";
}
实现注解后我这里定义了Controller的基类,API模块所有的Controller都继承该类,以此来保证AOP能作用到所有Controller,当然直接将注解写在Controller也是可以的,就是比较麻烦了。
代码语言:javascript复制package com.xcx.core.common;
import com.alibaba.fastjson.JSONObject;
import com.xcx.core.annotation.TraceAnnotation;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
/**
* @Auther: 陈龙
* @Date: 2018-08-05 15:22
* @Description:
*/
@TraceAnnotation(desc = "请求记录")
public class BaseController {
protected JSONObject convertRequestBody() {
String param = null;
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
request.setCharacterEncoding("UTF-8");
BufferedReader br = request.getReader();
String buffer = null;
StringBuffer buff = new StringBuffer();
while ((buffer = br.readLine()) != null) {
buff.append(buffer "n");
}
br.close();
param = buff.toString();
return JSONObject.parseObject(param);
} catch (Exception var6) {
return null;
}
}
}
自定义注解实现了,下面就是切面了,在记录日志的时候,我并没有使用多线程,因为当时服务器太Low,感觉完全没必要,其中RequestUserInfo就是上一篇文章学弟聊到的ThreadLocal保存的用户信息,如果没有请求没有经过拦截,那就-1,表明该请求不需要记录调用者。需要说明的是Pointcut可以使用execution来匹配切面范围,我这里使用注解来强调切面作用范围。
关于切面也可以看我之前实现AOP的文章。
Spring AOP的简单应用
动手实现AOP
代码语言:javascript复制package com.xcx.core.trace;
import com.xcx.core.common.RequestUserInfo;
import com.xcx.orm.dao.TraceDao;
import com.xcx.orm.vo.UserVO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @Auther: 陈龙
* @Date: 2018-11-05 10:28
* @Description:
*/
@Component("请求追踪记录")
@Aspect
public class RequestTrace {
@Autowired
private TraceDao traceDao;
private final static Logger LOG = LoggerFactory.getLogger(RequestTrace.class);
@Pointcut("@within(com.xcx.core.annotation.TraceAnnotation)")
public void trace() {
}
@Around("trace()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long st = System.currentTimeMillis();
Object o = pjp.proceed();
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String url = request.getRequestURL().toString();
long et = System.currentTimeMillis();
String time = (et - st) "";
LOG.info("请求URL:{},耗时:{}", url, time);
UserVO userVO = RequestUserInfo.get();
traceDao.insertTrace(url, time, userVO == null ? -1 : userVO.getId());
return o;
}
}
走到这一步,核心的工作就算完成了,但是由于项目是多模块的,所以API的pom文件中需要引入core的项目,此外我们还需要注意一点,就是需要在启动类上加上如下代码,因为SpringBoot无法自动加载配置类RequestTrace,所以需要我们手动导入(关于SpringBoot自动加载配置类,玩SpringBoot有必要了解下,基本上我面试的时候说到SpringBoot都会聊到自动加载)其中RedisCache是Redis模块的配置类。
代码语言:javascript复制@Import({RequestTrace.class, RedisCache.class})
最后就是Controller层的类要继承BaseController了。
代码语言:javascript复制public class XcxArticleController extends BaseController
启动API使用小程序调用在数据库就会记录每次请求,在小程序的后台会将这些数据解析成chart图,可以直观看到哪个模块访问量高。