老大喊我记录下API的操作日志,免得前端甩锅,主要记录新增,修改,删除等操作。我想了下就决定用AOP来实现这个功能。
由于使用的是SpringBoot,所以首先应该在依赖中引入AOP包。
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
一般引入了AOP之后,一般不用做其他特殊配置,也不用加上@EnableAspectJAutoProxy注解。但是它仍有两个属性需要我们注意
1 2 3 4 5
| # 相当于@EnableAspectJAutoProxy spring.aop.auto=true
# 默认使用jdk来实现AOP,如果想要使用CGLIB的话,这里要改成true spring.aop.proxy-target-class=false
|
实现日志记录功能
我想要记录某个API对模块进行了什么操作,操作的key,对于修改,删除来说我们记录id,对于新增来说,最开始没有id,我们记录name即可(也可以是其他属性),所以OpLog注解是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface OpLog {
String module(); String opType(); String level() default OpLevel.COMMON; String key(); }
|
为了让这个功能更加易用,所以我要求其他开发者必须在Controller上加上注解@OpLog,为什么是Controller呢?因为API是在Controller上,我并不关心具体业务,我只要记录对应的操作即可。在Controller层你只需要这要做就行了
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController @RequestMapping("/user") public class CpGroupController {
@OpLog(module = "用户管理", opType = "新增", key = "userParam.name") @PostMapping("/add") public BaseResponse<Boolean> add(@RequestBody UserParam userParam) { return ResponseUtil.success(userService.add(userParam)); } }
|
接下来就是切面代码的编写了,其中我们要记录访问的url以及必要的操作信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| @Component @Aspect @Slf4j public class OpLogAspect {
@Autowired private OperateLogService operateLogService;
int paramNameIndex = 0; int propertyIndexFrom = 1;
@Pointcut("execution(public * com.generalthink.springboot.web.controller..*.*(..))") public void webLogPointcut() { }
@Around(value = "webLogPointcut() && @annotation(opLog)", argNames = "joinPoint,opLog") public Object around(ProceedingJoinPoint joinPoint, OpLog opLog) throws Throwable {
Object objReturn = joinPoint.proceed();
try { OperateLog operationLog = generateOperateLog(joinPoint, opLog);
operateLogService.save(operationLog); } catch (Exception e) { log.error("operateLog record error", e); }
return objReturn; }
private OperateLog generateOperateLog(ProceedingJoinPoint joinPoint, OpLog opLog) {
String requestUrl = extractRequestUrl();
Object recordKey = getOpLogRecordKey(joinPoint, opLog);
return OperateLog.builder() .module(opLog.module()) .opType(opLog.opType()) .level(opLog.level()) .operateTimeUnix(CommonUtils.getNowTimeUnix()) .recordKey(recordKey != null ? recordKey.toString() : null) .url(requestUrl) .operator(getCurrentUser()) .build(); }
private String getCurrentUser() { Subject subject = SecurityUtils.getSubject(); return (String) subject.getPrincipal(); }
private Object getOpLogRecordKey(ProceedingJoinPoint joinPoint, OpLog opLog) { String key = opLog.key();
if(StringUtils.isEmpty(key)) { return null; }
String[] keys = key.split("\\.");
Object[] args = joinPoint.getArgs();
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
String[] paramNames = codeSignature.getParameterNames();
Object paramArg = null;
for (int i = 0; i < paramNames.length; i++) { String paramName = paramNames[i]; if (paramName.equals(keys[paramNameIndex])) { paramArg = args[i]; break; } }
if(keys.length == 1) { return paramArg; }
Object paramValue = null; if(null != paramArg) { try { paramValue = getParamValue(paramArg, keys, propertyIndexFrom); } catch (IllegalAccessException e) { log.error("parse field error", e); } }
return paramValue; }
public Object getParamValue(Object param, String[] keys, int idx) throws IllegalAccessException { Optional<Field> fieldOptional = getAllFields(new ArrayList<>(), param.getClass()) .stream() .filter(field -> field.getName().equalsIgnoreCase(keys[idx])) .findFirst();
if(!fieldOptional.isPresent()) { return null; } Field field = fieldOptional.get(); field.setAccessible(true);
if(idx + 1 == keys.length) { return field.get(param); }
return getParamValue(field.get(param), keys, idx + 1); }
public List<Field> getAllFields(List<Field> fieldList, Class<?> type) {
fieldList.addAll(Arrays.asList(type.getDeclaredFields())); Class<?> superClass = type.getSuperclass(); if(superClass != null && superClass != Object.class) { getAllFields(fieldList,superClass); }
return fieldList; } private String extractRequestUrl() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = attributes.getRequest();
return request.getRequestURL().toString(); } }
|
主要就是记录了操作了什么模块,操作内容等等,对于修改和删除操作我们可以记录id,但是对于save操作,我们没有办法记录id,只能记录其他属性,比如说name,就可以记录保存的数据了
1 2 3 4 5
| { name: "zhangsan", age: 20 }
|
对于上面的数据就会把”zhangsan”这个值保存下来,后面就可以通过查询日志表知道是谁操作的,修改和删除也是同样的.