使用spring-aop做系统日志
系统日志注解定义
package com.emmmya.harin.modules.sys.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
@Target({ PARAMETER, METHOD })
/**
* 日志注解类
*/
public @interface OperateLog {
/**
*日志内容
* @return
*/
String value() default "";
/**
* 日志类型
*
* @return ;1:登录日志;2:操作日志
*/
int logType() default 2;
/**
* 操作日志类型
*
* @return (1查询,2添加,3修改,4删除)
*/
int operateType() default 0;
/**
* 模块
* @return
*/
String module() default "";
}
系统日志实体类定义
package com.emmmya.harin.modules.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.emmmya.harin.base.BaseEntity;
import lombok.Data;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* @description:
* @author: laijieguan@sunwoda.com
* @createDate: 2020-12-11
* @version: 1.0
*/
@Data
@Entity
@TableName("sys_log")
@Table(name="sys_log")
@ToString
public class SysLog extends BaseEntity{
@Column(name="log_type")
private Integer logType; //登录日志,操作日志
@Column(name="log_content")
private String logContent; //日志内容
@Column(name="operate_type")
private Integer operateType; //操作类型 1-查询 2-添加 3-更新 4-删除 5-导入 6-导出
@Column(name="module")
private String module; //模块
@Column(name="user_id")
private Long userId; //请求用户ID
@Column(name="username")
private String username; //请求用户名
@Column(name="ip")
private String ip;
@Column(name="method")
private String method; //请求的java方法
@Column(name="request_type")
private String requestType; //请求方式
@Column(name="request_url")
private String requestUrl; //请求url
@Column(name="request_param")
private String requestParam;//请求参数
@Column(name="cost_time")
private Long CostTime; //耗时
@Column(name="response_code")
private Integer responseCode; //http响应码
@Column(name ="result")
private String result; //系统处理结果,只有返回json,并使用Result<T>封装结果时才会记录
}
常量定义
package com.emmmya.harin.common.constant;
/**
* @description: 日志表的常量
* @author: laijieguan@sunwoda.com
* @createDate: 2020-12-11
* @version: 1.0
*/
public interface LogConstant {
//登录日志
Integer LOG_TYPE_LOGIN = 1;
//操作日志
Integer LOG_TYPE_OPARETE = 2;
//1-查询 2-添加 3-更新 4-删除 5-导入 6-导出
Integer LOG_OPERATE_TYPE_QUERY = 1;
Integer LOG_OPERATE_TYPE_ADD = 2;
Integer LOG_OPERATE_TYPE_UPDATE = 3;
Integer LOG_OPERATE_TYPE_DELETE = 4;
Integer LOG_OPERATE_TYPE_IMPORT = 5;
Integer LOG_OPERATE_TYPE_EXPORT = 6;
}
切面类定义
package com.emmmya.harin.modules.sys.aspect;
import com.alibaba.fastjson.JSONObject;
import com.emmmya.harin.common.constant.LogConstant;
import com.emmmya.harin.common.utils.IpUtils;
import com.emmmya.harin.common.vo.Result;
import com.emmmya.harin.modules.sys.annotation.OperateLog;
import com.emmmya.harin.modules.sys.entity.SysLog;
import com.emmmya.harin.modules.sys.entity.User;
import com.emmmya.harin.modules.sys.service.SysLogService;
import com.emmmya.harin.modules.sys.utils.UserUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.multipart.MultipartFile;
import org.thymeleaf.util.ArrayUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 系统操作日志切面
* */
@Aspect
@Component
@Slf4j
public class OperateLogAspect {
//注入日志service层
@Resource
private SysLogService logService;
//注入request
@Autowired(required=false)
private HttpServletRequest request;
//注入response
@Autowired(required=false)
private HttpServletResponse response;
//注入用户工具
@Autowired
private UserUtils userUtils;
/**
* 使用@OperateLog注解方法则会做系统日志
* 定义切面
* */
@Pointcut("@annotation(com.emmmya.harin.modules.sys.annotation.OperateLog)")
public void cutOperate() {}
/**
* 环绕通知
* @param point
* @return
* @throws Throwable
*/
@Around(value="cutOperate()")
public Object around(ProceedingJoinPoint point) throws Throwable{
long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
saveSmsLog(point, time,result);
return result;
}
private void saveSmsLog(ProceedingJoinPoint joinPoint, long time, Object result) {
//1.获取执行的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//2.新建日志实体
SysLog sysLog = new SysLog();
//获取方法上的@OperateLog注解
OperateLog anSysLog = method.getAnnotation(OperateLog.class);
//记录注解上的参数
if(anSysLog != null){
//注解上的描述,操作日志内容
sysLog.setLogContent(anSysLog.value());
sysLog.setLogType(anSysLog.logType());
sysLog.setModule(anSysLog.module());
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
//记录类名、方法名
sysLog.setMethod(className + "." + methodName + "()");
//设置操作类型
if (sysLog.getLogType() == LogConstant.LOG_TYPE_OPARETE) {
sysLog.setOperateType(getOperateType(methodName, anSysLog.operateType()));
}
//请求的参数
Object[] args = joinPoint.getArgs();
Stream<?> stream = ArrayUtils.isEmpty(args) ? Stream.empty() : Arrays.asList(args).stream();
//过滤request和response和上传文件的参数MultipartFile,必须加入,不然报错
List<Object> logArgs = stream
.filter(arg -> (!(arg instanceof HttpServletRequest) &&
!(arg instanceof HttpServletResponse) &&
!(arg instanceof MultipartFile)))
.collect(Collectors.toList());
try{
//过滤后序列化无异常
String params = JSONObject.toJSONString(logArgs);
sysLog.setRequestParam(params);
}catch (Exception e){
e.printStackTrace();
}
//记录IP地址
sysLog.setIp(IpUtils.getIpAddr(request));
//记录访问的uri
sysLog.setRequestUrl(request.getRequestURI());
//记录请求方式
sysLog.setRequestType(request.getMethod());
//记录http相应吗
sysLog.setResponseCode(response.getStatus());
//记录系统自身的处理结果与信息
if(result!= null && result instanceof Result){
Result result2 = (Result)result;
sysLog.setResult("{\"success\":"+result2.isSuccess()+",\"message\":\""+result2.getMessage()+"\",\"code\":"+result2.getCode()+"}");
}
//获取登录用户信息,看你用的什么安全框架了,可以从安全上下文获取,不需要就去掉
User currentUser = userUtils.getCurrentUser();
if(currentUser != null){
//记录当前登录用户的用户名和ID
sysLog.setUsername(currentUser.getUsername());
sysLog.setUserId(currentUser.getId());
}
//记录耗时
sysLog.setCostTime(time);
//创建时间
sysLog.setCreateTime(new Date());
//保存系统日志
logService.save(sysLog);
}
/**
* 获取操作类型
* 如果没有传入操作类型,则根据方法名来判断操作类型
*/
private int getOperateType(String methodName,int operateType) {
if (operateType > 0) {
return operateType;
}
if (methodName.startsWith("list")) {
return LogConstant.LOG_OPERATE_TYPE_QUERY;
}
if (methodName.startsWith("add")) {
return LogConstant.LOG_OPERATE_TYPE_ADD;
}
if (methodName.startsWith("update")) {
return LogConstant.LOG_OPERATE_TYPE_UPDATE;
}
if (methodName.startsWith("delete")) {
return LogConstant.LOG_OPERATE_TYPE_DELETE;
}
if (methodName.startsWith("import")) {
return LogConstant.LOG_OPERATE_TYPE_IMPORT;
}
if (methodName.startsWith("export")) {
return LogConstant.LOG_OPERATE_TYPE_EXPORT;
}
return LogConstant.LOG_OPERATE_TYPE_QUERY;
}
}
使用例子
我这里用登录接口作为例子。主要看注解的使用就好了,是系统登录模块的用户登录。
/**
* 用户登录接口
* @return
*/
@RequestMapping (value = "/user/login")
@OperateLog(value="用户登录",logType= 1,module = "系统登录")
public Result<Object> login (HttpServletRequest request, HttpServletResponse response,@RequestBody User user) throws ServletException, IOException {
String loginToken = user.getLoginToken();
System.out.println("loginToken:"+loginToken);
if(StringUtils.isEmpty(loginToken)){
response.sendRedirect("/login");
return null;
}
String uuid = (String) redisManager.get(RedisConstant.LOGIN_TOKEN + loginToken);
if(!"true".equals(uuid)){
response.sendRedirect("/login");
//return ResultUtils.error(210,"登陆token无效,请返回登陆页面重新登陆!");
}
String username = user.getUsername();
String password = user.getPassword();
String token = null;
//使用shiro框架验证user
Subject subject = SecurityUtils.getSubject();
String redisKeyUser = (String)subject.getPrincipal();
if(!StringUtils.isEmpty(redisKeyUser)){
subject.logout();
//Boolean del = redisManager.del(RedisConstant.REDIS_USER_TOKEN + jwtConfig.getUsernameFromToken(token));
//修改项
Boolean del = redisManager.del( redisKeyUser );
}
//如果没有授权,则需要登录
if (!subject.isAuthenticated()) {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
username,
password
);
if(!StringUtils.isEmpty(user.getRememberMe())) {
usernamePasswordToken.setRememberMe(true);
}
//这个token应该在shiro登录时生成。
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
redisKeyUser = (String) subject.getPrincipal();
token = (String)redisManager.get(redisKeyUser);
Boolean del = redisManager.del(RedisConstant.LOGIN_TOKEN + loginToken);
if (del) {
log.info("redis消费了" + RedisConstant.LOGIN_TOKEN + loginToken);
}
user = userService.findByUsername(user.getUsername());
user.setLastLoginIp(IpUtils.getIpAddr(request));
user.setLastLoginTime(new Date());
userService.updateEnhance(user);
Session session = subject.getSession();
session.setAttribute("currentUser",user);
} catch (AuthenticationException e) {
e.printStackTrace();
return ResultUtils.error(210, e.getMessage());
} catch (AuthorizationException e) {
e.printStackTrace();
return ResultUtils.error(401, "没有权限");
}
}
return ResultUtils.data(token);
}
登陆后,在数据库的sys_log表里就会多一条数据拉。