SSM实现日志存储数据库

日志的dao、server、serverImpl的实现,我就不写了
以下代码复制粘贴可用,保存操作自己写实现。
引入依赖
AOP切入编程依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

创建注解

package com.common.log;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用于controller层的日志注解(可用在service层的)
 * @author YYQ
 *
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogData {

	/**
	 * 操作模块
	 * @return
	 */
	String module() default "";
	
	/**
	 * 操作说明
	 * @return
	 */
	String remarks() default ""; 
	
	
}

创建实体类

package com.modules.entity.sys;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.common.entity.MyEntity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

/**
 * 日志实体类
 * @author yyq
 *
 */
@SuppressWarnings("serial")
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper=true)
@TableName("sys_log")
public class LogEntity extends MyEntity{

	@TableId(type=IdType.AUTO)	
	private Integer id;				// 日志ID
	private String moduleName;		// 模块名称
	private String moduleOperation;	// 模块操作
	private String method;			// 方法名
	private String ip;				// 操作人IP地址
	private Long userId;			// 操作用户ID
	private String userName;		// 操作用户名称
	private String param;			// 请求参数
	private String classUrl;		// 类的包路径
	private Integer type;			// 日志类型(1 正常, 2异常)
	private String remarks;			// 备注
	

}

创建处理类
普通非并发处理,我并发处理方式的放后面了

package com.common.log;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;
import com.common.vo.LoginUser;
import com.modules.entity.sys.LogEntity;
import com.modules.service.sys.LogService;

import cn.hutool.core.util.StrUtil;

/**
 * AOP切面日志持久化处理
 * 
 * @author YYQ
 *
 */
@Component // 对象由spring管理
@Aspect // 切面注解
public class LogAspect {

	@Autowired
	private LogService logService;

	private final Logger logger = Logger.getLogger(LogAspect.class);


	/**
	 * 定义切入点,只要方法添加了LogData的都会进入日志处理
	 */
	@Pointcut("@annotation(com.common.log.LogData)") // LogData是切入指定包路径的注解
	public void pointcut() {
		System.out.println("切入点...");
	}

	/**
	 * 正常数据时,走这个方法
	 * 
	 * @param joinPoint
	 */
	@AfterReturning("pointcut()")
	public void afterReturning(JoinPoint joinPoint) {
		try {
			// 获取到方法签名
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			// 请求的controller层路径
			String targetName = joinPoint.getTarget().getClass().getName();
			// 获取到连接点方法对象
			Method method = signature.getMethod();
			// 获取方法上面特定的注解
			LogData annotation = method.getAnnotation(LogData.class);
			//logger.info("-------------获取到注解内容:logType=" + annotation.remarks() + ",description:" + annotation.module());
			// aop中获取request
			ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
					.getRequestAttributes();
			HttpServletRequest request = requestAttributes.getRequest();
			HttpSession session = request.getSession();
			// 获取操作人
			LoginUser user = (LoginUser) session.getAttribute("loginUser");
			// 获取请求数据 TODO(已使用线程内解析参数成json格式存储)
			// Map<String, String[]> parameterMap = request.getParameterMap();
			// 将对象转换成json字符串==>存储到请求数据字段中
			// jackSon json字符串操作
			// ObjectMapper objectMapper = new ObjectMapper();
			// String s = objectMapper.writeValueAsString(parameterMap);
			// logger.info("请求数据:"+s);
			LogEntity log = new LogEntity();
			log.setIp(getIP(request));
			log.setOid(user.getLastOid());
			log.setUserId(user.getId());
			log.setUserName(user.getName());
			log.setClassUrl(targetName);
			log.setMethod(method.getName());
			log.setModuleName(annotation.module());
			log.setModuleOperation(annotation.remarks());
			// log.setParam(s); // TODO(已使用线程内解析参数成json格式存储)
			log.setParam(getParam(request));
			log.setType(1);
			logService.save(log);
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error(ex.getMessage());
		}
	}

