1. 操作日志表postgresql脚本:
CREATE TABLE public.sys_log (
id varchar(32) NOT NULL,
username varchar(20) NULL,
operation varchar(20) NULL,
"method" varchar(100) NULL,
params text NULL,
ip varchar(20) NULL,
createdate timestamp NULL
);
--添加两索引,提升查询速度
CREATE INDEX sys_log_createdate_idx ON public.sys_log USING btree (createdate);
CREATE INDEX sys_log_operation_idx ON public.sys_log USING btree (operation);
2. 集成mybatisplus等依赖
springsource-repos
SpringSource Repository
http://repo.spring.io/release/
com.alibaba
fastjson
2.0.5
org.postgresql
postgresql
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-aop
com.baomidou
mybatis-plus-boot-starter
3.5.1
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
org.apache.logging.log4j
log4j-core
org.apache.logging.log4j
log4j-web
3. 添加mybatisplus分页配置类 ---必须添加,否则无法使用mybatisplus自带分页
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
return interceptor;
}
}
4. 实体类 SysLog
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
//SysLog 对应的表名就是 sys_log mybatisplus自动驼峰命名对应 , 如果表名是syslog那么类名就是Syslog
@Data
public class SysLog implements Serializable {
private Long id;
private String username; //用户名
private String operation; //操作
private String method; //方法名
private String params; //参数
private String ip; //ip地址
private Date createdate; //操作时间
//非数据库字段必须 添加注解@TableField(exist = false),因mybatisplus会给表字段自动映射
@TableField(exist = false)
Integer pageNo;//第几页数据
@TableField(exist = false)
Integer size;//每页多少条数据
@TableField(exist = false)
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date createDateSt; //操作时间 -- 开始
@TableField(exist = false)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createDateEd; //操作时间 --结束
}
5. LogMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xxx.entity.SysLog;
import org.springframework.stereotype.Repository;
@Repository //表明这个类具有对对象进行CRUD(增删改查)的功能
public interface LogMapper extends BaseMapper {
}
6. LogService 以及LogServiceImpl
import com.baomidou.mybatisplus.extension.service.IService;
import com.xxx.entity.SysLog;
public interface LogService extends IService {
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxs.entity.SysLog;
import com.xxx.mapper.LogMapper;
import com.xxx.service.LogService;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImpl extends ServiceImpl implements LogService {
}
5.AOP面向切入(插入操作日志相关类): AnnotationResolver 、MyLog 、SysLogAspect
import java.lang.annotation.*;
/**
* 自定义注解类
*/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface MyLog {
String value() default "";
}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 获取注解的详细信息 注解的值
*/
public class AnnotationResolver {
private static AnnotationResolver resolver;
public static AnnotationResolver newInstance() {
if (resolver == null) {
return resolver = new AnnotationResolver();
} else {
return resolver;
}
}
/**
* 解析注解上的值
*
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) {
return null;
}
Object value = null;
// 如果name匹配上了#,则表示为map,使用map.get方法
if (str.startsWith("Map.")) {
String newStr = str.replaceAll("Map.", "").replaceAll("", "");
if(newStr.contains(".")){
try {
value = complexResolverMap(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
String newStr = str.replaceAll("", "");
// 复杂类型 调用对象.get属性()方法
if (newStr.contains(".")) {
try {
value = complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = simpleResolverPost(joinPoint, newStr);
}
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Object obj = args[i];
Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
Object value = dmethod.invoke(args[i]);
return getValue(value, 1, strs);
}
}
return null;
}
private Object complexResolverMap(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Map obj = (Map) args[i];
return obj.get(strs[1]);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strs) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strs);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
private Object simpleResolverPost(JoinPoint joinPoint, String str) {
return joinPoint.getArgs()[0];
}
}
注意: SysLogAspect 类中切入位置需要修改成自己注解类所在包路径 ,否则切入无效
import com.alibaba.fastjson2.JSON;
import com.xxx.entity.SessionBean;
import com.xxx.entity.SysLog;
import com.xxx.mapper.LogMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 系统日志:切面处理类
*/
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Autowired
private LogMapper dao;
//定义切点 @Pointcut
//在注解的位置切入代码 ---------------需要修改成自己注解类所在包路径
@Pointcut("@annotation(com.xxx.MyLog)")
public void logPoinCut() {
}
//切面 配置通知
@AfterReturning("logPoinCut()")
public void saveSysLog(JoinPoint joinPoint) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
HttpSession session = request.getSession();
//保存日志
SysLog sysLog = new SysLog();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
MyLog myLog = method.getAnnotation(MyLog.class);
if (myLog != null) {
String value = myLog.value();
sysLog.setOperation(value);//保存获取的操作
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
sysLog.setMethod(className + "." + methodName + "()");
//请求的参数
Map rtnMap = converMap(request.getParameterMap());
// 将参数所在的数组转换成json
String params = JSON.toJSONString(rtnMap);
sysLog.setParams(params);
sysLog.setCreatedate(new Date());
//-----------------------------用户名信息可根据实际存储进行修改-------------
//操作用户 --登录时有把用户的信息保存在session中,可以直接取出
SessionBean sessionBean = (SessionBean)session.getAttribute("sid");
if(sessionBean!=null){//如果为空 ,就是未登录
sysLog.setUsername(sessionBean.getUserName());
}
//----------------------------------------------------------------
//获取用户ip地址
sysLog.setIp(getIpAddr(request));
//调用service保存SysLog实体类到数据库
dao.insert(sysLog);
}
/**
* 转换request 请求参数
*
* @param paramMap request获取的参数数组
*/
public Map converMap(Map paramMap) {
Map rtnMap = new HashMap();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* 获取IP地址
*
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
log.error("IPUtils ERROR ", e);
}
return ip;
}
}
7. LogController
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xxx.entity.SysLog;
import com.xxx.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
public class LogController extends BasicController {
@Autowired
LogService logService;
@RequestMapping("toLog") //跳转操作日志页面
public String toLog(HttpServletResponse response) {
setHeaderSuccess(response);
return "Log";
}
@RequestMapping("logPager") //查询操作日志分页数据
public String logPager(SysLog log, HttpServletRequest request, HttpServletResponse response) {
Page page = new Page<>(log.getPageNo(), null!=log.getSize()?log.getSize():10); //分页参数, 每页log.getPageSize()条,第log.getPageNo()页
QueryWrapper wrapper = new QueryWrapper();//实体参数
if(StringUtils.isNotBlank(log.getOperation())){ //操作
wrapper.lambda().like(SysLog::getOperation, log.getOperation());
}
if(StringUtils.isNotBlank(log.getUsername())){ //用户名
wrapper.lambda().like(SysLog::getUsername, log.getUsername());
}
if(null != log.getCreateDateSt()){ //操作开始时间
wrapper.ge("createdate", log.getCreateDateSt());
}
if(null != log.getCreateDateEd()){//操作结束时间
wrapper.le("createdate", log.getCreateDateEd());
}
wrapper.orderByDesc("createdate");
IPage logs = logService.page(page,wrapper);
request.setAttribute("logs", logs);
setHeaderSuccess(response);
return "Log";
}
}
8. js
function toLog() {
var postData = {};
$.ajax({
type : "post",
url : "/toLog",
data : postData,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("requestType", "ajax");
},
success : function(data,textStatus,response) {
handleResponseData(data,response,null);
var createDateSt = getAfter2Date();
var createDateEd = getServerDate();
// alert(createDateSt + "- " + createDateEd);
$("#createDateSt").val(createDateSt);//初始化时间 页面加载默认设置当前日期的前两天
$("#createDateEd").val(createDateEd);//初始化时间 页面加载默认当天日期
}
});
}
function toPage(flag){
var operation = $("#operation").val();
var username = $("#username").val();
var createDateSt = $("#createDateSt").val();
var createDateEd = $("#createDateEd").val();
var tempCreateDateEd = createDateEd.length==10?createDateEd+' 23:59:59':'';
var size = $("#size").val();//每页显示条数
var postData = {"pageNo":flag, "size":size,
"operation":operation, "username":username,
"createDateSt":createDateSt, "createDateEd":tempCreateDateEd};
$.ajax({
type : "post",
url : "/logPager",
data : postData,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("requestType", "ajax");
},
success : function(data,textStatus,response) {
handleResponseData(data,response,null);
$("#operation").val(operation);
$("#username").val(username);
$("#createDateSt").val(createDateSt);
$("#createDateEd").val(createDateEd);
},
error : function(data) {
reLogin();
}
});
}
// 获取服务器端日期年月日
function getServerDate() {
var time, year, month, date;
time = new Date($.ajax({
type : "HEAD",
async : false,
cache : false
}).getResponseHeader("Date"));
year = time.getFullYear();// 年
// 小于10的数在前面加上0
month = (time.getMonth() + 1) < 10 ? ("0" + (time.getMonth() + 1)) : (time
.getMonth() + 1);// 月
date = time.getDate() < 10 ? ("0" + time.getDate()) : time.getDate();// 日
// 拼成自己想要的日期格式
time = year + "-" + month + "-" + date;
return time;
}
// 获取当前时间的前两天日期
function getAfter2Date() {
var time = new Date();
time.setTime(time.getTime()-24*60*60*1000*2);
// 小于10的数在前面加上0
var month = (time.getMonth() + 1) < 10 ? ("0" + (time.getMonth() + 1)) : (time.getMonth() + 1);// 月
var date = time.getDate() < 10 ? ("0" + time.getDate()) : time.getDate();// 日
return time.getFullYear()+ "-" + month + "-" + date;
}
9. freemarker (.ftlh文件,也可改成html只需修改页面参数)+ 分页
操作日志
-
查询
<#if logs??>
用户
操作
方法名
参数
IP
操作时间
<#list logs.records as log>
${(log.username)!}
${(log.operation)!}
${(log.method)!}
${(log.params)!}
${(log.ip)!}
${(log.createdate?string("yyyy-MM-dd HH:mm"))!}
#list>
#if>
10. 页面效果