接口日志表
id | int | |
---|---|---|
request_url | varchar | 请求url |
method | varchar | 请求方式 |
request_time | varchar | 接口响应时间 |
params | text | 请求参数 |
ip | varchar | 客户端IP |
operation_desc | varchar | 操作描述 |
create_date | datetime | 创建时间 |
环绕通知
在实际开发中应用非常广泛,所以下面用环绕通知
去实现。
创建注解
/anotation/Log.java
// 这个注解用来对controller方法进行描述
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
// 来一个属性,描述这个方法是用来干嘛的
String desc();
}
在接口上加注解
controller/StudentController.java
@GetMapping
@Log(desc = "这是学生列表接口")
public List<Student> list() {
...
}
切面类
/aop/LogAspect.java
// @Pointcut用来标识切点
// RestfulStudentController.*(),指RestfulStudentController里面的所有方法
// @Pointcut("execution(* com.ca.controller.StudentController.*())")
@Pointcut("execution(* com.ca.controller.StudentController.*(..))") // 所有方法的所有参数.*(..)
public void pointcut() {
}
// 添加常量
private static final String title="SpringMvc action report -----";
// 用ThreadLocal来获取时间,new一个ThreadLocal后在里面初始化SimpleDateFormat对象
/*
ThreadLocal操作值的时候是取得当前线程的ThreadLocalMap对象,然后把值设置到了这个对象中,这样对于不同的线程得到的就是不同
的ThreadLocalMap,那么向其中保存值或者修改值都只是会影响到当前线程,这样就保证了线程安全。
*/
private static final ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal() {
@Override
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
@Around("pointcut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) {
// 拼接标题(考虑到线程安全,可以用StringBuffer)
StringBuffer sb = new StringBuffer(title);
// 然后在标题后需要拼接当前时间
// 如果使用SimpleDateFormat的话,他是非线程安全类,所以在这里需要去使用ThreadLocal。
sb.append(sdf.get().format(new Date())).append("\n");
String url = RequestUtils.getRequestUrl();// 通过工具类获取当前请求的url路径
// 通过request获取当前的请求方式
HttpServletRequest request = RequestUtils.getRequest();
String method = request.getMethod();
sb.append("Url: ").append(method).append(" ").append(url).append("\n");
// 获取controller
Class<?> controllerClass = proceedingJoinPoint.getTarget().getClass();// 获取到controller类
sb.append("Controller: ").append(controllerClass).append("\n");
// 获取方法
Object[] args = proceedingJoinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
String methodName = targetMethod.getName();
sb.append("Method: ").append(methodName).append("\n");
sb.append("Params: ").append(JSONUtil.toJsonStr(args)).append("\n");
// 获取接口描述
// 要先获取注解
Log log = targetMethod.getAnnotation(Log.class);
if (log != null) {
// 如果注解不为空,获取注解上面的描述
Striing desc = log.desc();
sb.append("接口描述: ").append(desc).append("\n");
}
Object result;
try {
// 这个接口的执行时间
long startTime = System.currentTimeMillis();// 获取当前时间
result = proceedingJoinPoint.proceed();// 执行目标方法
// 接口的执行时间(当前时间 - startTime),由于是毫秒值,所以加上" ms"
long executionTime = System.currentTimeMillis() - startTime;
sb.append("接口响应时间: ").append(executionTime).append(" ms").append("\n");
// 把上面的信息保存到数据库
// 由于保存日志不会影响到系统的正常使用,为了系统效率更高,建议这里new线程后使用异步保存。
// 这样做的话,可以提升接口的响应时间。
// TODO:new Thread()...
// 打印
logger.info(sb.toString());
}catch(Throwable e) {
// 还可以捕获异常
logger.error(e.getMessage(), e);
throw e;
}
// 执行目标方法后执行后置通知
System.out.println("执行后置通知");
return result;
}
工具类
RequestUtils.java
public class RequestUtil {
// 获取当前线程的request对象
public static HttpServletRequest getRequest() {
try {
return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
}catch(Exception e) {
return null;
}
}
public static String getRequestUrl() {
return getRequestUrl(getRequest());
}
// 获取接口请求路径(获取当前的接口地址)
public static String getRequestUrl(HttpServletRequest request) {
String contentPath = request.getServletContext().getContextPath();
int contentPathLength = contentPath.length();
String target = request.getRequestURI();
if(contentPathLength != 0) {
target = target.substring(contentPathLength);
}
return target;
}
}
启动服务后,访问列表接口,在控制台就能看到刚才拼接的信息。
Url: GET /student
Controller: class com.ca.controller.StudentController
Method: list
Params: []【只有在浏览器传递参数后才会显示,跟方法里面的参数无关】
接口描述: 这是学生列表接口
接口响应时间: 125 ms
以上内容源于网络。
版权声明:本文为PurineKing原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。