	/**
	 * 发生异常时,走这个方法
	 * 
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "pointcut()", throwing = "e")
	public void afterThrowing(JoinPoint joinPoint, Throwable e) {
		try {
			String exMsg = StrUtil.isNotEmpty(e.toString())?e.toString():(e.getCause() != null?e.getCause().toString():e.getMessage());
			if (exMsg != null) {
				// 获取到方法签名
				MethodSignature signature = (MethodSignature) joinPoint.getSignature();
				// 请求的controller层路径
				String targetName = joinPoint.getTarget().getClass().getName();
				// 获取到连接点方法对象
				Method method = signature.getMethod();
				// 获取方法上面特定的注解
				LogData annotation = method.getAnnotation(LogData.class);
				// logger.info("获取到注解内容:logType=" + annotation.remarks() + ",description:" +annotation.module());
				// aop中获取request
				ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes();
				HttpServletRequest request = requestAttributes.getRequest();
				HttpSession session = request.getSession();
				// 获取操作人
				LoginUser user = (LoginUser) session.getAttribute("loginUser");
				LogEntity log = new LogEntity();
				log.setIp(getIP(request));
				log.setOid(user.getLastOid());
				log.setUserId(user.getId());
				log.setUserName(user.getName());
				log.setClassUrl(targetName);
				log.setMethod(method.getName());
				log.setModuleName(annotation.module());
				log.setModuleOperation(annotation.remarks());
				log.setType(2);
				log.setParam(getParam(request));
				log.setRemarks(exMsg);
				logService.save(log);

			}
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error(ex.getMessage());
		}
	}
	/**
	 * 返回json参数
	 * 
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public String getParam(HttpServletRequest request) {
		JSONObject paramJson = new JSONObject();
		Map<String, String[]> map = request.getParameterMap();
		Set<Entry<String, String[]>> keSet = map.entrySet();
		for (Iterator<Entry<String, String[]>> itr = keSet.iterator(); itr.hasNext();) {
			Entry me = (Entry) itr.next();
			Object ok = me.getKey();
			Object ov = me.getValue();
			String[] value = new String[1];
			if (ov instanceof String[]) {
				value = (String[]) ov;
				paramJson.put(ok + "", value);
				for (int k = 0; k < value.length; k++) {
					paramJson.put(ok + "", value[k]);
				}
			} else {
				value[0] = ov.toString();
				paramJson.put(ok + "", ov.toString());
			}
		}
		// System.out.println(paramJson.toJSONString());
		return paramJson.toJSONString();
	}

	/**
	 * 描述:获取客户端ip
	 * 
	 * @param request
	 * @return
	 */
	public String getIP(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;
	}


}

到spring-mvc.xml文件中配置
这里的spring-mvc.xml文件是在web.xml中配置的
如:

<!-- 配置文件指向 -->
	<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

然后到spring-mvc.xml文件中

<!-- 扫描日志包 -->
    <context:component-scan base-package="com.common.log" />
	<!-- 开启AOP,cglib代理 (通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller) -->
	<!-- <aop:aspectj-autoproxy/> --><!-- 或使用这个 -->
	<aop:aspectj-autoproxy proxy-target-class="true"/>

使用 <aop:aspectj-autoproxy />配置,顶部需要引入aop
(这里把所有丢进来了,自己选)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"    
    xmlns:context="http://www.springframework.org/schema/context"    
    xmlns:mvc="http://www.springframework.org/schema/mvc"    
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans      
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd      
                        http://www.springframework.org/schema/context      
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd      
                        http://www.springframework.org/schema/mvc      
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

使用
在方法上加入@LogData

/**
	 *批量 删除
	 * @param request
	 * @return
	 */
	@LogData(module = "用户管理", remarks = "批量删除")
	@RequestMapping("/deleteBatch")
	@ResponseBody
	public Result<?> deleteBatch(String ids){
		return service.deleteBatch(ids);
	}

结果

日志系统适合用什么数据库_aop

以上配置完成即可实现!


LogAspect处理类并发实现
使用队列和线程池来实现
analysisThread 是为了自定义参数格式而写

package com.common.log;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;
import com.common.vo.LoginUser;
//import com.fasterxml.jackson.databind.ObjectMapper;
import com.modules.entity.sys.LogEntity;
import com.modules.service.sys.LogService;

import cn.hutool.core.util.StrUtil;

/**
 * AOP切面日志持久化处理
 * 
 * @author YYQ
 *
 */
@Component // 对象由spring管理
@Aspect // 切面注解
public class LogAspect {

	@Autowired
	private LogService logService;

