目标:
每个系统都会有相应的日志,那么在这里就和大家分享一下,我做过的项目里,操作日志都是怎么记录入库的
首先我们记录的操作日志都是需要落库保存的,所以需要数据库表的支持。我们的实现方式是通过在方法上面加上我们自己写的注解,然后通过切面去获取相应的操作,在进行操作日志落库的。
分享一下数据设计:
create table operate_log
(
id bigint not null auto_increment comment '主键id(自增) ',
operator_code varchar(64) not null default '' comment '操作人id',
operator varchar(50) not null default '' comment '操作人',
uri varchar(500) not null default '' comment '请求路径',
operate_ip varchar(200) not null default '' comment '操作人ip',
operate_type varchar(50) not null default '' comment '操作类型',
operate_desc varchar(200) not null default '' comment '操作描述',
request text not null default '' comment '请求',
system varchar(12) not null default '' comment '系统',
operate_time datetime not null default CURRENT_TIMESTAMP comment '操作时间',
primary key (id)
);
然后创建对应的实体类、业务层接口和实现类、持久化层的接口和xml文件,提供新增和查询的方法就可以了。
实现逻辑如下及代码:
第一步:需要自己写一个自定义注解,代码如下:
import com.chuxin.example.common.enums.OperationType;
import java.lang.annotation.*;
/**
* <p>Description: [自定义注解-系统操作日志 拦截Controller]</p>
* Created on: 11:38 2018/7/23
* @author: myp
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
/**
* 操作描述 业务名称description
* @return
*/
String description() default "";
/**
* 日志类型(0:操作日志 1:监控日志)
* @return
*/
int logType() default 0;
/**
* 操作类型 create modify delete
* @return
*/
OperationType operationType();
}
OperationType枚举类
package com.chuxin.example.common.enums;
/**
* <p>Description: [操作日志类型]</p>
* Created on 2018年11月05日
* @author <a href="mailto: ***@163.com">myp</a>
* @version 1.0
*/
public enum OperationType {
INSERT,UPDATE,QUERY,DELETE
}
第二步:编写切入类
package com.xiaojukeji.fund.service.aspect;
import com.alibaba.fastjson.JSON;
import com.chuxin.example.common.annotation.SystemControllerLog;
import com.chuxin.example.common.util.IpAddressUtil;
import com.chuxin.example.dao.model.OperateLog;
import com.chuxin.example.dao.model.User;
import com.chuxin.example.service.OperateLogService;
import com.chuxin.example.service.UserService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Description: [切入类]</p>
* Created on: 12:37 2018/7/23
* @author: myp
*/
@Component //将该类加入ioc 容器
@Aspect // 指定该类为切面类
public class SystemLogAspect {
//注入Service用于把日志保存数据库
@Resource
private OperateLogService logService;
//注入Service用于获取当前登录用户信息
@Resource
private UserService userService;
@Value("${chuxin.example.serverName}")
private String serverName;
//本地异常日志记录对象
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
//Controller层切点
@Pointcut("@annotation(com.chuxin.example.common.annotation.SystemControllerLog)")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
//读取登录用户
User currentUser = userService.getCurrentUser();
//请求的IP
String ip = IpAddressUtil.getIpAddress(request);
try {
//获取信息
Map<String, Object> map = getControllerMethodDescription(joinPoint);
//*========数据库日志=========*//
OperateLog log = new OperateLog();
//域账号,直接从登陆信息中获取,现在暂时写死
log.setOperatorCode(currentUser.getEmplId());
log.setOperator(currentUser.getEmplName());
log.setUri(request.getRequestURL().toString());
log.setOperateIp(ip);
log.setOperateDesc(map.get("description").toString());
log.setOperateType(map.get("operationType").toString());
log.setRequest(getArgs(request, joinPoint));
log.setOperateTime(new Date());
log.setSystem(serverName);
logService.insertSelective(log);
} catch (Exception e) {
//记录本地异常日志
logger.error("存储用户操作日志异常,异常信息:{}", e.getMessage());
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
public static Map<String, Object> 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();
Map<String, Object> map = new HashMap<>();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
String description = method.getAnnotation(SystemControllerLog.class).description();
Integer logType = method.getAnnotation(SystemControllerLog.class).logType();
String operationType = method.getAnnotation(SystemControllerLog.class).operationType().toString();
map.put("logType", logType);
map.put("operationType", operationType);
map.put("description", description);
break;
}
}
}
return map;
}
/**
* Discription:[获取请求参数]
* @param request 请求对象
* @return String 请求参数
* Created on 2018/7/31
* @author: myp
*/
public static String getArgs(HttpServletRequest request, JoinPoint joinPoint){
Enumeration<String> parameterNames = request.getParameterNames();
Map<String,Object> requestParams = new HashMap<>();
while (parameterNames.hasMoreElements()){
String argName = parameterNames.nextElement();
String value = request.getParameter(argName);
requestParams.put(argName,value);
}
if (requestParams.size()<=0) {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (null == arg) {
continue;
}
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
continue;
}
String argName = arg.getClass().getName();
Object value = arg;
requestParams.put(argName,value);
}
}
return JSON.toJSONString(requestParams);
}
}
第三步:控制类方法上使用
// 在控制类方法上加该注解即可
@SystemControllerLog(description = "编辑***", operationType = OperationType.UPDATE)
通过以上操作就可以完成操作日志的记录了。
上述代码用到的工具类如下:
import javax.servlet.http.HttpServletRequest;
/**
* 自定义访问对象工具类
* <p>
* 获取对象的IP地址等信息
*
* @author myp
*/
public class IpAddressUtil {
/**未识别*/
public static final String UNKNOWN = "unknown";
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, 参考文章:
* http://developer.51cto.com/art/201111/305181.htm
* <p>
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
* 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
* <p>
* 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100
* <p>
* 用户真实IP为: 192.168.1.110
*
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
//获取ip
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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
网友评论