说在前面

前两天项目经理给了一个需求,就是把所有的rest请求的日志记录下来并存到数据库中,要包含:

  1. 请求的信息:参数、方法、rest地址
  2. 响应的信息:状态、描述、数据
  3. 操作的记录:操作的业务模块、业务名称、操作的类型(SELECT|INSERT|DELETE|UPDATE)
  4. 请求人信息:userid、username(业务用户,登录后token里带着id)

我本来想的是用正常的增删改查业务去写这个功能,但是写到一半我就放弃了,传参、dto转entity真的会累死人啊,就想着用注解去完成这个业务,虽然中间的业务差不多,但个人感觉业务层面的耦合少了很多。

注解+AOP实现自定义业务存库功能

定义自定义注解

  1. @Target({ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface MyLog{
  5. //操作的服务模块
  6. String operationModule() default "";
  7. //操作的类型
  8. String operationType() default "";
  9. }

注解中定义的函数式参数就是后面在使用时传进来的参数。

控制层调用

触发注解的地方就在控制层

  1. @RestController
  2. @RequestMapping("/demo/api")
  3. @Api(value = "示例接口")
  4. public class DemoController {
  5. @Autowired
  6. private IDemoServie demoServie;
  7. @ApiOperation(value = "获取示例数据", httpMethod = "GET", notes = "记录存储数据")
  8. @RequestMapping(value = "/getAll", method = {RequestMethod.GET})
  9. @XkSfLog(operationType = "SELECT", operationModule = "测试模块")
  10. public APIResponse<DemoDto> page(PageQueryDto<DemoDto> pageDto) {
  11. List<DemoDto> dList=demoServie.getAll();
  12. return new APIResponse<DemoDto>(dList);
  13. }
  14. }

定义切面

切面需要用到:

  • @Aspect:将一个类定义为切面类
  • @Pointcut:定义一个切点 ```java @Aspect @Component public class XkSfLogAspect { ThreadLocal startTime = new ThreadLocal();

    @Autowired private XkSfLogService xkSfLogService;

    public XkSfLogAspect() { }

    @Pointcut(“@annotation(com.xk.sifang.api.utils.log.XkSfLog)”) public void sfLogPointCut() { }

    @Before(“sfLogPointCut()”) public void doBefore(JoinPoint joinPoint) {

    1. this.startTime.set(System.currentTimeMillis());

    }

    @AfterReturning(

    1. value = "sfLogPointCut()",
    2. returning = "response"

    ) /**

    • 正常请求日志存库 */ public void saveOperLog(JoinPoint joinPoint, Object response) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = (HttpServletRequest)requestAttributes.resolveReference(“request”); XkSfLogDto xkSfLogDto = new XkSfLogDto();

      try {

      1. XkSfLog annotationXkSfLog = (XkSfLog)method.getAnnotation(XkSfLog.class);
      2. ApiOperation annotationApi = (ApiOperation) method.getAnnotation(ApiOperation.class);
      3. //获取从注解传来的参数
      4. if (annotationXkSfLog != null) {}
      5. //从api注解中获取请求方法、业务详情、业务名称
      6. if(annotationApi != null){}
      7. //记录请求信息
      8. if (null != response) {
      9. //记录响应数据
      10. }
      11. //调用业务层存入成功请求的日志
      12. xkSfLogService.saveXkSfLog(xkSfLogDto);

      } catch (Exception e) {

      1. e.printStackTrace();

      } }

      @AfterThrowing(

      1. pointcut = "sfLogPointCut()",
      2. throwing = "e"

      ) /**

    • 异常业务日志存库 */ public void saveExceptionLog(JoinPoint joinPoint, Throwable e) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = (HttpServletRequest)requestAttributes.resolveReference(“request”); XkSfLogDto excepLog = new XkSfLogDto();

      try {

      1. XkSfLog annotationXkSfLog = (XkSfLog)method.getAnnotation(XkSfLog.class);
      2. ApiOperation annotationApi = (ApiOperation)method.getAnnotation(ApiOperation.class);
      3. //获取从注解传来的参数
      4. if(annotationXkSfLog != null){}
      5. if (annotationApi != null) {}
      6. //记录请求信息
      7. //记录响应失败的信息
      8. excepLog.setResStatus("ERROR");
      9. excepLog.setResDescription(e.getMessage());
      10. //异常业务存库
      11. xkSfLogService.saveXkSfLog(excepLog);

      } catch (Exception var12) {

      1. var12.printStackTrace();

      } }

      /**

    • 工具方法 */ public Map converMap(Map paramMap) { Map rtnMap = new HashMap(); Iterator var3 = paramMap.keySet().iterator();

      while(var3.hasNext()) {

      1. String key = (String)var3.next();
      2. rtnMap.put(key, ((String[])paramMap.get(key))[0]);

      }

      return rtnMap; }

}

```

调用rest接口并查看数据库

image.png

小结

这样的话,就要比传一大堆参数来记录业务爽得多了

参考文章