	private final Logger logger = Logger.getLogger(LogAspect.class);
	// 队列
	private static BlockingQueue<LogEntity> queue = new LinkedBlockingQueue<LogEntity>();
	// 缓存线程池 (大小根据需求调整)
	private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
	// 任务数(大小根据需求调整)
	private static int taskSize = 3;
	// 正常-线程是否已启动
	boolean isStartThread = false;
	// 异常-线程是否已启动
	boolean exStartThread = false;
	// 用来启动或停止线程
	static boolean run = true;

	public static BlockingQueue<LogEntity> getQueue() {
		return queue;
	}

	public static void setQueue(BlockingQueue<LogEntity> queue) {
		LogAspect.queue = queue;
	}

	public static boolean isRun() {
		return run;
	}

	public static void setRun(boolean run) {
		LogAspect.run = run;
	}

	/**
	 * 定义切入点,只要方法添加了LogData的都会进入日志处理
	 */
	@Pointcut("@annotation(com.common.log.LogData)") // LogData是切入指定包路径的注解
	public void pointcut() {
		System.out.println("切入点...");
	}

	/**
	 * 正常数据时,走这个方法
	 * 
	 * @param joinPoint
	 */
	@AfterReturning("pointcut()")
	public void afterReturning(JoinPoint joinPoint) {
		try {
			// 获取到方法签名
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			// 请求的controller层路径
			String targetName = joinPoint.getTarget().getClass().getName();
			// 获取到连接点方法对象
			Method method = signature.getMethod();
			// 获取方法上面特定的注解
			LogData annotation = method.getAnnotation(LogData.class);
			//logger.info("-------------获取到注解内容:logType=" + annotation.remarks() + ",description:" + annotation.module());
			// aop中获取request
			ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
					.getRequestAttributes();
			HttpServletRequest request = requestAttributes.getRequest();
			HttpSession session = request.getSession();
			// 获取操作人
			LoginUser user = (LoginUser) session.getAttribute("loginUser");
			// 获取请求数据 TODO(已使用线程内解析参数成json格式存储)
			// Map<String, String[]> parameterMap = request.getParameterMap();
			// 将对象转换成json字符串==>存储到请求数据字段中
			// jackSon json字符串操作
			// ObjectMapper objectMapper = new ObjectMapper();
			// String s = objectMapper.writeValueAsString(parameterMap);
			// logger.info("请求数据:"+s);
			LogEntity log = new LogEntity();
			log.setIp(getIP(request));
			log.setOid(user.getLastOid());
			log.setUserId(user.getId());
			log.setUserName(user.getName());
			log.setClassUrl(targetName);
			log.setMethod(method.getName());
			log.setModuleName(annotation.module());
			log.setModuleOperation(annotation.remarks());
			// log.setParam(s); // TODO(已使用线程内解析参数成json格式存储)
			log.setType(1);
			// logService.save(log);
			// 放入队列
			// queue.put(log);
			new analysisThread(request, log).start();
			if (!isStartThread) {
				for (int i = 0; i < taskSize; i++) {// 初始化任务线程总数
					threadPool.execute(new saveLogThread());
				}
				isStartThread = true;
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error(ex.getMessage());
		}
	}

	/**
	 * 发生异常时,走这个方法
	 * 
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "pointcut()", throwing = "e")
	public void afterThrowing(JoinPoint joinPoint, Throwable e) {
		try {
			String exMsg = StrUtil.isNotEmpty(e.toString())?e.toString():(e.getCause() != null?e.getCause().toString():e.getMessage());
			if (exMsg != null) {
				// 获取到方法签名
				MethodSignature signature = (MethodSignature) joinPoint.getSignature();
				// 请求的controller层路径
				String targetName = joinPoint.getTarget().getClass().getName();
				// 获取到连接点方法对象
				Method method = signature.getMethod();
				// 获取方法上面特定的注解
				LogData annotation = method.getAnnotation(LogData.class);
				// logger.info("获取到注解内容:logType=" + annotation.remarks() + ",description:" +
				// annotation.module());
				// aop中获取request
				ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
						.getRequestAttributes();
				HttpServletRequest request = requestAttributes.getRequest();
				HttpSession session = request.getSession();
				// 获取操作人
				LoginUser user = (LoginUser) session.getAttribute("loginUser");
				// logger.info("请求数据:"+s);
				LogEntity log = new LogEntity();
				log.setIp(getIP(request));
				log.setOid(user.getLastOid());
				log.setUserId(user.getId());
				log.setUserName(user.getName());
				log.setClassUrl(targetName);
				log.setMethod(method.getName());
				log.setModuleName(annotation.module());
				log.setModuleOperation(annotation.remarks());
				log.setType(2);
				log.setRemarks(exMsg);
				// logService.save(log);
				// 放入队列
				// queue.put(log);
				new analysisThread(request, log).start();
				if (!exStartThread) {
					new Thread(new saveLogThread()).start();// 异常数据不放入线程池 exStartThread = true;
				}

			}
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error(ex.getMessage());
		}
	}

	/**
	 * 描述:获取客户端ip
	 * 
	 * @param request
	 * @return
	 */
	public String getIP(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;
	}

	/**
	 * 解析参数-线程
	 *
	 */
	class analysisThread extends Thread {

		private HttpServletRequest request;
		private LogEntity log;

		public analysisThread(HttpServletRequest request, LogEntity log) {
			this.request = request;
			this.log = log;
		}

		@Override
		public void run() {
			try {
				log.setParam(getParam());
				queue.put(log);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		/**
		 * 返回json参数
		 * 
		 * @return
		 */
		@SuppressWarnings("rawtypes")
		public String getParam() {
			JSONObject paramJson = new JSONObject();
			Map<String, String[]> map = request.getParameterMap();
			Set<Entry<String, String[]>> keSet = map.entrySet();
			for (Iterator<Entry<String, String[]>> itr = keSet.iterator(); itr.hasNext();) {
				Entry me = (Entry) itr.next();
				Object ok = me.getKey();
				Object ov = me.getValue();
				String[] value = new String[1];
				if (ov instanceof String[]) {
					value = (String[]) ov;
					paramJson.put(ok + "", value);
					for (int k = 0; k < value.length; k++) {
						paramJson.put(ok + "", value[k]);
					}
				} else {
					value[0] = ov.toString();
					paramJson.put(ok + "", ov.toString());
				}
			}
			// System.out.println(paramJson.toJSONString());
			return paramJson.toJSONString();
		}
	}

	/**
	 * 
	 * 异步保存日志-线程
	 */
	class saveLogThread implements Runnable {
		Lock lock = new ReentrantLock();

		@Override
		public void run() {
			try {
				while (run) {// 使用死循环休眠3秒的方式重复利用线程
					while (queue.size() != 0) {
						// 如果对插入顺序无要求,此处不需要同步可提升效率
						lock.lock();
						LogEntity log = queue.take(); // 相关queue
						logService.save(log);
						lock.unlock();
					}
					Thread.sleep(3000);
				}
			} catch (InterruptedException e) {
				logger.error("saveLogThread被唤醒:" + e.toString());
			} catch (Exception e) {
				logger.error("saveLogThread异常:" + e.toString());
			}
		}
	}


}

监听application的生命周期,
在web.xml中配置,且写个实现类

<listener>
	<listener-class>com.common.listener.MyServletContextListener</listener-class>
</listener>

释放线程

package com.common.listener;


import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.log4j.Logger;

import com.common.log.LogAspect;

/**
 * application 生命周期监听
 * @author YYQ
 *
 */
public class MyServletContextListener implements ServletContextListener {

	private final Logger logger = Logger.getLogger(MyServletContextListener.class);

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		logger.info("===================> 系统已启动");
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		logger.info("===================> 系统已销毁");
		// 操作日志线程释放
		while(LogAspect.getQueue().size() == 0) {
			LogAspect.setRun(false);
			break;
		}
	}
	
	
}

【保存正常日志,如果只需要保存接口完成操作的纪录】
需要在改成如下:

/**
	 * 正常数据时,走这个方法
	 * 
	 * @param joinPoint
	 */
	@AfterReturning(value = "pointcut()", returning = "res")// returning 获取返回值
	public void afterReturning(JoinPoint joinPoint, Object res) {
		try {
			if(res instanceof Result) {// Result是接口返回的对象
				Result<?> r = (Result<?>) res;
				if(!r.getState() || r.getCode().equals(Result.FAIL_STATE) || r.getCode().equals(Result.FAIL_CODE)) {
					logger.info("-------------不保存接口校验失败信息");
					System.out.println(r.toString());
					return;
				}
			}
			logger.info("-------------日志保存");
			
		} catch (Exception ex) {
			ex.printStackTrace();
			logger.error(ex.getMessage());
		}
	}