spring-aop做系统日志

本文介绍了如何使用Spring AOP为系统日志添加自定义注解,包括`OperateLog`注解的定义,`SysLog`实体类的配置,以及`OperateLogAspect`切面类的实现,以记录登录日志和操作日志,展示了在用户登录接口中的具体应用。
摘要由CSDN通过智能技术生成

使用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表里就会多一条数据拉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值