- 一、介绍
- 二、登录日志记录分析
- 2.1 异步定时任务管理器(线程池)
- 2.2 异步工厂(产生任务用)
- 三、用户操作行为记录分析
- 3.1 定义注解
- 3.2 定义切面
- 3.3 使用注解
- 四、源码
- SQL
系统访问记录表:
操作日志记录表:
无论登录成功还是失败,都会用一个异步的任务AsyncManager.me().execute()来保存登录结果:
通过SpringUtils工具类的getBean方法获得定时任务线程池对象,(也可以通过@Autowired注解注入)。
通过executor.schedule(Runnable command, long delay, TimeUnit unit);方法执行任务:
- TimerTask是实现了Runnable接口。
- OPERATE_DELAY_TIME 操作延迟10毫秒 。
- TimeUnit.MILLISECONDS 时间单位。
pom依赖:
eu.bitwalker UserAgentUtils 1.21
import com.lsh.common.enums.BusinessType;
import com.lsh.common.enums.OperatorType;
import java.lang.annotation.*;
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface Log {
public String title() default "";
public BusinessType businessType() default BusinessType.OTHER;
public OperatorType operatorType() default OperatorType.MANAGE;
public boolean isSaveRequestData() default true;
}
业务操作类型:
操作人类别:
定义切面,
package com.lsh.oauth2.aspectj;
import com.alibaba.fastjson.JSON;
import com.lsh.common.annotation.Log;
import com.lsh.common.enums.BusinessStatus;
import com.lsh.common.enums.HttpMethod;
import com.lsh.common.util.ServletUtils;
import com.lsh.common.util.StringUtils;
import com.lsh.common.util.ip.IpUtils;
import com.lsh.oauth2.entity.SysOperLog;
import com.lsh.oauth2.manager.AsyncManager;
import com.lsh.oauth2.manager.factory.AsyncFactory;
import com.lsh.oauth2.service.TokenService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@Autowired
TokenService tokenService;
// 配置织入点
@Pointcut("@annotation(com.lsh.common.annotation.Log)")
public void logPointCut() {
}
@AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
Log controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
// 获取当前的用户 获取请求头中的Authorization信息(JWT令牌) 解析令牌 获得tokenkey,通过key获取Redis中的用户信息
String loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
log.info("LogAspect:获取当前的用户:"+loginUser);
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
// 返回参数
operLog.setJsonResult(JSON.toJSONString(jsonResult));
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null) {
// operLog.setOperName(loginUser.getUsername());
operLog.setOperName(loginUser);
}
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
} catch (Exception exp) {
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog);
}
}
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} else {
Map, ?> paramsMap = (Map, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
private Log getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(Log.class);
}
return null;
}
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
if (!isFilterObject(paramsArray[i])) {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
}
}
return params.trim();
}
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Iterator iter = collection.iterator(); iter.hasNext();) {
return iter.next() instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
3.3 使用注解
四、源码
代码中涉及的工具类不在贴出,代码已经上传Git:https://gitee.com/L1692312138/spring-cloud-alibaba
CREATE TABLE `sys_logininfor` ( `info_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '访问ID', `user_name` varchar(50) DEFAULT '' COMMENT '用户账号', `ipaddr` varchar(128) DEFAULT '' COMMENT '登录IP地址', `login_location` varchar(255) DEFAULT '' COMMENT '登录地点', `browser` varchar(50) DEFAULT '' COMMENT '浏览器类型', `os` varchar(50) DEFAULT '' COMMENT '操作系统', `status` char(1) DEFAULT '0' COMMENT '登录状态(0成功 1失败)', `msg` varchar(255) DEFAULT '' COMMENT '提示消息', `login_time` datetime DEFAULT NULL COMMENT '访问时间', PRIMARY KEY (`info_id`) ) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8 COMMENT='系统访问记录'; CREATE TABLE `sys_oper_log` ( `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', `title` varchar(50) DEFAULT '' COMMENT '模块标题', `business_type` int(2) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)', `method` varchar(100) DEFAULT '' COMMENT '方法名称', `request_method` varchar(10) DEFAULT '' COMMENT '请求方式', `operator_type` int(1) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)', `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员', `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称', `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL', `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址', `oper_location` varchar(255) DEFAULT '' COMMENT '操作地点', `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数', `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数', `status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)', `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息', `oper_time` datetime DEFAULT NULL COMMENT '操作时间', PRIMARY KEY (`oper_id`) ) ENGINE=InnoDB AUTO_INCREMENT=139 DEFAULT CHARSET=utf8 COMMENT='操作日志记录';



