自定义操作日志集成springboot

275 阅读4分钟

为了防止每次写代码到处拷代码的鱼唇行为,将操作日志相关的类写成spring-boot-stater形式进行引入。


首先为了将配置简化,需自定义配置文件进行注入,在这咱使用实现EnvironmentPostProcessor的方式进行自定义写入配置。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.Properties;

/**
 * 加载配置文件
 */
public class LogEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private final Properties properties = new Properties();

    /**
     * 存放properties文件名
     */
    private String[] profiles = {
            "Log.properties",
    };


    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        //遍历profiles,加载配置文件
        for (String profile : profiles) {
            Resource resource = new ClassPathResource(profile);
            environment.getPropertySources().addLast(loadProfiles(resource));
        }

    }

    //加载配置文件
    private PropertySource<?> loadProfiles(Resource resource) {
        if (!resource.exists()) {
            throw new IllegalArgumentException("file" + resource + "not exist");
        }
        System.out.println("--------------------------------加载");
        try {
            properties.load(resource.getInputStream());
            return new PropertiesPropertySource(resource.getFilename(), properties);
        } catch (IOException ex) {
            throw new IllegalStateException("load resource exception" + resource, ex);
        }
    }

}

resouces下的Log.properties

actable.table.auto=update
actable.model.pack=org.zlf.log.starter.entity
actable.database.type=mysql
#mybatis-plus.mapperLocations=classpath*:com/gitee/sunchenbin/mybatis/actable/mapping/*/*.xml

这样,当项目启动时,自定义配置就注入啦~

SysLog类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.gitee.sunchenbin.mybatis.actable.annotation.Column;
import com.gitee.sunchenbin.mybatis.actable.annotation.Table;
import com.gitee.sunchenbin.mybatis.actable.annotation.TableCharset;
import com.gitee.sunchenbin.mybatis.actable.annotation.TableEngine;
import com.gitee.sunchenbin.mybatis.actable.constants.MySqlCharsetConstant;
import com.gitee.sunchenbin.mybatis.actable.constants.MySqlEngineConstant;
import com.gitee.sunchenbin.mybatis.actable.constants.MySqlTypeConstant;


import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 
 * </p>
 *
 * @author zlf
 * @since 2022-01-12
 */
@Table(value = "sys_log",comment = "操作日志类")
@TableCharset(MySqlCharsetConstant.UTF8)
@TableEngine(MySqlEngineConstant.InnoDB)
public class SysLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 日志id
     */
    @TableId(value = "log_id", type = IdType.AUTO)
    @Column(name = "log_id",type = MySqlTypeConstant.BIGINT,comment = "日志id",isKey = true,isAutoIncrement = true)
    private Long logId;

    /**
     * 模块名称
     */
    @Column(name = "log_module",type = MySqlTypeConstant.VARCHAR,comment = "模块名称")
    private String logModule;

    /**
     * 日志内容
     */
    @Column(name = "log_content",type = MySqlTypeConstant.LONGTEXT,comment = "日志内容")
    private String logContent;

    /**
     * 日志描述
     */
    @Column(name = "log_description",type = MySqlTypeConstant.VARCHAR,comment = "日志描述")
    private String logDescription;

    /**
     * 操作用户ip
     */
    @Column(name = "log_operation_ip",type = MySqlTypeConstant.VARCHAR,comment = "操作用户ip")
    private String logOperationIp;

    /**
     * 操作用户名
     */
    @Column(name = "log_username",type = MySqlTypeConstant.VARCHAR,comment = "操作用户名")
    private String logUsername;

    /**
     * 操作时间
     */
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    @Column(name = "log_date",type = MySqlTypeConstant.DATETIME,comment = "操作时间")
    private Date logDate;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public Long getLogId() {
        return logId;
    }

    public void setLogId(Long logId) {
        this.logId = logId;
    }

    public String getLogModule() {
        return logModule;
    }

    public void setLogModule(String logModule) {
        this.logModule = logModule;
    }

    public String getLogContent() {
        return logContent;
    }

    public void setLogContent(String logContent) {
        this.logContent = logContent;
    }

    public String getLogDescription() {
        return logDescription;
    }

    public void setLogDescription(String logDescription) {
        this.logDescription = logDescription;
    }

    public String getLogOperationIp() {
        return logOperationIp;
    }

    public void setLogOperationIp(String logOperationIp) {
        this.logOperationIp = logOperationIp;
    }

    public String getLogUsername() {
        return logUsername;
    }

    public void setLogUsername(String logUsername) {
        this.logUsername = logUsername;
    }

    public Date getLogDate() {
        return logDate;
    }

    public void setLogDate(Date logDate) {
        this.logDate = logDate;
    }
}

其中诸如@Column、@Table等注解主要是在自动生成数据库表时使用的。

@Log

