springboot集成系统操作日志超详细

我使用的是springboot框架,写系统日志之前需要先引入pom依赖:

<!--aop用于插入操作日志 -->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

1.日志表 (我使用的时postgre,其他数据库需要修改脚本)

-- public.sys_log definition

-- Drop table

-- DROP TABLE sys_log;

CREATE TABLE sys_log (
	id varchar(38) NOT NULL,
	username varchar(30) NOT NULL,
	"method" varchar(255) NULL, -- 方法名
	params varchar(1000) NULL, -- 参数
	ip varchar(20) NULL,
	create_date timestamp(0) NULL DEFAULT now(),  --默认设置为now,插入日志时就不需要设置时间了
	"type" varchar(20) NULL, -- 类型 : 查询、新增等等
	model varchar(50) NULL, -- 模块
	"result" varchar(50) NULL, -- 操作结果
	description varchar(255) NULL,
	url varchar(400) NULL, -- 请求url
	CONSTRAINT log_pk PRIMARY KEY (id)
);

-- Column comments

COMMENT ON COLUMN public.sys_log."method" IS '方法名';
COMMENT ON COLUMN public.sys_log.params IS '参数';
COMMENT ON COLUMN public.sys_log."type" IS '类型 : 查询、新增等等';
COMMENT ON COLUMN public.sys_log.model IS '模块';
COMMENT ON COLUMN public.sys_log."result" IS '操作结果';
COMMENT ON COLUMN public.sys_log.url IS '请求url';

2. 日志实体



import java.util.Date;

import javax.persistence.*;

import lombok.Data;

@Data
@Table(name = "sys_log")
public class SysLog {
    /**
     * 权限角色ID
     */
    @Id
    private String id;

    @Column(name = "username")
    private String username; //用户名

    @Column(name = "method")
    private String method; //方法名

    @Column(name = "params")
    private String params; //参数

    @Column(name = "ip")
    private String ip; //ip地址
    
    @Column(name = "url")
    private String url; //请求url
    
    @Column(name = "type")
    private String type; //操作类型 :新增、删除等等
    
    @Column(name = "model")
    private String model; //模块

    @Column(name = "create_date")
    private Date createDate; //操作时间
    
    @Column(name = "result")
    private String result; //操作结果
    
    @Column(name = "description")
    private String description;//描述
    
}

3. dao (继承了tk.mapper中的Mymapper,所以没有写插入日志方法),因没有业务相关代码,所以没有写service以及的impl

import org.springframework.stereotype.Repository;

import com.album.manager.pojo.SysLog;

import tk.mapper.MyMapper;

@Repository
public interface LogMapper extends MyMapper<SysLog>{
}

4. 自定义注解类(用于拦截操作方法并插入日志)

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author hewenjun
 * @title: OperationLog
 * @date 2021/9/23
 * @description: 自定义操作日志注解
 */
@Target(ElementType.METHOD)//注解放置的目标位置即方法级别
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface SysLogAnnotation {
    String operModul() default ""; // 操作模块

    String operType() default "";  // 操作类型

    String operDesc() default "";  // 操作说明
}

