javaWEB SSM AOP+注解保存操作日志
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaWEB SSM AOP+注解保存操作日志相关的知识,希望对你有一定的参考价值。
本篇文章的诞生离不开这篇文章的作者:http://blog.csdn.net/czmchen/article/details/42392985。
前言
操作日志在javaWeb的业务系统中是在是太常见的功能了,主要记录用户再什么时间,什么位置进行了什么操作。如果每新增一个功能都要写一个插入代码的话,是非常不容易维护的。加一个字段就要在每个插入语句上加入这个字段。所以AOP+注解的优势就显现了出来,不仅如此,当我们有了这套代码以后,可以通用在该系统的wap端或者其他的系统中,不必修改太多的代码。针对日志这种实时性不是很高的功能,这里用了异步的方式进行,这样日志系统独立出来,不会影响业务。下面是我整理的代码,欢迎留下宝贵意见。这里再次感谢原作者提供良好的思路。
代码
一、核心类
1.自定义注解 拦截Controller
/** * * 自定义注解 拦截Controller * @see [相关类/方法] * @since [产品/模块版本] */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SystemControllerLog { String description() default "";//描述 String moduleType() default "";//模块代码 String operateValue() default "";//操作类型 boolean firstParamName() default false; }
2.自定义注解 拦截service
/** * * 自定义注解 拦截service * @see [相关类/方法] * @since [产品/模块版本] */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SystemServiceLog { String description() default "";// 描述 String moduleType() default "";// 模块代码 String operateValue() default "";// 操作类型 }
3.AOP可以拦截到controller的配置
spring.xml中加入下面这句话
<aop:aspectj-autoproxy proxy-target-class="true" />
4.切点类
这里用的是返回通知,用来接收成功或失败。
/** * * AOP记录操作&异常日志-切点类 * @see [相关类/方法] * @since [产品/模块版本] */ @Aspect @Component public class SystemLogAspect { private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class); // 队列 private static BlockingQueue<Log> queue = new LinkedBlockingQueue<Log>(); // 缓存线程池 private static ExecutorService threadPool = Executors.newFixedThreadPool(3); // 任务数 private static int taskSize = 6; // 线程是否已启动 boolean isStartThread = false; // 用来启动或停止线程 static boolean run = true; @Autowired private LogService logService; @Autowired private UserService userService; // Service层切点 @Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemServiceLog)") public void serviceAspect() { } // Controller层切点 @Pointcut("@annotation(com.rzzl.wap.log.annotation.SystemControllerLog)") public void controllerAspect() { } public static BlockingQueue<Log> getQueue() { return queue; } public static void setQueue(BlockingQueue<Log> queue) { SystemLogAspect.queue = queue; } public static boolean isRun() { return run; } public static void setRun(boolean run) { SystemLogAspect.run = run; } /** * * 返回通知 用于拦截Controller层记录用户的操作 * @param joinPoint 切点 * @param result 返回值 * @see [类、类#方法、类#成员] */ @AfterReturning(value = "controllerAspect()", returning = "result") public void afterReturn(JoinPoint joinPoint, Object result) { // 请求的IP User user = WebUtils.getSessionValue(LoginContact.SESSION_USER); String params = ""; WebResult webResult = new WebResult(); webResult.setCode(FlagContact.BACK_SUCCESS); try { if (WebResult.class.isInstance(result)) { webResult = (WebResult)result; } String loginName = ""; InnnerBean innnerBean = getControllerMethodDescription(joinPoint); Object[] arguments = innnerBean.getArguments(); String remark = innnerBean.getDescription(); Log log = new Log.Builder().type(LogTypes.type.operate) .moduleType(innnerBean.getModuleType()) .operateCode(joinPoint.getSignature().getName()) .operateValue(innnerBean.getOperateValue()) .remark(remark) .operateStatus(webResult.getCode().equals(FlagContact.BACK_SUCCESS) ? LogTypes.operateStatus.Y : LogTypes.operateStatus.N)// 返回值1操作成功,否则失败 .method((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")) .param(params) .loginName(user.getAccountNo()) .fullName(user.getUserName()) .build(); // 放入队列 queue.put(log); if (!isStartThread) { for (int i = 0; i < taskSize; i++) { threadPool.execute(new saveLogThread()); } isStartThread = true; } } catch (Exception e) { logger.error("异常信息:{}", e.toString()); } } /** * 异常通知 用于拦截service层记录异常日志 * @param joinPoint * @param e * @see [类、类#方法、类#成员] */ @AfterThrowing(pointcut = "serviceAspect()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Throwable e) { // 读取session中的用户 User user = WebUtils.getSessionValue(LoginContact.SESSION_USER); String params = ""; try { if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) { for (int i = 0; i < joinPoint.getArgs().length; i++) { params += JSONUtils.valueToString(joinPoint.getArgs()[i].toString()) + ";"; } } InnnerBean innnerBean = getServiceMthodDescription(joinPoint); String loginName = ""; Log log = new Log.Builder().type(LogTypes.type.exception) .moduleType(innnerBean.getModuleType()) .operateCode(joinPoint.getSignature().getName()) .operateValue(innnerBean.getOperateValue()) .remark(innnerBean.getDescription()) .operateStatus(LogTypes.operateStatus.N) .method( (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")) .param(params) .exceptionDetail(e.toString()) .build(); // 放入队列 queue.put(log); if (!isStartThread) { new Thread(new saveLogThread()).start(); isStartThread = true; } } catch (Exception ex) { logger.error("异常信息:{}", ex.toString()); } finally { logger.error("异常方法:{" + joinPoint.getTarget().getClass().getName() + "}异常代码:{" + joinPoint.getSignature().getName() + "}异常信息:{" + e.toString() + "}参数:{" + params + "}"); } } /** * 获取注解中对方法的描述信息 用于service层注解 * @param joinPoint 切点 * @return 方法描述 * @throws Exception * @see [类、类#方法、类#成员] */ @SuppressWarnings("rawtypes") public static InnnerBean getServiceMthodDescription(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[] methods = targetClass.getMethods(); String moduleType = ""; String operateValue = ""; String description = ""; InnnerBean innnerBean = new InnnerBean(moduleType, operateValue, description); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { SystemServiceLog annotation = method.getAnnotation(SystemServiceLog.class); moduleType = annotation.moduleType(); operateValue = annotation.operateValue(); description = annotation.description(); innnerBean = new InnnerBean(moduleType, operateValue, description); break; } } } innnerBean.setArguments(arguments); return innnerBean; } /** * 获取注解中对方法的描述信息 用于Controller层注解 * @param joinPoint 切点 * @return 方法描述 * @throws Exception * @see [类、类#方法、类#成员] */ @SuppressWarnings("rawtypes") public static InnnerBean getControllerMethodDescription(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[] methods = targetClass.getMethods(); String moduleType = ""; String operateValue = ""; String description = ""; boolean firstParamName = false; InnnerBean innnerBean = new InnnerBean(moduleType, operateValue, description); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { SystemControllerLog annotation = method.getAnnotation(SystemControllerLog.class); moduleType = annotation.moduleType(); operateValue = annotation.operateValue(); description = annotation.description(); firstParamName = annotation.firstParamName(); innnerBean = new InnnerBean(moduleType, operateValue, description); innnerBean.setFirstParamName(firstParamName); break; } } } innnerBean.setArguments(arguments); return innnerBean; } /** * * 内部类封装注入信息 * @see [相关类/方法] * @since [产品/模块版本] */ static class InnnerBean { private String moduleType;// 模块代码 private String description;// 描述 private String operateValue;// 操作类型 private boolean firstParamName; private Object[] arguments; public InnnerBean(String moduleType, String operateValue, String description) { super(); this.moduleType = moduleType; this.description = description; this.operateValue = operateValue; } public String getOperateValue() { return operateValue; } public void setOperateValue(String operateValue) { this.operateValue = operateValue; } public String getModuleType() { return moduleType; } public void setModuleType(String moduleType) { this.moduleType = moduleType; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Object[] getArguments() { return arguments; } public void setArguments(Object[] arguments) { this.arguments = arguments; } public boolean isFirstParamName() { return firstParamName; } public void setFirstParamName(boolean firstParamName) { this.firstParamName = firstParamName; } } /** * * 异步保存日志 * @see [相关类/方法] * @since [产品/模块版本] */ class saveLogThread implements Runnable { Lock lock = new ReentrantLock(); @Override public void run() { try { while (run) { while (queue.size() != 0) { // 如果对插入顺序无要求,此处不需要同步可提升效率 lock.lock(); Log log = queue.take(); logService.insert(log); lock.unlock(); } Thread.sleep(3000); } } catch (InterruptedException e) { logger.error("saveLogThread被唤醒:" + e.toString()); } catch (Exception e) { logger.error("saveLogThread异常:" + e.toString()); } } } }
二、定值类
日志系统中的定值
/** * * 日志系统中的定值 * * @see [相关类/方法] * @since [产品/模块版本] */ public interface LogTypes { /** * * 操作状态(成功与否Y\N) * @see [相关类/方法] * @since [产品/模块版本] */ static interface operateStatus { // 成功 final String Y = "Y"; // 失败 final String N = "N"; } /** * * 日志类型 * @see [相关类/方法] * @since [产品/模块版本] */ static interface type { // 操作日志 final String operate = "operate"; // 异常日志 final String exception = "exception"; } /** * * 模块类型 * @see [相关类/方法] * @since [产品/模块版本] */ static interface moduleType { // 登录模块 final String LOGIN = "LOGIN"; // 项目模块 final String PROJECT = "PROJECT"; // 客户模块 final String CUSTOMER = "CUSTOMER"; // 用户模块 final String SYS_USER = "SYS_USER"; } /** * * 操作类型 * @see [相关类/方法] * @since [产品/模块版本] */ static interface operateValue { // 查询 final String select = "select"; // 登录 final String login = "login"; // 保存 final String save = "save"; // 新增 final String add = "add"; // 修改 final String edit = "edit"; // 删除 final String delete = "delete"; // 查看 final String view = "view"; // 修改密码 final String editPassword = "editPassword"; // 上传 final String upload = "upload"; // 下载 final String down = "down"; // 下载 final String packagedown = "packagedown"; } /** * * 保存描述的前缀 * 方便于批量修改该备注 * @see [相关类/方法] * @since [产品/模块版本] */ static interface Prefix { // 查询 final String savePrefix = "新增/编辑"; } }
三、实体类
1、日志实体
应用了Builder设计模式
/** * * 操作日志&异常日志 * @see [相关类/方法] * @since [产品/模块版本] */ public class Log implements Serializable { /** * serialVersionUID */ private static final long serialVersionUID = 1L; private static final String reqSource;// 请求来源,pc:pc端,wap:wap端 默认来源为pc private static final String localAddr;// 服务器IP private String ip;// 操作电脑ip private String fullName;// 操作人员名字 private String loginName;// 操作人员登录账号 private Date operateDateTime;// 操作时间 private Date createDateTime;// 创建时间 private Long id; private String type;// 日志类型,‘operate’:操作日志,‘exception’:异常日志 private String moduleType;// 模块代码 private String operateCode;// 操作代码 private String operateValue;// 操作类型 private String remark;// 操作备注(记录参数) private String operateStatus;// 操作状态(成功与否Y\N) private String method;// 调用方法 private String param;// 方法的请求参数 private String exceptionDetail;// 异常信息 static{ reqSource = "wap"; localAddr = WebUtils.getLocalAddr(); } public void init() { User user = WebUtils.getSessionValue(LoginContact.SESSION_USER); ip = WebUtils.getRemoteIP(); loginName = user != null ? user.getAccountNo() : getLoginName(); fullName = user != null ? user.getUserName() : getFullName(); operateDateTime = new Date(); createDateTime = new Date(); } public Log() { init(); } public Log(Log origin) { init(); this.id = origin.id; this.type = origin.type; this.moduleType = origin.moduleType; this.operateCode = origin.operateCode; this.operateValue = origin.operateValue; this.remark = origin.remark; this.operateStatus = origin.operateStatus; this.method = origin.method; this.param = origin.param; this.exceptionDetail = origin.exceptionDetail; this.fullName = origin.fullName; this.loginName = origin.loginName; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public Date getOperateDateTime() { return operateDateTime; } public void setOperateDateTime(Date operateDateTime) { this.operateDateTime = operateDateTime; } public Date getCreateDateTime() { return createDateTime; } public void setCreateDateTime(Date createDateTime) { this.createDateTime = createDateTime; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getModuleType() { return moduleType; } public void setModuleType(String moduleType) { this.moduleType = moduleType; } public String getOperateCode() { return operateCode; } public void setOperateCode(String operateCode) { this.operateCode = operateCode; } public String getOperateValue() { return operateValue; } public void setOperateValue(String operateValue) { this.operateValue = operateValue; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public String getOperateStatus() { return operateStatus; } public void setOperateStatus(String operateStatus) { this.operateStatus = operateStatus; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getParam() { return param; } public void setParam(String param) { this.param = param; } public String getExceptionDetail() { return exceptionDetail; } public void setExceptionDetail(String exceptionDetail) { this.exceptionDetail = exceptionDetail; } public String getLocalAddr() { return localAddr; } public static class Builder { private Log target; public Builder() { target = new Log(); } public Builder id(Long id) { target.id = id; return this; } public Builder type(String type) { target.type = type; return this; } public Builder moduleType(String moduleType) { target.moduleType = moduleType; return this; } public Builder operateCode(String operateCode) { target.operateCode = operateCode; return this; } public Builder operateValue(String operateValue) { target.operateValue = operateValue; return this; } public Builder remark(String remark) { target.remark = remark; return this; } public Builder operateStatus(String operateStatus) { target.operateStatus = operateStatus; return this; } public Builder method(String method) { target.method = method; return this; } public Builder param(String param) { target.param = param; return this; } public Builder exceptionDetail(String exceptionDetail) { target.exceptionDetail = exceptionDetail; return this; } public Builder loginName(String loginName) { target.loginName = loginName; return this; } public Builder fullName(String fullName) { target.fullName = fullName; return this; } public Log build() { return new Log(target); } } }
2.返回结果实体
/** * @ClassName: WebResult * @version 1.0 * @Desc: WEB返回JSON结果 * @history v1.0 */ public class WebResult implements Serializable { private static final long serialVersionUID = -4776437900752507269L; /** * 返回消息 */ private String msg; /** * 返回码 */ private String code; /** * 返回数据 */ private Object data; private Map<?,?> map; private List<Map<String, Object>> list; public WebResult() { } public WebResult(String msg, String code) { super(); this.msg = msg; this.code = code; } public WebResult(String msg, String code, Object data) { super(); this.msg = msg; this.code = code; this.data = data; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Map<?, ?> getMap() { return map; } public void setMap(Map<?, ?> map) { this.map = map; } public List<Map<String, Object>> getList() { return list; } public void setList(List<Map<String, Object>> list) { this.list = list; } @Override public String toString() { return "WebResult [msg=" + msg + ", code=" + code + ", data=" + data + "]"; } /** * 初始失败方法 * * @author cc HSSD0473 * @see [类、类#方法、类#成员] */ public void invokeFail(){ this.data = null; this.code = FlagContact.BACK_FAIL; this.msg = "操作失败"; } public void invokeFail(String msg){ this.data = null; this.code = FlagContact.BACK_FAIL; if(msg != null && !msg.equals("")) { this.msg = msg; } } public void invokeSuccess() { this.code = FlagContact.BACK_SUCCESS; this.msg = "操作成功"; } public void invokeSuccess(String msg) { if(msg != null && !msg.equals("")) { this.msg = msg; } this.code = FlagContact.BACK_SUCCESS; } }
三、工具类
获取登录用户,客户ip主机ip等方法
** * @ClassName: WebUtils * @version 1.0 * @Desc: WebUtils * @history v1.0 */ public class WebUtils { /** * 描述:获取request对象 * @return */ public static HttpServletRequest getRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } /** * 描述:获取responce对象 * @return */ public static HttpServletResponse getResponse() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); } /** * 描述:获取session * @return */ public static HttpSession getSession() { return getRequest().getSession(); } /** * 描述:设置session值 * @param key * @param val */ public static <T> void setSessionValue(String key, T val) { getSession().setAttribute(key, val); } /** * 描述:获取session值 * @param key * @return */ @SuppressWarnings("unchecked") public static <T> T getSessionValue(String key) { return (T) getSession().getAttribute(key); } /** * 描述:移除session * @param key * @return */ @SuppressWarnings("unchecked") public static <T> T removeSessionValue(String key) { Object obj = getSession().getAttribute(key); getSession().removeAttribute(key); return (T) obj; } /** * 描述:获取客户端ip * @param request * @return */ public static String getRemoteIP(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip; } /** * * 获得本机IP * @return * @throws Exception * @see [类、类#方法、类#成员] */ public final static String getLocalAddr() { String hostAddress = ""; try { hostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (Exception e) { } return hostAddress; } /** * 描述:获取客户端ip * @return */ public static String getRemoteIP() { HttpServletRequest request = getRequest(); return getRemoteIP(request); } }
四、清理工作
tomcat停止前,异步日志的清理动作
/** * * tomcat停止前,异步日志的清理动作 * @see [相关类/方法] * @since [产品/模块版本] */ @WebListener() public class BeforeDestoryListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { } @Override public void contextDestroyed(ServletContextEvent sce) { while (SystemLogAspect.getQueue().size() == 0) { SystemLogAspect.setRun(false); break; } } }
数据库
用的是mysql,其他数据库请自行更改语句
CREATE TABLE `t_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `reqSource` varchar(10) DEFAULT 'pc' COMMENT '请求来源,pc:pc端,wap:wap端 默认来源为pc', `type` varchar(10) DEFAULT NULL COMMENT '日志类型,‘operate’:操作日志,‘exception’:异常日志', `ip` varchar(20) NOT NULL COMMENT '操作电脑ip', `fullName` varchar(50) NOT NULL COMMENT '操作人员名字', `loginName` varchar(50) NOT NULL COMMENT '操作人员登录账号', `moduleType` varchar(50) NOT NULL COMMENT '模块代码', `operateCode` varchar(50) NOT NULL COMMENT '操作代码', `operateValue` varchar(50) DEFAULT NULL COMMENT '操作类型', `operateDateTime` datetime NOT NULL COMMENT '操作时间', `createDateTime` datetime NOT NULL COMMENT '创建时间', `remark` varchar(100) DEFAULT NULL COMMENT '操作备注(记录参数)', `operateStatus` varchar(20) DEFAULT NULL COMMENT '操作状态(成功与否Y\N)', `localAddr` varchar(20) DEFAULT NULL COMMENT '服务器IP', `method` varchar(100) DEFAULT NULL COMMENT '调用方法', `param` varchar(2000) DEFAULT NULL COMMENT '方法的请求参数', `exceptionDetail` varchar(1000) DEFAULT NULL COMMENT '异常信息', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3430 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
测试及结果
控制层代码
/** * 登录请求 * * @param userName 用户名 * @param password 密码 * @return WebResult msg:系统反馈消息 code:登录标识码 * @see [类、类#方法、类#成员] */ @ResponseBody @RequestMapping("login") @SystemControllerLog(moduleType=LogTypes.moduleType.LOGIN,operateValue=LogTypes.operateValue.login,description = "登录动作") public WebResult userLogin(String accoutnNo, String password) { WebResult wt = new WebResult(); try { // 登录逻辑 } catch(Exception e) { log.error("登录异常:" + e.toString()); wt.invokeFail(); } return wt; }
数据库结果
以上是关于javaWEB SSM AOP+注解保存操作日志的主要内容,如果未能解决你的问题,请参考以下文章