老大喊我记录下API的操作日志,免得前端甩锅,主要记录新增,修改,删除等操作。我想了下就决定用AOP来实现这个功能。
由于使用的是SpringBoot,所以首先应该在依赖中引入AOP包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
一般引入了AOP之后,一般不用做其他特殊配置,也不用加上@EnableAspectJAutoProxy注解。但是它仍有两个属性需要我们注意
# 相当于@EnableAspectJAutoProxy
spring.aop.auto=true
# 默认使用jdk来实现AOP,如果想要使用CGLIB的话,这里要改成true
spring.aop.proxy-target-class=false
实现日志记录功能
我想要记录某个API对模块进行了什么操作,操作的key,对于修改,删除来说我们记录id,对于新增来说,最开始没有id,我们记录name即可(也可以是其他属性),所以OpLog注解是这样的
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface OpLog {
// 模块
String module();
// 类型
String opType();
// 级别,默认一般
String level() default OpLevel.COMMON;
// 需要记录的key
String key();
}
为了让这个功能更加易用,所以我要求其他开发者必须在Controller上加上注解@OpLog,为什么是Controller呢?因为API是在Controller上,我并不关心具体业务,我只要记录对应的操作即可。在Controller层你只需要这要做就行了
@RestController
@RequestMapping("/user")
public class CpGroupController {
// 注意这里的userParam和@RequestBody的userParam名字必需要要一致
@OpLog(module = "用户管理", opType = "新增", key = "userParam.name")
@PostMapping("/add")
public BaseResponse<Boolean> add(@RequestBody UserParam userParam) {
return ResponseUtil.success(userService.add(userParam));
}
}
接下来就是切面代码的编写了,其中我们要记录访问的url以及必要的操作信息。
@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();
}
// 获取想要记录的key
private Object getOpLogRecordKey(ProceedingJoinPoint joinPoint, OpLog opLog) {
String key = opLog.key();
if(StringUtils.isEmpty(key)) {
return null;
}
String[] keys = key.split("\\.");
//入参 value
Object[] args = joinPoint.getArgs();
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
// 获取Controller方法上的参数名称
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;
}
// 设置属性可访问,因为bean当中属性一般都是private
Field field = fieldOptional.get();
field.setAccessible(true);
if(idx + 1 == keys.length) {
return field.get(param);
}
return getParamValue(field.get(param), keys, idx + 1);
}
// 递归获取所有Field
public List<Field> getAllFields(List<Field> fieldList, Class<?> type) {
// 获取当前类的所有Field
fieldList.addAll(Arrays.asList(type.getDeclaredFields()));
// 获取父类的所有Field
Class<?> superClass = type.getSuperclass();
if(superClass != null && superClass != Object.class) {
getAllFields(fieldList,superClass);
}
return fieldList;
}
// 获取访问URL
private String extractRequestUrl() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request.getRequestURL().toString();
}
}
主要就是记录了操作了什么模块,操作内容等等,对于修改和删除操作我们可以记录id,但是对于save操作,我们没有办法记录id,只能记录其他属性,比如说name,就可以记录保存的数据了
{
name: "zhangsan",
age: 20
}
对于上面的数据就会把"zhangsan"这个值保存下来,后面就可以通过查询日志表知道是谁操作的,修改和删除也是同样的.
网友评论