5. AOP拦截插入日志类(返回值使用的是Map接收):

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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import com.album.manager.common.utils.IPUtils;
import com.album.manager.common.utils.SysLogAnnotation;
import com.album.manager.dao.LogMapper;
import com.album.manager.pojo.SysLog;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import cn.hutool.core.lang.id.NanoId;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author hewenjun
 * @title: OperationAspect
 * @date 2021/9/23
 * @description: 操作日志切面处理类
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
    @Autowired
    LogMapper logDao;

    /**
     * 设置操作日志切入点   在注解的位置切入代码
     */
    @Pointcut("@annotation(com.album.manager.common.utils.SysLogAnnotation)") 
    public void operLogPoinCut() {
    }

    /**
     * 记录操作日志
     * @param joinPoint 方法的执行点
     * @param result  方法返回值
     * @throws Throwable
     */
    @AfterReturning(returning = "result", value = "operLogPoinCut()")
    public void saveOperLog(JoinPoint joinPoint, Object result) throws Throwable {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            SysLog sysLog = new SysLog();
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            SysLogAnnotation annotation = method.getAnnotation(SysLogAnnotation.class);
            if (annotation != null) {
                sysLog.setModel(annotation.operModul());
                sysLog.setType(annotation.operType());
                sysLog.setDescription(annotation.operDesc());
            }
            
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            sysLog.setMethod(methodName); // 类名.请求方法
            
            sysLog.setCreateDate(new Date()); //操作时间
            //操作用户 --登录时有把用户的信息保存在session中,可以直接取出
            String user = (String)request.getSession().getAttribute("user");
            sysLog.setUsername(user);
            
            sysLog.setIp(IPUtils.getIpAddr(request)); //操作IP IPUtils工具类网上大把的,比如工具类集锦的hutool.jar
            sysLog.setUrl(request.getRequestURI()); // 请求URI
            
            // 方法请求的参数
            Map<String, String> rtnMap = converMap(request.getParameterMap());
            // 将参数所在的数组转换成json
            String params = JSON.toJSONString(rtnMap);
            //获取json的请求参数
            if (rtnMap == null || rtnMap.size() == 0) {
                params = getJsonStrByRequest(request);
            }
            sysLog.setParams(params); // 请求参数
            Map <String, Object> dataResult = (Map <String, Object>)result;  //返回值信息
            //需要先判断返回值是不是Map <String, Object>,如果不是會拋異常,需要控制层的接口返回数据格式统一
            //如果嫌返回格式统一太麻烦建议日志保存时去掉操作结果
        	sysLog.setResult(dataResult.get("msg").toString()); //獲取方法返回值中的msg,如果上面的類型錯誤就拿不到msg就會拋異常
          
            //保存日志
            sysLog.setId(NanoId.randomNanoId());
            logDao.insert(sysLog);

        } catch (Exception e) {
            e.printStackTrace();
            log.error("日誌記錄異常,請檢查返回值是否是Map <String, Object>類型");
        }
    }
    
    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }
    
    /**
     * 获取json格式 请求参数
     */
    public String getJsonStrByRequest(HttpServletRequest request) {
        String param = null;
        try {
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
            StringBuilder responseStrBuilder = new StringBuilder();
            String inputStr;
            while ((inputStr = streamReader.readLine()) != null) {
                responseStrBuilder.append(inputStr);
            }

            JSONObject jsonObject = JSONObject.parseObject(responseStrBuilder.toString());
            param = jsonObject.toJSONString();
            System.out.println(param);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return param;
    }
    

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }

}

5.1 AOP拦截插入日志类(返回值统一使用ReturnT.java --20230720更新版):

package com.epson.Aspect;

import com.alibaba.druid.support.json.JSONUtils;
import com.epson.entity.ReturnT;
import com.epson.entity.SysLog;
import com.epson.mapper.LogMapper;
import com.epson.service.LogService;
import com.epson.utils.IPUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


import cn.hutool.core.lang.id.NanoId;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author hewenjun
 * @title: OperationAspect
 * @date 2021/9/23
 * @description: 操作日志切面处理类
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
    @Autowired
    LogService logService;

    /**
     * 设置操作日志切入点   在注解的位置切入代码
     */
    @Pointcut("@annotation(com.epson.Aspect.SysLogAnnotation)")
    public void operLogPoinCut() {
    }

    /**
     * 记录操作日志
     * @param joinPoint 方法的执行点
     * @param result  方法返回值
     */
    @AfterReturning(returning = "result", value = "operLogPoinCut()")
    public void saveOperLog(JoinPoint joinPoint, ReturnT result) { //必须统一返回值,否则拿不到返回的数据
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        try {
            SysLog sysLog = new SysLog();
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            SysLogAnnotation annotation = method.getAnnotation(SysLogAnnotation.class);
            if (annotation != null) {
                sysLog.setModel(annotation.operModul());
                sysLog.setType(annotation.operType());
                sysLog.setDescription(annotation.operDesc());
            }

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            sysLog.setMethod(methodName); // 类名.请求方法

            //操作用户 --登录时有把用户的信息保存在session中,可以直接取出
            String loginUserName = (String)request.getSession().getAttribute("loginUserNo");
            String loginUserNo = (String)request.getSession().getAttribute("loginUserNo");
            sysLog.setUsername(loginUserName+ "(" + loginUserNo + ")");

            sysLog.setIp(IPUtils.getIpAddr(request)); //操作IP IPUtils工具类网上大把的,比如工具类集锦的hutool.jar
            sysLog.setUrl(request.getRequestURI()); // 请求URI

            // 方法请求的参数
            Map<String, String> rtnMap = converMap(request.getParameterMap());
            // 将参数所在的数组转换成json
            String params = JSONUtils.toJSONString(rtnMap);
            //获取json的请求参数
            if (rtnMap == null || rtnMap.size() == 0) {
                params = getJsonStrByRequest(request);
            }
            sysLog.setParams(params); // 请求参数
            String updateDataCount = annotation.operType()+"了" + (null!= result.getCount()?result.getCount():0)+"条数据";
            sysLog.setResult(result.getMsg()+"("+updateDataCount+")"); //獲取方法返回值中的msg,如果上面的類型錯誤就拿不到msg就會拋異常

            //保存日志
            sysLog.setId(NanoId.randomNanoId());
            logService.insert(sysLog);

        } catch (Exception e) {
            e.printStackTrace();
            log.error("日誌記錄異常,請檢查返回值是否是Map <String, Object>類型");
        }
    }

    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 获取json格式 请求参数
     */
    public String getJsonStrByRequest(HttpServletRequest request) {
        String param = null;
        try {
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
            StringBuilder responseStrBuilder = new StringBuilder();
            String inputStr;
            while ((inputStr = streamReader.readLine()) != null) {
                responseStrBuilder.append(inputStr);
            }

            param = JSONUtils.toJSONString(responseStrBuilder);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return param;
    }


    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder strbuff = new StringBuilder();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet).append("\n");
        }
        return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
    }

}

