Hello大家好,本章我们添加aop异步记录日志功能 。有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
创建core→aop文件夹
在aop文件夹下中创建注解,其中remark为记录的备注
package com.example.demo.core.aop; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AnnotationLog { String remark() default ""; }
创建切面
注:关于 SystemLog
类,之前 集成generator自动生成model,xml,dao功能这篇文章 提到过的,忘记的可以再去看一下
package com.example.demo.core.aop; import com.alibaba.fastjson.JSON; import com.example.demo.core.systemlog.SystemLogQueue; import com.example.demo.core.ret.ServiceException; import com.example.demo.core.utils.ApplicationUtils; import com.example.demo.model.SystemLog; import com.example.demo.model.UserInfo; import com.example.demo.service.SystemLogService; import org.apache.ibatis.javassist.*; import org.apache.ibatis.javassist.bytecode.CodeAttribute; import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute; import org.apache.ibatis.javassist.bytecode.MethodInfo; import org.apache.shiro.SecurityUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @Description: aop记录操作日志 * @author 张瑶 * @date 2018年5月28日 18:43:28 */ @Aspect @Component public class AspectLog { private static final Logger logger = LoggerFactory.getLogger(AspectLog.class); @Resource private SystemLogService systemLogService; /** * 定义切点 */ @Pointcut("@annotation(com.example.demo.core.aop.AnnotationLog)") public void methodCachePointcut() { } @Before("methodCachePointcut()") public void doBefore(JoinPoint p) throws Exception{ SystemLog systemLog = getSystemLogInit(p); systemLog.setLogType(SystemLog.LOGINFO); systemLogService.insert(systemLog); } /** * 调用后的异常处理 * @param p * @param e */ @AfterThrowing(pointcut = "methodCachePointcut()", throwing = "e") public void doAfterThrowing(JoinPoint p, Throwable e) throws Throwable { //业务异常不用记录 if(!(e instanceof ServiceException)) { try { SystemLog systemLog =getSystemLogInit(p); systemLog.setLogType(SystemLog.LOGERROR); systemLog.setExceptionCode(e.getClass().getName()); systemLog.setExceptionDetail(e.getMessage()); systemLogService.insert(systemLog); } catch (Exception ex) { logger.error("==异常通知异常=="); logger.error("异常信息:{}", ex.getMessage()); } } } private SystemLog getSystemLogInit(JoinPoint p){ SystemLog systemLog = new SystemLog(); try{ //类名 String targetClass = p.getTarget().getClass().toString(); //请求的方法名 String tartgetMethod = p.getSignature().getName(); //获取类名 UserController String classType = p.getTarget().getClass().getName(); Class<?> clazz = Class.forName(classType); String clazzName = clazz.getName(); //请求参数名+参数值的map Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, tartgetMethod, p.getArgs()); systemLog.setId(ApplicationUtils.getUUID()); systemLog.setDescription(getMthodRemark(p)); systemLog.setMethod(targetClass+"."+tartgetMethod); //大家可自行百度获取ip的方法 systemLog.setRequestIp("192.168.1.104"); systemLog.setParams(JSON.toJSONString(nameAndArgs)); systemLog.setUserId(getUserId()); systemLog.setCreateTime(new Date()); }catch (Exception ex){ logger.error("==异常通知异常=="); logger.error("异常信息:{}", ex.getMessage()); } return systemLog; } /** * 通过反射机制 获取被切参数名以及参数值 * * @param cls * @param clazzName * @param methodName * @param args * @return * @throws NotFoundException */ private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException { Map<String, Object> map = new HashMap<>(); ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(cls); pool.insertClassPath(classPath); CtClass cc = pool.get(clazzName); CtMethod cm = cc.getDeclaredMethod(methodName); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; for (int i = 0; i < cm.getParameterTypes().length; i++) { //HttpServletRequest 和HttpServletResponse 不做处理 if(!(args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse)){ //paramNames即参数名 map.put(attr.variableName(i + pos), JSON.toJSONString(args[i])); } } return map; } /** * 获取方法的中文备注____用于记录用户的操作日志描述 * @param joinPoint * @return * @throws Exception */ private static String getMthodRemark(JoinPoint joinPoint) throws Exception { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] method = targetClass.getMethods(); String methode = ""; for (Method m : method) { if (m.getName().equals(methodName)) { Class[] tmpCs = m.getParameterTypes(); if (tmpCs.length == arguments.length) { AnnotationLog methodCache = m.getAnnotation(AnnotationLog.class); if (methodCache != null) { methode = methodCache.remark(); } break; } } } return methode; } private static String getUserId() { String userId = ""; UserInfo userInfo = (UserInfo) SecurityUtils.getSubject().getPrincipal(); if(userInfo != null){ userId = userInfo.getId(); } return userId; } }
在UserInfoController的selectById上添加我们刚创建的注解
@PostMapping("/selectById") @AnnotationLog(remark = "查询") public RetResult<UserInfo> selectById(@RequestParam String id) { UserInfo userInfo = userInfoService.selectById(id); return RetResponse.makeOKRsp(userInfo); }
输入localhost:8080/userInfo/selectById
拿到结果
查看数据库System_log
可以看到我们的日志
我们知道,日志系统主要是方便我们分析程序,定位异常的。但是对于用户来说,用户一点都不在乎,所以我们不能因为需要记录日志,而延长用户的等待时间,所以,这里我们创建队列来异步执行记录日志操作
1:创建批量添加方法
SystemLogMapper.xml
<insert id="insertByBatch" parameterType="java.util.List" > insert into system_log ( id, description, method, log_type, request_ip, exception_code, exception_detail, params, user_id, create_time ) values <foreach collection="list" item="item" index= "index" separator =","> ( #{item.id,jdbcType=VARCHAR}, #{item.description,jdbcType=VARCHAR}, #{item.method,jdbcType=VARCHAR}, #{item.logType,jdbcType=VARCHAR}, #{item.requestIp,jdbcType=VARCHAR}, #{item.exceptionCode,jdbcType=VARCHAR}, #{item.exceptionDetail,jdbcType=VARCHAR}, #{item.params,jdbcType=VARCHAR}, #{item.userId,jdbcType=VARCHAR}, #{item.createTime,jdbcType=TIMESTAMP} ) </foreach>
SystemLogMapper.java
Integer insertByBatch(List<SystemLog> list);
Service层可参考码云地址,这里就不做示例
2:创建日志的存放队列
创建core→systemlog文件夹
在该文件夹下创建 SystemLogQueue
package com.example.demo.core.systemlog; import com.example.demo.model.SystemLog; import org.springframework.stereotype.Component; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @Component public class SystemLogQueue { private BlockingQueue<SystemLog> blockingQueue = new LinkedBlockingQueue<>(); public void add(SystemLog systemLog) { blockingQueue.add(systemLog); } public SystemLog poll() throws InterruptedException { return blockingQueue.poll(1, TimeUnit.SECONDS); } }
3:创建日志队列的消费者
package com.example.demo.core.systemlog; import com.example.demo.model.SystemLog; import com.example.demo.service.SystemLogService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Component public class SystemLogConsumer implements Runnable{ private static Logger logger = LoggerFactory.getLogger(SystemLogConsumer.class); public static final int DEFAULT_BATCH_SIZE = 64; private SystemLogQueue auditLogQueue; private SystemLogService systemLogService; private int batchSize = DEFAULT_BATCH_SIZE; private boolean active = true; private Thread thread; @PostConstruct public void init() { thread = new Thread(this); thread.start(); } @PreDestroy public void close() { active = false; } @Override public void run() { while (active) { execute(); } } public void execute() { List<SystemLog> systemLogs = new ArrayList<>(); try { int size = 0; while (size < batchSize) { SystemLog systemLog = auditLogQueue.poll(); if (systemLog == null) { break; } systemLogs.add(systemLog); size++; } } catch (Exception ex) { logger.info(ex.getMessage(), ex); } if (!systemLogs.isEmpty()) { try { //休眠10秒来模拟业务复杂,正在计算,测试之后大家别忘记删除这句话 Thread.sleep(10000); systemLogService.insertByBatch(systemLogs); }catch (Exception e){ logger.error("异常信息:{}", e.getMessage()); } } } @Resource public void setAuditLogQueue(SystemLogQueue auditLogQueue) { this.auditLogQueue = auditLogQueue; } @Resource public void setAuditLogService(SystemLogService systemLogService) { this.systemLogService = systemLogService; } public void setBatchSize(int batchSize) { this.batchSize = batchSize; } }
4:修改切面AspectLog
将:
@Resource private SystemLogService systemLogService;
修改为:
@Resource private SystemLogQueue systemLogQueue;
将:
systemLogQueue.insert(systemLog);
修改为:
systemLogQueue.add(systemLog);
5:测试
输入localhost:8080/userInfo/selectById
注意:这里是立刻返回结果,并没有等待10秒,10秒之后打开数据库,得到我们刚操作的日志
gitee.com/beany/mySpr…
写文章不易,如对您有帮助,请帮忙点下star
添加aop异步记录日志功能已完成,后续功能接下来陆续更新,有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢大家。