使用自定义注解,日志切面(前置通知 后置通知,获取controller的error执行结果),多线程
首先写个自定义注解
/**
* 系统日志自定义注解
*
* @author weijianxing
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于参数或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLog {
/**
* 日志名称
*
* @return
*/
String description() default "";
}
然后写AOP切面
@Aspect
@Component
public class SysLogAspect {
/**
* Controller层切点,注解方式
*/
// @Pointcut("execution(* *..controller..*Controller*.*(..))")
@Pointcut("@annotation(com.face.ele.api.common.annotation.SystemLog)")
public void controllerAspect() {
}
/**
* 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
*
* @param joinPoint 切点
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException {
//线程绑定变量(该数据只有当前请求的线程可见)
Date beginTime = new Date();
beginTimeThreadLocal.set(beginTime);
}
/**
* 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
* @author weijianxing
* @param joinPoint 切点
* @param Object 方法执行的结果
*/
@AfterReturning(returning="obj",pointcut ="@annotation(com.face.ele.api.common.annotation.SystemLog)")
public void after(JoinPoint joinPoint,Object obj) {
System.out.println("获取目标返回值" + obj);
try {
//这里获取切点的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//判断这个日志是否记录
boolean flag = true;
SystemLog perms = method.getAnnotation(SystemLog.class);
//这里要通过日志配置的权限码和每个访问的注解权限码匹配
QueryWrapper<OperationLogConfigEntity> codeWrapper = new QueryWrapper<>();
codeWrapper.eq("LOG_CONFIG_CODE", perms.type());
OperationLogConfigEntity logConfig = configService.getOne(codeWrapper);
if (perms != null) {
if (logConfig.getLogRecord() == 0) {
flag = false;
}
}
//下面是获取保存日志的各个参数
Map<String, String[]> logParams = request.getParameterMap();
String principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
//用户
String userId = null;
if (!"anonymousUser".equals(principal)) {
UserDetails user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SysUser sysUser = userService.findByUsername(user.getUsername());
userId = sysUser.getId();
}
//获取注解中对方法的描述信息
Map params = getControllerMethodInfo(joinPoint);
//日志类型
int type = MapUtil.getInt(params, "type");
OperationLogEntity logEntity = new OperationLogEntity();
//请求开始时间
long beginTime = beginTimeThreadLocal.get().getTime();
long endTime = System.currentTimeMillis();
//请求耗时
Long logElapsedTime = endTime - beginTime;
//这里是第一种情况保存,默认方法前加了需要添加注释的方法,并且日志记录中配置了需要记录日志
this.saveAllOperateByAnnotation(flag,perms,logEntity,obj,logConfig,method,joinPoint,type,
params,userId,logParams, logElapsedTime);
} catch (Exception e) {
log.error("AOP后置通知异常", e);
}
}
/**
* 获取所有的参数
* @author weijianxing
*/
public void saveAllOperateByAnnotation(boolean flag,SystemLog sysLog,OperationLogEntity logEntity,Object obj,
OperationLogConfigEntity logConfig,Method method,JoinPoint joinPoint,int type,
Map params,String userId,Map<String, String[]> logParams,Long logElapsedTime ){
//请求用户
if (sysLog != null) {
logEntity.setLogContent(sysLog.description());
} else {
//没有加SystemLog的,表示其他默认系统操作的
//默认没有加该注解,就用API的内容描述(其他每个类就不需要都去添加该注解)
ApiOperation otherLog = method.getAnnotation(ApiOperation.class);
logEntity.setLogContent(otherLog.value());
}
//判断日志上面有标注了类型的,没有就表示默认的都归为 普通的系统操作
logEntity.setLogType(type);
//这里获取每个方法的参数
Object[] args = joinPoint.getArgs();
String param = new Gson().toJson(args);
if (param.length() < 5000)
logEntity.setLogParams(param);
else {
param = new Gson().toJson(args[1]);
logEntity.setLogParams(param);
}
logEntity.setOperationTime(new Date());
logEntity.setOperationUser(userId);
logEntity.setLogUrl(request.getRequestURI());
logEntity.setCostTime(logElapsedTime.intValue());
log.info("本次请求耗时:{}毫秒;请求参数{},路径{}", logElapsedTime, JSONUtil.toJsonStr(logParams), request.getRequestURI());
//先调用含有错误日志的,如果没有错误的就直接调用 默认添加日志的
saveErrorOperate(flag,logEntity,obj,logConfig,type,userId,logElapsedTime);
}
/**
* 这里保存第二次含有错误日志信息
* 获取 controller中执行 的 error的信息,可以获取错误的信息 进行保存
* @author weijianxing
*/
public void saveErrorOperate(boolean flag,OperationLogEntity logEntity,Object obj,OperationLogConfigEntity logConfig,int type,String userId, Long logElapsedTime){
//这里是第二种情况保存,controller中error的信息,可以获取错误的信息 进行保存
String jsonStr = JSON.toJSONString(obj);
JSONObject resultJson = JSON.parseObject(jsonStr);
//只记录错误信息,保存到数据库日志中
if (resultJson.get("success") != null) {
if (resultJson.get("success").equals(false)) {
//获取当前操作的日志等级
int level=0;
if(logConfig!=null){
level=logConfig.getLogLevel();
}
//获取信息保存登录错误信息
OperationLogEntity operateLog=new OperationLogEntity(
type,userId,new Date(),new Gson().toJson(request.getParameterMap()),
resultJson.get("message").toString(),logElapsedTime.intValue(),
request.getRequestURI(),level,1);
operationLogService.addOperateLog(operateLog);
}else{
if (flag) {
//调用线程保存
ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(logEntity, operationLogService));
}
}
}else{
if (flag) {
//调用线程保存
ThreadPoolUtil.getPool().execute(new SaveSystemLogThread(logEntity, operationLogService));
}
}
}
/**
* 保存日志至数据库
*/
private static class SaveSystemLogThread implements Runnable {
private OperationLogEntity log;
private OperationLogService logService;
public SaveSystemLogThread(OperationLogEntity esLog, OperationLogService logService) {
this.log = esLog;
this.logService = logService;
}
@Override
public void run() {
logService.save(log);
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
public static Map<String, Object> getControllerMethodInfo(JoinPoint joinPoint) throws Exception {
Map<String, Object> map = new HashMap<String, Object>(16);
//获取目标类名
String targetName = joinPoint.getTarget().getClass().getName();
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取相关参数
Object[] arguments = joinPoint.getArgs();
//生成类对象
Class targetClass = Class.forName(targetName);
//获取该类中的方法
Method[] methods = targetClass.getMethods();
String description = "";
Integer type = null;
Integer subtype = null;
for (Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
Class[] clazzs = method.getParameterTypes();
if (clazzs.length != arguments.length) {
//比较方法中参数个数与从切点中获取的参数个数是否相同,原因是方法可以重载
continue;
}
description = method.getAnnotation(SystemLog.class).description();
type = method.getAnnotation(SystemLog.class).type().getValue();
subtype = method.getAnnotation(SystemLog.class).logSubType().getValue();
map.put("description", description);
map.put("type", type);
map.put("subtype", subtype);
}
return map;
}
数据库
CREATE TABLE `ele_operation_log_config` (
`log_config_id` bigint(20) NOT NULL AUTO_INCREMENT,
`log_config_name` varchar(50) DEFAULT NULL COMMENT '日志配置名称',
`log_config_code` varchar(50) DEFAULT NULL COMMENT '日志配置权限码',
`sort` int(11) DEFAULT '0' COMMENT '排序',
`log_record` int(2) DEFAULT NULL COMMENT '是否记录日志(0:不记录,1:记录)',
`remark` varchar(50) DEFAULT NULL COMMENT '描述',
`log_level` int(2) DEFAULT '0' COMMENT '日志等级(0:普通,1:一般,2:重要)',
PRIMARY KEY (`log_config_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
CREATE TABLE `ele_operation_log` (
`operation_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '操作日志表id',
`log_type` int(1) DEFAULT NULL COMMENT ' 1、系统操作, 2,用户绑定角色操作, 3 用户管理,4 登录等操作',
`log_sub_type` int(1) DEFAULT NULL COMMENT '日志类型,1增加、2修改、3删除',
`operation_user` varchar(33) DEFAULT NULL COMMENT '操作人',
`operation_time` datetime DEFAULT NULL COMMENT '操作时间',
`log_params` varchar(5000) DEFAULT NULL COMMENT '操作关键参数',
`log_content` varchar(100) DEFAULT NULL COMMENT '操作内容',
`log_url` varchar(255) DEFAULT NULL,
`log_path` varchar(255) DEFAULT NULL COMMENT '操作路径',
`cost_time` int(10) DEFAULT NULL COMMENT '耗时',
`log_level` int(2) DEFAULT '0' COMMENT '日志等级(0:正常1:异常错误 2:越权访问 3:)',
`log_result` int(2) DEFAULT '0' COMMENT '操作结果 0:成功,1:失败',
PRIMARY KEY (`operation_log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1328185403790069763 DEFAULT CHARSET=utf8 COMMENT='操作日志表';