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);
}
结果
以上配置完成即可实现!
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());
}
}