6. Controller层接口(也是操作被拦截的地方):

 @RequestMapping(value = "/setUser", method = RequestMethod.POST)
 @ResponseBody
 @SysLogAnnotation(operModul = "系统管理>>用户管理", operType = "新增", operDesc = "新增用户") //operType尽量统一为 查询/新增/更新/删除 ,这样返回值就可以拼接得到更完善的结果
    public Map<String,Object> setUser(BaseAdminUser user) {
        Map<String,Object> data = new HashMap();
        if(user.getId().isBlank()){
            data = adminUserService.addUser(user);
        }
        return data;
    }

7. 特别特别特别需要注意,如果系统日志中需要插入操作结果,那么就一定需要统一返回格式,比如上面代码中返回结果 Map<String,Object> data = new HashMap(),service代码:

@Transactional(rollbackFor = Exception.class)
    public Map<String,Object> addUser(BaseAdminUser user) {
        Map<String,Object> data = new HashMap();
        try {
            BaseAdminUser old = baseAdminUserMapper.getUserByUserName(user.getSysUserName(),null);
            if(old != null){
                data.put("code",0);
                data.put("msg","用户名已存在!");
                log.error("用户[新增],结果=用户名已存在!");
                return data;
            }
            String phone = user.getUserPhone();
            if(phone.length() != 11){
                data.put("code",0);
                data.put("msg","手机号位数不对!");
                log.error("设置用户[新增或更新],结果=手机号位数不对!");
                return data;
            }
            String username = user.getSysUserName();
            if(user.getSysUserPwd() == null){
                String password = DigestUtils.Md5(username,"123456");
                user.setSysUserPwd(password);
            }else{
                String password = DigestUtils.Md5(username,user.getSysUserPwd());
                user.setSysUserPwd(password);
            }
            user.setRegTime(DateUtils.getCurrentDate());
            user.setUserStatus(1);
            user.setId(UUID.randomUUID().toString());
            int result = baseAdminUserMapper.insert(user);
            if(result == 0){
                data.put("code",0);
                data.put("msg","新增失败!");
                log.error("用户[新增],结果=新增失败!");
                return data;
            }
        	data.put("code",1);
        	data.put("msg","新增成功!");
        	log.info("用户[新增],结果=新增成功!");
        } catch (Exception e) {
            e.printStackTrace();
            data.put("code",0);
            data.put("msg","新增异常失败!");
            log.error("用户[新增]异常!", e);
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//捕捉的异常需要手动回滚
        }
        return data;
    }

上面service代码中返回的map格式定义有没有code都可以,视页面处理而定,如果系统操作日志需要保存结果,那么这个msg就必须要有,步骤5中的 AOP拦截插入日志类中获取操作结果时获取的就是map中的msg

分享个工具类jar 的依赖(官网:Hutool参考文档 , API文档:hutool-码云(gitee.com)):

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.19</version>
</dependency>

好的,我可以为您提供一些关于Spring Boot整合Log4j2的指导。 首先,您需要在Spring Boot项目的pom.xml文件中添加Log4j2相关依赖。以下是一个示例: ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> </dependencies> ``` 接下来,您需要在项目中创建log4j2.xml文件。以下是一个示例: ```xml <?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="console"/> </Root> </Loggers> </Configuration> ``` 在这个示例中,我们定义了一个名为“console”的Appender,用于将日志输出到控制台。我们还定义了一个名为“Root”的Logger,用于将所有日志消息发送到“console”Appender,并将日志级别设置为“info”。 最后,在项目中使用Log4j2日志记录。您可以使用Spring的内置日志记录注解@Log4j2来注释您的类。以下是一个示例: ```java import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller @Log4j2 public class HomeController { @GetMapping("/") public String home() { log.info("Home page accessed"); return "home"; } } ``` 这个示例中,我们使用了Lombok提供的@Log4j2注解来注释HomeController类。然后,我们在home()方法中使用log.info()方法来记录日志消息。 希望这些指导对您有所帮助!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

往事不堪回首..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值