因为是后加的功能,所以原实现不能大面积修改;退一步讲,就算是新开发的项目,考虑添加日志审计功能时也应该尽可能的减少代码的耦合,减少代码侵入
package com.ultra.annotation;
import java.lang.annotation.*;
/**
* 日志审计注解
*
* @author admin
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAudit {
/**
* 账号
*/
String account() default "";
/**
* 模块id对应模块名称(用户,角色,资源等)
*/
String moduleId() default "";
/**
* 操作id对应操作名称(新增、更新、删除等)
*/
String operateId() default "";
/**
* 对象id
*/
String id() default "";
/**
* 对象名称
*/
String name() default "";
}
package com.ultra.bo;
import lombok.Getter;
import lombok.Setter;
/**
* 日志详情
*
* @author fan
*/
@Setter
@Getter
public class LogDetails {
/**
* 账号
*/
private String account;
/**
* 操作
*/
private String operate;
/**
* 模块
*/
private String module;
/**
* id
*/
private String id;
/**
* 名称
*/
private String name;
/**
* 结果
*/
private String result;
@Override
public String toString() {
return "用户[" + account + "]" + operate + module + "id:[" + id + "]" + "name:[" + name + "]" + "结果:[" + result + "]";
}
}
package com.ultra.constant;
/**
* 日志操作id与名称枚举关系
*
* @author fan
*/
public enum LogOperateEnum {
/**
* id与操作对应关系
*/
ADD("01", "新增"),
UPDATE("02", "更新"),
DELETE("03", "删除");
LogOperateEnum(String id, String name) {
this.id = id;
this.name = name;
}
private String id;
private String name;
public static String getValue(String id) {
for (LogOperateEnum operateEnum : LogOperateEnum.values()) {
if (operateEnum.id.equals(id)) {
return operateEnum.name;
}
}
return null;
}
}
package com.ultra.constant;
/**
* 日志模块id与名称枚举关系
*
* @author fan
*/
public enum LogModuleEnum {
/**
* id与操作对应关系
*/
ADD("01", "用户"),
UPDATE("02", "角色"),
DELETE("03", "资源");
LogModuleEnum(String id, String name) {
this.id = id;
this.name = name;
}
private String id;
private String name;
public static String getValue(String id) {
for (LogModuleEnum moduleEnum : LogModuleEnum.values()) {
if (moduleEnum.id.equals(id)) {
return moduleEnum.name;
}
}
return null;
}
}
package com.ultra.dao.entity;
import java.io.Serializable;
import lombok.ToString;
import lombok.Getter;
import lombok.Setter;
/**
* 角色
*
* @author ${author}
* @since 2019-09-06
*/
@Setter
@Getter
@ToString
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
package com.ultra.aspect;
import com.ultra.annotation.LogAudit;
import com.ultra.bo.LogDetails;
import com.ultra.conditional.BeanRegisterConditional;
import com.ultra.constant.LogModuleEnum;
import com.ultra.constant.LogOperateEnum;
import com.ultra.util.ArrayUtil;
import com.ultra.util.StringUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 日志审计切面
*
* @author fan
*/
@Aspect
@Component
public class LogAuditAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAuditAspect.class);
/**
* 获取注解参数当做方法入参
*
* @param joinPoint 切点方法
* @param logAudit 注解参数
* @return 方法执行的返回值
* @throws Throwable 方法执行可能抛的异常
*/
@Around("@annotation(logAudit)")
public Object doAround(ProceedingJoinPoint joinPoint, LogAudit logAudit) throws Throwable {
Object proceed;
LogDetails logDetails = new LogDetails();
try {
// 调度之类没有账号的可以手动指定account
String account = logAudit.account();
if (StringUtil.isBlank(account)) {
// 伪代码实现获取当前账号
account = "admin";
}
String operateId = logAudit.operateId();
String moduleId = logAudit.moduleId();
String id = getElValue(logAudit.id(), joinPoint);
String name = getElValue(logAudit.name(), joinPoint);
logDetails.setAccount(account);
logDetails.setOperate(LogOperateEnum.getValue(operateId));
logDetails.setModule(LogModuleEnum.getValue(moduleId));
logDetails.setId(id);
logDetails.setName(name);
proceed = joinPoint.proceed();
// 这里假定认为没有异常是成功,有异常是失败;根据实际业务判断
logDetails.setResult("成功");
} catch (Throwable throwable) {
logDetails.setResult("失败");
throw throwable;
} finally {
//入库
logger.info("logDetails:{}", logDetails);
}
return proceed;
}
/**
* 用于SpEL表达式解析.
*/
private SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
private String getElValue(String elKey, ProceedingJoinPoint joinPoint) {
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
if (paramNames != null && paramNames.length > 0) {
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(elKey);
Object expressionValue = expression.getValue(context);
if (expressionValue == null) {
return null;
}
return String.valueOf(expressionValue);
}
return null;
}
}
package com.ultra.web;
import com.ultra.annotation.LogAudit;
import com.ultra.dao.entity.Role;
import com.ultra.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 角色
*
* @author fan
* @since 2019-09-06
*/
@RestController
@RequestMapping("/role")
public class RoleController {
@Autowired
private RoleService roleService;
@PostMapping
@LogAudit(moduleId = "02", operateId = "01", id = "#entity.id", name = "#entity.name")
public boolean save(@RequestBody Role entity) {
return super.save(entity);
}
}
@Around("@annotation(logAudit)")
public Object doAround(ProceedingJoinPoint joinPoint, LogAudit logAudit) throws Throwable {}