SpringBoot+AOP实现记录操作日志和异常日志,并保存到数据库

1. 简介

 

  项目中对日志的收集往往是非常重要的,不仅方便开发人员快速定位问题,而且越来越多的客户需要查询用户行为日志、用户审计日志等。因此,在收集日志时,不仅要考虑功能实现,而且要考虑可靠性、稳定性和不耦合性。
  在每个操作和每个方法都加上日志处理肯定时不现实的,因此使用Spring提供的AOP原理就变得非常方便。定义好切面以及切点之后,可以非常方便的打印、收集或保存日志,不影响业务性能。

2. 初始化数据库

  创建数据库aop,并初始化表结构:

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `log_type` varchar(50) NOT NULL COMMENT '日志类型',
  `create_user_code` varchar(64) NOT NULL COMMENT '创建用户编码',
  `create_user_name` varchar(100) NOT NULL COMMENT '创建用户名称',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `request_uri` varchar(500) DEFAULT NULL COMMENT '请求URI',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `request_params` text COMMENT '请求参数',
  `request_ip` varchar(20) NOT NULL COMMENT '请求IP',
  `server_address` varchar(50) NOT NULL COMMENT '请求服务器地址',
  `is_exception` char(1) DEFAULT NULL COMMENT '是否异常',
  `exception_info` text COMMENT '异常信息',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `execute_time` int DEFAULT NULL COMMENT '执行时间',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `device_name` varchar(100) DEFAULT NULL COMMENT '操作系统',
  `browser_name` varchar(100) DEFAULT NULL COMMENT '浏览器名称',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_sys_log_lt` (`log_type`) USING BTREE,
  KEY `idx_sys_log_cub` (`create_user_code`) USING BTREE,
  KEY `idx_sys_log_ie` (`is_exception`) USING BTREE,
  KEY `idx_sys_log_cd` (`create_date`) USING BTREE
) COMMENT='系统日志表';

3. 示例代码

  • 创建项目
  • 修改pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.c3stones</groupId>
	<artifactId>spring-aop-log-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-aop-log-demo</name>
	<description>Spring Aop Log Demo</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.5.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
  • 创建配置文件application.yml
server:
   port: 8080
   
spring:
   datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/aop?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
      username: root
      password: 123456
      
# Mybatis-plus配置
mybatis-plus:
   mapper-locations: classpath:mapper/*.xml
   global-config:
      db-config:
         id-type: AUTO
#   configuration:
#      # 打印sql
#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 创建公共参数类
/**
 * 公共常量类
 * 
 * @author CL
 */
public interface Global {

	/**
	 * 成功标识
	 */
	Boolean TRUE = true;

	/**
	 * 失败标识
	 */
	Boolean FALSE = false;

	/**
	 * 是标识
	 */
	String YES = "1";

	/**
	 * 否标识
	 */
	String NO = "0";

	/**
	 * 日志级别-INFO
	 */
	String LOG_INGO = "INFO";

	/**
	 * 日志级别-DEBUG
	 */
	String LOG_DEBUG = "DEBUG";

	/**
	 * 日志级别-ERROR
	 */
	String LOG_ERROR = "ERROR";

}
  • 创建工具类
import java.io.Serializable;

import com.c3stones.constants.Global;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * 响应工具类
 *
 * @param <T>
 * @author CL
 */
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R<T> implements Serializable {

	private static final long serialVersionUID = 1L;

	@Getter
	@Setter
	private Boolean code;

	@Getter
	@Setter
	private String msg;

	@Getter
	@Setter
	private T data;

	public static <T> R<T> ok() {
		return restResult(null, Global.TRUE, null);
	}

	public static <T> R<T> ok(T data) {
		return restResult(data, Global.TRUE, null);
	}

	public static <T> R<T> ok(T data, String msg) {
		return restResult(data, Global.TRUE, msg);
	}

	public static <T> R<T> failed() {
		return restResult(null, Global.FALSE, null);
	}

	public static <T> R<T> failed(String msg) {
		return restResult(null, Global.FALSE, msg);
	}

	public static <T> R<T> failed(T data) {
		return restResult(data, Global.FALSE, null);
	}

	public static <T> R<T> failed(T data, String msg) {
		return restResult(data, Global.FALSE, msg);
	}

	private static <T> R<T> restResult(T data, Boolean code, String msg) {
		R<T> apiResult = new R<>();
		apiResult.setCode(code);
		apiResult.setData(data);
		apiResult.setMsg(msg);
		return apiResult;
	}

}
/**
 * 字节转换工具类
 * 
 * @author CL
 */
public class ByteUtils {

	private static final int UNIT = 1024;

	/**
	 * 格式化字节大小
	 * 
	 * @param byteSize 字节大小
	 * @return
	 */
	public static String formatByteSize(long byteSize) {

		if (byteSize <= -1) {
			return String.valueOf(byteSize);
		}

		double size = 1.0 * byteSize;

		String type = "B";
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1KB
			type = "B";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1MB
			type = "KB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1GB
			type = "MB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1TB
			type = "GB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) { // 不足1PB
			type = "TB";
			return format(size, type);
		}

		size = size / UNIT;
		if ((int) Math.floor(size / UNIT) <= 0) {
			type = "PB";
			return format(size, type);
		}
		return ">PB";
	}

	/**
	 * 格式化字节大小为指定单位
	 * 
	 * @param size 字节大小
	 * @param type 单位类型
	 * @return
	 */
	private static String format(double size, String type) {
		int precision = 0;

		if (size * 1000 % 10 > 0) {
			precision = 3;
		} else if (size * 100 % 10 > 0) {
			precision = 2;
		} else if (size * 10 % 10 > 0) {
			precision = 1;
		} else {
			precision = 0;
		}

		String formatStr = "%." + precision + "f";

		if ("KB".equals(type)) {
			return String.format(formatStr, (size)) + "KB";
		} else if ("MB".equals(type)) {
			return String.format(formatStr, (size)) + "MB";
		} else if ("GB".equals(type)) {
			return String.format(formatStr, (size)) + "GB";
		} else if ("TB".equals(type)) {
			return String.format(formatStr, (size)) + "TB";
		} else if ("PB".equals(type)) {
			return String.format(formatStr, (size)) + "PB";
		}
		return String.format(formatStr, (size)) + "B";
	}

}
  • 创建线程池配置类  
    ​
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    /**
     * 线程池配置类
     * 
     * @author CL
     *
     */
    @Configuration
    public class ThreadPoolTaskExecutorConfig {
    
    	@Bean
    	public Executor customThreadPoolTaskExecutor() {
    		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    		// Java虚拟机可用的处理器数
    		int corePoolSize = Runtime.getRuntime().availableProcessors();
    		// 配置核心线程数
    		executor.setCorePoolSize(corePoolSize);
    		// 配置最大线程数
    		executor.setMaxPoolSize(corePoolSize * 2 + 1);
    		// 配置队列大小
    		executor.setQueueCapacity(100);
    		// 空闲的多余线程最大存活时间
    		executor.setKeepAliveSeconds(3);
    		// 配置线程池中的线程的名称前缀
    		executor.setThreadNamePrefix("thread-execute-");
    		// 当线程池达到最大大小时,在调用者的线程中执行任务
    		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    		// 执行初始化
    		executor.initialize();
    		return executor;
    	}
    }
    
    ​
  • 创建实体类
import java.io.Serializable;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import lombok.Data;

/**
 * 系统用户
 * 
 * @author CL
 *
 */
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 用户ID
	 */
	@TableId
	private Long id;

	/**
	 * 日志类型
	 */
	private String logType;

	/**
	 * 创建用户编码
	 */
	private String createUserCode;

	/**
	 * 创建用户名称
	 */
	private String createUserName;

	/**
	 * 创建时间
	 */
	private LocalDateTime createDate;

	/**
	 * 请求URI
	 */
	private String requestUri;

	/**
	 * 请求方式
	 */
	private String requestMethod;

	/**
	 * 请求参数
	 */
	private String requestParams;

	/**
	 * 请求IP
	 */
	private String requestIp;

	/**
	 * 请求服务器地址
	 */
	private String serverAddress;

	/**
	 * 是否异常
	 */
	private String isException;

	/**
	 * 异常信息
	 */
	private String exceptionInfo;

	/**
	 * 开始时间
	 */
	private LocalDateTime startTime;

	/**
	 * 结束时间
	 */
	private LocalDateTime endTime;

	/**
	 * 执行时间
	 */
	private Long executeTime;

	/**
	 * 用户代理
	 */
	private String userAgent;

	/**
	 * 操作系统
	 */
	private String deviceName;

	/**
	 * 浏览器名称
	 */
	private String browserName;

}
  • 创建Mapper
import org.apache.ibatis.annotations.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.c3stones.entity.SysLog;

/**
 * 系统日志Dao
 * 
 * @author CL
 *
 */
@Mapper
public interface SysLogDao extends BaseMapper<SysLog> {

}
  • 创建Service
import com.baomidou.mybatisplus.extension.service.IService;
import com.c3stones.entity.SysLog;

/**
 * 系统日志Service
 * 
 * @author CL
 *
 */
public interface SysLogService extends IService<SysLog> {

}
  • 创建Service实现
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.c3stones.dao.SysLogDao;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;

/**
 * 系统日志Service实现
 * 
 * @author CL
 *
 */
@Service
public class SysLogServiceImpl extends ServiceImpl<SysLogDao, SysLog> implements SysLogService {

}
  • 创建日志访问Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;
import com.c3stones.utils.R;

/**
 * 系统日志Controller
 * 
 * @author CL
 *
 */
@RestController
@RequestMapping(value = "/sys/log")
public class SysLogController {

	@Autowired
	private SysLogService sysLogService;

	/**
	 * 日志分页查询
	 * 
	 * @param start  起始页码
	 * @param limit  每页数量
	 * @param sysLog 系统日志
	 * @return
	 */
	@RequestMapping(value = "page")
	public R<Page<SysLog>> page(int start, int limit, SysLog sysLog) {
		QueryWrapper<SysLog> queryWrapper = new QueryWrapper<>(sysLog);
		queryWrapper.orderByDesc("create_date");
		Page<SysLog> page = sysLogService.page(new Page<>(start, limit), queryWrapper);
		return R.ok(page);
	}

}
  • 创建日志切面
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.c3stones.constants.Global;
import com.c3stones.entity.SysLog;
import com.c3stones.service.SysLogService;
import com.c3stones.utils.ByteUtils;
import com.c3stones.utils.R;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.extern.log4j.Log4j2;

/**
 * 系统日志切面
 * 
 * @author CL
 *
 */
@Log4j2
@Aspect
@Component
public class SysLogAspect {

	private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

	@Autowired
	private Executor customThreadPoolTaskExecutor;

	@Autowired
	private SysLogService sysLogService;

	/**
	 * 日志切点
	 */
	@Pointcut("execution(public * com.c3stones.controller.*.*(..))")
	public void sysLogAspect() {
	}

	/**
	 * 前置通知
	 * 
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before(value = "sysLogAspect()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		HttpServletRequest request = ((ServletRequestAttributes) Objects
				.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

		SysLog sysLog = new SysLog();
		// 创建人信息请根据实际项目获取方式获取
		sysLog.setCreateUserCode("");
		sysLog.setCreateUserName("");
		sysLog.setStartTime(LocalDateTime.now());
		sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
		sysLog.setRequestParams(formatParams(request.getParameterMap()));
		sysLog.setRequestMethod(request.getMethod());
		sysLog.setRequestIp(ServletUtil.getClientIP(request));
		sysLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
		String userAgentStr = request.getHeader("User-Agent");
		sysLog.setUserAgent(userAgentStr);
		UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
		sysLog.setDeviceName(userAgent.getOs().getName());
		sysLog.setBrowserName(userAgent.getBrowser().getName());

		sysLogThreadLocal.set(sysLog);

		log.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
	}

	/**
	 * 返回通知
	 * 
	 * @param ret
	 */
	@AfterReturning(pointcut = "sysLogAspect()", returning = "ret")
	public void doAfterReturning(Object ret) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_INGO);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(sysLog.getStartTime(), sysLog.getEndTime())));
		R<?> r = Convert.convert(R.class, ret);
		if (r.getCode() == Global.TRUE) {
			sysLog.setIsException(Global.NO);
		} else {
			sysLog.setIsException(Global.YES);
			sysLog.setExceptionInfo(r.getMsg());
		}
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();

		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}

	/**
	 * 异常通知
	 * 
	 * @param e
	 */
	@AfterThrowing(pointcut = "sysLogAspect()", throwing = "e")
	public void doAfterThrowable(Throwable e) {
		SysLog sysLog = sysLogThreadLocal.get();
		sysLog.setLogType(Global.LOG_ERROR);
		sysLog.setEndTime(LocalDateTime.now());
		sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime(), sysLog.getEndTime())));
		sysLog.setIsException(Global.YES);
		sysLog.setExceptionInfo(e.getMessage());
		customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
		sysLogThreadLocal.remove();
		
		Runtime runtime = Runtime.getRuntime();
		log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
				sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
				ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
	}

	/**
	 * 格式化参数
	 * 
	 * @param parameterMap
	 * @return
	 */
	private String formatParams(Map<String, String[]> parameterMap) {
		if (parameterMap == null) {
			return null;
		}
		StringBuilder params = new StringBuilder();
		for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
			if (params.length() != 0) {
				params.append("&");
			}
			params.append(param.getKey() + "=");
			if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
				params.append("*");
			} else if (param.getValue() != null) {
				params.append(ArrayUtil.join(param.getValue(), ","));
			}
		}
		return params.toString();
	}

	/**
	 * 保存日志线程
	 * 
	 * @author CL
	 *
	 */
	private static class SaveLogThread extends Thread {
		private SysLog sysLog;
		private SysLogService sysLogService;

		public SaveLogThread(SysLog sysLog, SysLogService sysLogService) {
			this.sysLog = sysLog;
			this.sysLogService = sysLogService;
		}

		@Override
		public void run() {
			sysLog.setCreateDate(LocalDateTime.now());
			sysLogService.save(sysLog);
		}
	}
}
  • 创建示例Controller
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.c3stones.utils.R;

/**
 * 示例Controller
 * 
 * @author CL
 *
 */
@RestController
public class DemoController {

	/**
	 * 示例方法1
	 * 
	 * @return
	 */
	@RequestMapping(value = "demo1")
	public R<String> demo1(String str) {
		return R.ok("成功返回 -> " + str);
	}

	/**
	 * 示例方法2
	 * 
	 * @return
	 */
	@RequestMapping(value = "demo2")
	public R<String> demo2(String str, int num) {
		return R.failed("失败返回");
	}

	/**
	 * 示例方法3
	 * 
	 * @return
	 */
	@SuppressWarnings("unused")
	@RequestMapping(value = "demo3")
	public R<String> demo3() {

		int a = 1 / 0;

		return R.ok("模拟异常");
	}

}
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 * 
 * @author CL
 *
 */
@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class);
	}

}

4. 测试

# 接口返回:
{
	"code": true,
	"msg": null,
	"data": "成功返回 -> demo1"
}

# 控制台打印
2021-07-04 14:45:12.077  INFO 16032 --- [nio-8080-exec-4] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:45:12.076  URI: /demo1  IP: 127.0.0.1
2021-07-04 14:45:12.077  INFO 16032 --- [nio-8080-exec-4] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:45:12.077  用时: 1ms  URI: /demo1  总内存: 323.5MB  已用内存: 70.332MB
# 接口返回:
{
	"code": false,
	"msg": "失败返回",
	"data": null
}

# 控制台打印
2021-07-04 14:48:57.795  INFO 16032 --- [nio-8080-exec-6] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:48:57.794  URI: /demo2  IP: 127.0.0.1
2021-07-04 14:48:57.796  INFO 16032 --- [nio-8080-exec-6] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:48:57.795  用时: 1ms  URI: /demo2  总内存: 323.5MB  已用内存: 72.496MB
# 接口返回:
异常无返回

# 控制台打印
2021-07-04 14:49:30.260  INFO 16032 --- [nio-8080-exec-7] com.c3stones.aspect.SysLogAspect         : 开始计时: 2021-07-04T14:49:30.260  URI: /demo3  IP: 127.0.0.1
2021-07-04 14:49:30.261  INFO 16032 --- [nio-8080-exec-7] com.c3stones.aspect.SysLogAspect         : 计时结束: 2021-07-04T14:49:30.261  用时: 0ms  URI: /demo3  总内存: 323.5MB  已用内存: 72.861MB
2021-07-04 14:49:30.272 ERROR 16032 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero
	......
# 接口返回:
{
	"code": true,
	"msg": null,
	"data": {
		"records": [
			{
				"id": 3,
				"logType": "ERROR",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:49:30",
				"requestUri": "/demo3",
				"requestMethod": "GET",
				"requestParams": "",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "1",
				"exceptionInfo": "/ by zero",
				"startTime": "2021-07-04T14:49:30",
				"endTime": "2021-07-04T14:49:30",
				"executeTime": 0,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			},
			
			{
				"id": 2,
				"logType": "INFO",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:48:58",
				"requestUri": "/demo2",
				"requestMethod": "GET",
				"requestParams": "str=demo2&num=10",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "1",
				"exceptionInfo": "失败返回",
				"startTime": "2021-07-04T14:48:58",
				"endTime": "2021-07-04T14:48:58",
				"executeTime": 1,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			},
			{
				"id": 1,
				"logType": "INFO",
				"createUserCode": "",
				"createUserName": "",
				"createDate": "2021-07-04T14:45:12",
				"requestUri": "/demo1",
				"requestMethod": "GET",
				"requestParams": "str=demo1",
				"requestIp": "127.0.0.1",
				"serverAddress": "http://127.0.0.1:8080",
				"isException": "0",
				"exceptionInfo": null,
				"startTime": "2021-07-04T14:45:12",
				"endTime": "2021-07-04T14:45:12",
				"executeTime": 1,
				"userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
				"deviceName": "Windows 10 or Windows Server 2016",
				"browserName": "Chrome"
			}
		],
		"total": 0,
		"size": 10,
		"current": 1,
		"orders": [],
		"optimizeCountSql": true,
		"hitCount": false,
		"searchCount": true,
		"pages": 0
	}
}

5. 项目地址

  spring-aop-log-demo

  • 9
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目描述 说明: spring security 全注解式的权限管理 动态配置权限,角色和资源,权限控制到按钮粒度 采用token进行权限校验,禁用session,未登录返回401,权限不足返回403 采用redis存储token及权限信息 内置功能: 用户管理:用户查询、添加用户、修改用户、给用户分配角色 菜单管理:菜单列表、添加菜单、修改菜单、删除菜单、权限配置、菜单图标设置、菜单排序 角色管理:角色查询、添加角色、修改角色、删除角色 代码生成:根据表名生成bean、controller、dao、Mapper.xml、列表页、搜索、分页、新增页、修改页 job集群:创建job、取消job、查询job、下拉搜索spring bean 数据源监控:druid 接口swagger文档 日志查询 邮件管理:发送邮件、搜索邮件 文件管理:上传文件、文件列表、文件删除 公告管理:公告未读提醒、发布公告、查询公告、公告阅读人列表 excel下载:自定义sql导出excel、也可在页面展示sql结果数据 字典管理:一些常量字典的维护 个人信息修改 修改密码 头像修改 其他说明: 日志模块 sl4j日志分包:将sql日志、业务日志异常日志进行了分离,更方便定位问题 日志表:使用aop拦截实现 权限控制:基于token方式,禁用session 对各种不同异常进行了全局统一处理 使用lombok简化java代码,让源码更简洁,可读性高 mybatis未进行二次封装,原滋原味,简单sql采用注解,复杂sql采用Mapper.xml配置 使用了layui的弹出层、菜单、文件上传、富文本编辑、日历、选项卡、数据表格等 表单数据采用bootstrapValidator校验,简单快捷方便 运行环境 jdk8+mysql+redis+IntelliJ IDEA+maven 项目技术(必填) Springboot+Mybatis+ SpringMvc+springsecrity+Redis+bootstrap+jquery 数据库文件 压缩包内 jar包文件 maven搭建
【资源说明】 1、基于SpringBoot+Vue开发的前后端分离外卖点单系统源码+数据库+项目说明.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 4、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。 基于SpringBoot+Vue开发的前后端分离外卖点单系统源码+数据库+项目说明.zip 基于SpringBoot+Vue开发的前后端分离外卖点单系统源码+数据库+项目说明.zip ## 目录结构 后台前端项目位于renren-ui下 小程序前端项目位于takeout_mp下 SQL文件位于根目录下的takeout_mysql8.sql,需要MYSQL8以上版本。 **ps:请先运行后端项目,再启动前端项目。** ``` take_out │ ├─renren-admin 美食元素后台管理后端服务 │ │ │ │ │ ├─modules 模块 │ │ ├─job 定时任务 │ │ ├─log 日志管理 │ │ ├─oss 文件存储 │ │ ├─security 安全模块 │ │ ├─sys 系统管理(核心) | | └─takeout 外卖业务模块(核心) │ │ │ └─resources │ ├─mapper MyBatis文件 │ ├─public 静态资源 │ └─application.yml 全局配置文件 │ │ ├─renren-api 美食元素小程序后端服务 │ ├─renren-common 公共模块 ├─renren-generator 代码生成器 │ └─resources │ ├─mapper MyBatis文件 │ ├─template 代码生成器模板(可增加或修改相应模板) │ ├─application.yml 全局配置文件 │ └─generator.properties 代码生成器,配置文件 │ ├─renren-ui 美食元素后台管理Vue前端项目 ├─takeout_mp uniapp微信小程序项目 ``` ## 项目特点 - 友好的代码结构及注释,便于阅读及二次开发 - 实现前后端分离,通过token进行数据交互 - 支持动态权限修改,采用RBAC模型,前端菜单和后台权限实时更新。 - 提供CrudService接口,对增删改查进行封装,代码更简洁 - 页面交互使用Vue2.x,极大的提高了开发效率 - 完善的部门管理及数据权限,通过注解实现数据权限的控制 - 完善的XSS防范及脚本过滤,彻底杜绝XSS攻击 - 完善的代码生成机制,可在线生成entity、xml、dao、service、vue、sql代码,减少70%以上的开发任务 - 引入quartz定时任务,可动态完成任务的添加、修改、删除、暂停、恢复及日志查看等功能 - 引入Hibernate Validator校验框架,轻松实现后端校验 - 引入云存储服务,已支持:七牛云、阿里云、腾讯云等 - 引入swagger文档支持,方便编写API接口文档 - 新增AOP注解实现日志管理。 - 代码遵循阿里巴巴开发规范,利于开发者学习。 ## 技术选型 - 核心框架:Spring Boot 2.7.1 - 安全框架:Apache Shiro 1.9 - 持久层框架:MyBatis 3.5 - 定时器:Quartz 2.3 - 数据库连接池:Druid 1.2 - 日志管理:Logback - 页面交互:Vue2.x - 微信小程序:uni-app ## 开发环境 | 开发工具 | 说明 | | ----------------------------- | ------------------ | | IDEA | Java开发工具IDE | | WebStrom或者VS Code | Vue开发工具IDE | | Navicat | MySQL远程连接工具 | | Another Redis Desktop Manager | R
抱歉,我是一名语言模型AI,无法为您提供具体的代码实现。但是我可以提供一些思路和步骤。 1. 准备好环境 首先需要安装Java、IntelliJ IDEA、MySQL等软件,并创建好相应的数据库和表。需要添加相关的依赖,比如spring-boot-starter、mybatis-plus-boot-starter等等,以及Ajax和LayUI的相关库。 2. 创建实体类和Mapper接口 创建员工和日志的实体类,以及对应的Mapper接口,用于对数据库进行操作。可以使用Mybatis-Plus的代码生成器来自动生成基础的代码。 3. 编写业务逻辑层 在业务逻辑层中编写增删改查等操作方法,以及一些自定义的业务方法,用于处理员工和日志的具体业务需求。 4. 编写Controller层 在Controller层中处理请求和返回数据,并调用相应的业务逻辑方法。可以使用Ajax来实现异步提交数据和返回结果,使用LayUI框架来美化页面和处理交互逻辑。 5. 添加安全认证和日志记录 添加安全认证功能,以实现员工的登录、登出等操作,并记录相关的日志信息。可以使用Spring Security等框架来实现认证和授权,并使用AOP技术来记录日志信息。 6. 测试和部署 在本地进行测试,并将代码打包部署到服务器上。可以使用Maven来管理依赖和构建项目,使用Docker等容器技术来部署和管理应用程序。 以上就是实现员工管理和日志管理的大致步骤和思路。当然,具体实现还需要根据具体的业务需求和技术选型来进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值