import java.lang.annotation.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: zlf
 * @Date: 2022/01/12/22:31
 * @Description:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**模块*/
    String module() default "";

    /**描述*/
    String description() default "";
}

LogAspect

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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zlf.log.starter.annotation.Log;
import org.zlf.log.starter.service.SysLogService;

import java.lang.reflect.Method;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: zlf
 * @Date: 2022/01/12/22:32
 * @Description:
 */
@Aspect
@Component
public class LogAspect {

    @Autowired
    SysLogService sysLogService;

    @Pointcut("@annotation(org.zlf.log.starter.annotation.Log)")
    public void logPointCut(){}

    @AfterReturning(pointcut = "logPointCut()")
    public void doAfter(JoinPoint joinPoint){
        /**
         * 解析Log注解
         */
        String methodName = joinPoint.getSignature().getName();
        Method method = currentMethod(joinPoint,methodName);
        Log log = method.getAnnotation(Log.class);
        sysLogService.put(joinPoint,methodName,log.module(),log.description());
    }

    /**
     * 获取当前执行的方法
     *
     * @param joinPoint  连接点
     * @param methodName 方法名称
     * @return 方法
     */
    private Method currentMethod(JoinPoint joinPoint, String methodName) {
        /**
         * 获取目标类的所有方法,找到当前要执行的方法
         */
        Method[] methods = joinPoint.getTarget().getClass().getMethods();
        Method resultMethod = null;
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                resultMethod = method;
                break;
            }
        }
        return resultMethod;
    }
}

SysLogServiceImpl

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.zlf.log.starter.entity.SysLog;
import org.zlf.log.starter.mapper.SysLogMapper;
import org.zlf.log.starter.service.SysLogService;
import org.zlf.log.starter.utils.IpUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author zlf
 * @since 2022-01-12
 */
@Service
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {

    private static final String LOG_CONTENT = "[类名]:%s,[方法]:%s,[参数]:%s,[IP]:%s";

    @Override
    public void put(JoinPoint joinPoint, String methodName, String module, String description) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String username = userDetails.getUsername();
            if (StringUtils.isEmpty(username)) {
                username = "未知用户";
            }
            String ip = IpUtils.getIpAddr(request);
            SysLog sysLog = new SysLog();
            sysLog.setLogUsername(username);
            sysLog.setLogModule(module);
            sysLog.setLogContent(operateContent(joinPoint, methodName, ip, request));
            sysLog.setLogDate(new Date());
            sysLog.setLogDescription(description);
            sysLog.setLogOperationIp(ip);
            baseMapper.insert(sysLog);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        }
    }

    public String operateContent(JoinPoint joinPoint, String methodName, String ip, HttpServletRequest request) throws ClassNotFoundException, NotFoundException {
        String className = joinPoint.getTarget().getClass().getName();
        Object[] params = joinPoint.getArgs();
        String classType = joinPoint.getTarget().getClass().getName();
        Class<?> clazz = Class.forName(classType);
        String clazzName = clazz.getName();
        Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, methodName, params);
        StringBuffer bf = new StringBuffer();
        if (!CollectionUtils.isEmpty(nameAndArgs)) {
            Iterator it = nameAndArgs.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                String key = (String) entry.getKey();
                String value = JSONObject.toJSONString(entry.getValue());
                bf.append(key).append("=");
                bf.append(value).append("&");
            }
        }
        if (StringUtils.isEmpty(bf.toString())) {
            bf.append(request.getQueryString());
        }
        return String.format(LOG_CONTENT, className, methodName, bf.toString(), ip);
    }

    private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
        Map<String, Object> map = new HashMap<String, Object>();

        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
            return map;
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            map.put(attr.variableName(i + pos), args[i]);//paramNames即参数名
        }
        return map;
    }
}

注入Spring

以上都是正常的贴代码,大家会发现这些类都使用了@Component等这些注解 这些注解在咱第三方组件中并不会注入到Spring容器中! 所以需要进行注解扫描。

LogAutoConfig

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
// 扫描 mybatis.actable
@ComponentScan(basePackages = {"org.zlf.log.starter","com.gitee.sunchenbin.mybatis.actable.manager.*"})
// 配置mybatis扫描actable的mapper以及本模块的mapper
@MapperScan(basePackages = {"com.gitee.sunchenbin.mybatis.actable.dao.*","org.zlf.log.starter.mapper"} )
public class LogAutoConfig {

}

咱需要将LogAutoConfig加入到spring中,所以我们需要使用Spring.facrories .

Spring.facrories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.zlf.log.starter.config.LogAutoConfig
# 加载自定义配置文件
org.springframework.boot.env.EnvironmentPostProcessor=\
    org.zlf.log.starter.config.LogEnvironmentPostProcessor

这样在引入在springboot项目中时,spring就会扫描到这些类并管理啦~

链接

github

acTable