最近在工作中需要给项目添加日志功能,看了项目里面是用了以前项目的日志模块,是通过在不同方法里面通过调用日志新增方法新增的,感觉这个太麻烦了,然后通过网上的一些列子就改了个切面的方式。
我这里要实现是在controller方法上添加一个自定义的注解,声明方法的模块名称,和作用,通过切面类,获取参数,新增日志,保存到数据库。
下面就上代码
首先是我们的自定义注解
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerLog {
//模块名称
String Module() default "";
//方法描述
String Remark() default "";
}
日志实体类,这里记得写get和set方法,或者使用lombok插件
public class Log {
//日志ID
private String lId;
//操作行为
private String lAction;
//备注
private String lRemark;
//操作人
private String lCreator;
//操作时间
private Date lCreatetime;
//模块
private String lModule;
//IP
private String lIp;
}
//日志详情类
public class LogDetail {
private String lId;
private String lKey;
private String lValue;
}
这里就不写service和mapper方法了,其实就是两个新增的方法
然后就是最重要的切面类了
Aspect
@Component
public class SystemLogAspect {
//因为这里使用的线程,所以我使用了ThreadLocal来保存信息
//保存开始时间
private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
//保存用户信息
private static final ThreadLocal<User> currentUser=new NamedThreadLocal<>("ThreadLocal userName");
//保存要新增的日志信息
private static final ThreadLocal<Log> logThreadLocal = new NamedThreadLocal<>("ThreadLocal log");
//保存新增的日志详情
private static final ThreadLocal<LogDetail> logDetailThreadLocal = new NamedThreadLocal<>("ThreadLocal logDetail");
@Autowired(required=false)
private HttpServletRequest request;
//导入线程池
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
private LogServiceI logService;
/**
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(com.atoz.ytl.server.log.ControllerLog)")
public void controllerAspect(){}
/**
* 前置通知 用于拦截Controller层记录用户的操作的开始时间
* @param joinPoint 切点
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException{
Date beginTime=new Date();
beginTimeThreadLocal.set(beginTime);
//因为项目里面是shiro,所以这里获取用户信息是这样子的
//如果没有使用,这里可以直接使用session来获取
User loginUser = (User) SecurityUtils.getSubject().getPrincipal();
currentUser.set(loginUser);
}
/**
* 后置通知 用于拦截Controller层记录用户的操作
* @param joinPoint 切点
*/
@SuppressWarnings("unchecked")
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint) {
//获取当前用户的登录信息
User loginUser = currentUser.get();
if(loginUser == null){
loginUser = (User) SecurityUtils.getSubject().getPrincipal();
if(loginUser==null){
return;
}
}
String userName=loginUser.getLoginName() + "(" + loginUser.getUserFullName() + ")";
HashMap<String, String> controllerMethodDescription=null;
String remoteAddr=request.getRemoteAddr();//请求的IP
String requestUri=request.getRequestURI();//请求的Uri
try {
//调用方法,获取注解参数
controllerMethodDescription = getControllerMethodDescription(joinPoint);
} catch (Exception e) {
e.printStackTrace();
}
Log log = logThreadLocal.get();
if (log == null) {
log = new Log();
}
LogDetail logDetail = logDetailThreadLocal.get();
if (logDetail == null) {
logDetail = new LogDetail();
}
String id= UUID.randomUUID().toString().replace("-", "");
log.setLId(id);
log.setLAction(requestUri);
log.setLCreator(userName);
log.setLIp(remoteAddr);
log.setLModule(controllerMethodDescription.get("module"));
log.setLRemark(controllerMethodDescription.get("remark"));
Date operateDate=beginTimeThreadLocal.get();
log.setLCreatetime(operateDate);
logDetail.setLId(id);
logDetail.setLKey("content");
logDetail.setLValue(userName+controllerMethodDescription.get("remark"));
logThreadLocal.set(log);
logDetailThreadLocal.set(logDetail);
}
@AfterReturning(value = "controllerAspect()", returning = "result")
public void doReturn(Object result) {
Log log = logThreadLocal.get();
if (log == null) {
log = new Log();
}
LogDetail logDetail = logDetailThreadLocal.get();
if (logDetail == null) {
logDetail = new LogDetail();
}
//通过线程池来执行日志保存
threadPoolTaskExecutor.execute(new SaveLogThread(log, logDetail,logService));
currentUser.remove();
beginTimeThreadLocal.remove();
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @return 方法描述
*/
public static HashMap<String,String> getControllerMethodDescription(JoinPoint joinPoint) {
HashMap<String,String> hashMap=new HashMap<>();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ControllerLog controllerLog = method.getAnnotation(ControllerLog.class);
String module = controllerLog.Module();
hashMap.put("module",module);
String remark = controllerLog.Remark();
hashMap.put("remark",remark);
return hashMap;
}
/**
* 保存日志线程
*
* @author lin.r.x
*
*/
private static class SaveLogThread implements Runnable {
private Log log;
private LogServiceI logService;
private LogDetail logDetail;
public SaveLogThread(Log log,LogDetail logDetail, LogServiceI logService) {
this.log = log;
this.logService = logService;
this.logDetail=logDetail;
}
@Override
public void run() {
//这里是两个新增的方法
logService.insertSelective(log);
logService.InsertLogDetail(logDetail);
}
}
}
因为这里使用了ThreadPoolTaskExecutor线程池,所以要在spring的xml中配置bean
<bean id="taskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="WaitForTasksToCompleteOnShutdown" value="true" />
</bean>
最后就是使用注解了,这里是我项目里面的日志删除接口,就在方法上面添加一个注解就完成了
@RequestMapping(value="/deleteLog", method=RequestMethod.POST)
@ControllerLog(Module = "日志管理",Remark = "删除日志")
public void deleteLog( HttpServletRequest req,HttpServletResponse response,@RequestParam(value = "idList") String[] idList) throws BusinessException,IOException {
}