SpringBoot使用AOP统一处理日志
- 应用(记录用户操作日志): 有时候我们需要处理一些请求日志,或者对某些方法进行一些监控,如果出现例外情况应该进行怎么样的处理,现在,我们从spring boot中引入AOP
1、开发准备
环境:idea、jdk 1.8、springboot、mysql
1.1 目录结构
└─src
└─main
├─java
│ └─com
│ └─example
│ └─log
│ │ LogApplication.java
│ ├─annotation
│ │ Log.java
│ ├─aspect
│ │ LogAspect.java
│ ├─common
│ │ ├─context
│ │ │ BaseContext.java
│ │ │ CallBack.java
│ │ │ SpringContextHolder.java
│ │ ├─enums
│ │ │ Action.java
│ │ └─utils
│ │ CloseUtil.java
│ │ ExceptionUtil.java
│ │ FileUtil.java
│ │ ServletUtil.java
│ ├─controller
│ │ LogController.java
│ ├─domain
│ │ SysLog.java
│ ├─repository
│ │ LogRepository.java
│ └─service
│ │ LogService.java
│ └─impl
│ LogServiceImpl.java
└─resources
│ application.yml
└─ip2region
ip2region.db (注意这个文件,作用:转换IP地址来源)
resources/ip2region/ip2region.db 文件 下载:https://files.cnblogs.com/files/mmdz/ip2region.db.rar
1.2 日志表
准备sys_log日志表(mysql)
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` char(20) NOT NULL COMMENT '编号',
`operator` varchar(255) DEFAULT NULL COMMENT '操作人',
`operation_time` datetime DEFAULT NULL COMMENT '操作时间',
`title` varchar(255) DEFAULT NULL COMMENT '编号',
`method` varchar(255) DEFAULT NULL COMMENT '方法',
`type` varchar(255) DEFAULT NULL COMMENT '请求方式',
`params` varchar(255) DEFAULT NULL COMMENT '参数',
`state` bit(1) DEFAULT NULL COMMENT '状态',
`action` varchar(255) DEFAULT NULL COMMENT '操作',
`request_ip` varchar(255) DEFAULT NULL COMMENT '请求ip',
`address` varchar(255) DEFAULT NULL COMMENT 'ip来源',
`browser` varchar(255) DEFAULT NULL COMMENT '浏览器',
`time` bigint(10) DEFAULT NULL COMMENT '请求耗时',
`error` text COMMENT '异常信息',
`system` varchar(255) DEFAULT NULL COMMENT '操作系统',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
1.3 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>log</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>log</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<datasource.version>3.3.1</datasource.version>
<mysql.version>8.0.22</mysql.version>
<mybatis.plus.version>3.4.3</mybatis.plus.version>
<swagger.version>2.9.2</swagger.version>
<hutool.version>5.5.7</hutool.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 切 面 编 程 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 数 据 库 操 作 框 架 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- 数 据 库 连 接 工 具 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- 常 用 工 具 类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Swagger UI 相关 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
<!-- 解析客户端操作系统、浏览器信息 -->
<dependency>
<groupId>nl.basjes.parse.useragent</groupId>
<artifactId>yauaa</artifactId>
<version>5.23</version>
</dependency>
<!-- 根据IP获取城市-Java调用“ip2region” -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.4 application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/log?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT
username: root
password: 123456
2、编码
2.1 domain包
SysLog(日志模型)
package com.example.log.domain;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.log.common.enums.Action;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 日志模型
* */
@Data
@TableName("sys_log")
public class SysLog implements Serializable {
/** 编号 */
@TableId("id")
private String id;
/** 操作人 */
@TableField("operator")
private String operator;
/** 操作时间 */
@TableField("operation_time")
private LocalDateTime operationTime;
/** 标题 */
@TableField("title")
private String title;
/** 请求方法 */
@TableField("method")
private String method;
/** 请求方式 */
@TableField("type")
private String type;
/** 请求参数 */
@TableField("params")
private String params;
/** 状态(是否成功) */
@TableField("state")
private Boolean state;
/** 操作类型 */
@TableField("action")
private Action action;
/** 请求ip */
@TableField("request_ip")
private String requestIp;
/** ip来源 */
@TableField("address")
private String address;
/** 浏览器 */
@TableField("browser")
private String browser;
/** 请求耗时 */
@TableField("time")
private Long time;
/** 异常信息 */
@TableField("error")
private byte[] error;
@TableField(exist = false)
private String exceptionDetailStr;
/** 系统 */
@TableField("`system`")
private String system;
public String getExceptionDetailStr() {
return new String(ObjectUtil.isNotNull(error) ? error : "".getBytes());
}
}
2.2 annotation包
package com.example.log.annotation;
import com.example.log.common.enums.Action;
import java.lang.annotation.*;
/**
* 日志 注解
* */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Log {
/**
* Title 默认输入
* */
String title() default "暂无标题";
/**
* Describe 默认输入
* */
String describe() default "暂无描述";
/**
* Action 操作类型
* */
Action action() default Action.QUERY;
}
2.3 common包
2.3.1 enums
Action 日 志 分 类
package com.example.log.common.enums;
/**
* 日 志 分 类
*/
public enum Action {
/** 认证 */
AUTH,
/** 增 */
ADD,
/** 删 */
REMOVE,
/** 改 */
EDIT,
/** 查 */
QUERY,
/** 导入 */
IMPORT,
/** 导出 */
REPORT,
/** 上传 */
UPLOAD
}
2.3.2 context
CallBack
package com.example.log.common.context;
/**
* @Desc: TODO
* 针对某些初始化方法,在SpringContextHolder 初始化前时,<br>
* 提交一个 提交回调任务。<br>
* 在SpringContextHolder 初始化后,进行回调使用
*/
public interface CallBack {
/**
* 回调执行方法
*/
void executor();
/**
* 本回调任务名称
* @return /
*/
default String getCallBackName() {
return Thread.currentThread().getId() + ":" + this.getClass().getName();
}
}
SpringContextHolder
package com.example.log.common.context;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import java.util.ArrayList;
import java.util.List;
public class SpringContextHolder {
private static ApplicationContext applicationContext = null;
private static final List<CallBack> CALL_BACKS = new ArrayList<>();
private static boolean addCallback = true;
/**
* 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
* 在SpringContextHolder 初始化后,进行回调使用
*
* @param callBack 回调函数
*/
public synchronized static void addCallBacks(CallBack callBack) {
if (addCallback) {
SpringContextHolder.CALL_BACKS.add(callBack);
} else {
System.out.println("CallBack:" + callBack.getCallBackName() +" 已无法添加!立即执行");
callBack.executor();
}
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param defaultValue 默认值
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
T result = defaultValue;
try {
result = getBean(Environment.class).getProperty(property, requiredType);
} catch (Exception ignored) {}
return result;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
}
}
}
BaseContext
package com.example.log.common.context;
import com.example.log.common.utils.ServletUtil;
import com.example.log.domain.SysLog;
import com.example.log.common.enums.Action;
import com.example.log.service.LogService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* Base Context
*/
@Component
public class BaseContext {
/**
* 日 志 服 务
*/
@Resource
private LogService sysLogService;
/**
* 新增日志
*
* @param title 标题
* @param methodName 请求方法
* @param parameter 参数
* @param action 动作
* @param state 状态
* @param time 请求耗时
* @param error 异常
*/
@Async
@Transactional(rollbackFor = Exception.class)
public void record(String title,
String methodName,
String parameter,
Action action,
Boolean state,
Long time,
byte[] error) {
SysLog sysLog = new SysLog();
sysLog.setOperator("");// 操作人
sysLog.setOperationTime(LocalDateTime.now());// 操作时间
sysLog.setTitle(title);//标题
sysLog.setMethod(methodName);// 请求方法
sysLog.setType(ServletUtil.getMethod());// 请求方式
sysLog.setParams(parameter);// 参数
sysLog.setState(state);// 状态(是否成功)
sysLog.setAction(action);// 操作类型
String ip = ServletUtil.getIp();
sysLog.setRequestIp(ServletUtil.getIp());// 请求ip
sysLog.setAddress(ServletUtil.getCityInfo(ip));// ip来源
sysLog.setBrowser(ServletUtil.getBrowser());// 浏览器
sysLog.setTime(time);// 请求耗时
sysLog.setError(error);// 异常信息
sysLog.setSystem(ServletUtil.getSystem());// 操作系统
sysLogService.save(sysLog);
}
}
2.3.3 utils
CloseUtil
package com.example.log.common.utils;
import java.io.Closeable;
/**
* @Desc: TODO 用于关闭各种连接,缺啥补啥
*/
public class CloseUtil {
public static void close(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (Exception e) {
// 静默关闭
}
}
}
public static void close(AutoCloseable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (Exception e) {
// 静默关闭
}
}
}
}
ExceptionUtil
package com.example.log.common.utils;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @Desc: TODO 将日志堆栈信息输出到文件
*/
public class ExceptionUtil {
public static String getMessage(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
// 将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
/**
* 获取堆栈信息
*/
public static String getStackTrace(Throwable throwable){
StringWriter sw = new StringWriter();
try (PrintWriter pw = new PrintWriter(sw)) {
throwable.printStackTrace(pw);
return sw.toString();
}
}
}
FileUtil
package com.example.log.common.utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @Desc: TODO File工具类,扩展 hutool 工具包
*/
public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 系统临时目录
* <br>
* windows 包含路径分割符,但Linux 不包含,
* 在windows \\==\ 前提下,
* 为安全起见 同意拼装 路径分割符,
* <pre>
* java.io.tmpdir
* windows : C:\Users/xxx\AppData\Local\Temp\
* linux: /temp
* </pre>
*/
public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
/**
* inputStream 转 File
*/
public static File inputStreamToFile(InputStream ins, String name){
File file = new File(SYS_TEM_DIR + name);
if (file.exists()) {
return file;
}
OutputStream os = null;
try {
os = new FileOutputStream(file);
int bytesRead;
int len = 8192;
byte[] buffer = new byte[len];
while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
os.write(buffer, 0, bytesRead);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
CloseUtil.close(os);
CloseUtil.close(ins);
}
return file;
}
}
ServletUtil
package com.example.log.common.utils;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.example.log.common.context.SpringContextHolder;
import nl.basjes.parse.useragent.UserAgent;
import nl.basjes.parse.useragent.UserAgentAnalyzer;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Servlet 工具类
* */
public class ServletUtil {
/**
* IP归属地查询
*/
private static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true";
/**
* 用于IP定位转换
*/
private static final String REGION = "内网IP|内网IP";
private static boolean ipLocal = false;
private static File file = null;
private static DbConfig config;
private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer
.newBuilder()
.hideMatcherLoadStats()
.withCache(10000)
.withField(UserAgent.AGENT_NAME_VERSION)
.build();
static {
SpringContextHolder.addCallBacks(() -> {
ipLocal = SpringContextHolder.getProperties("ip.local-parsing", false, Boolean.class);
if (ipLocal) {
/*
* 此文件为独享 ,不必关闭
*/
String path = "ip2region/ip2region.db";
String name = "ip2region.db";
try {
config = new DbConfig();
file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name);
} catch (Exception e) {
e.printStackTrace();
// log.error(e.getMessage(), e);
}
}
});
}
/**
* Describe: Request 客户端地址(获取ip地址)
*
* @return {@link String}
* */
public static String getIp() {
HttpServletRequest request = getRequest();
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
ipAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
/**
* 根据ip获取详细地址
*/
public static String getCityInfo(String ip) {
if (ipLocal) {
return getLocalCityInfo(ip);
} else {
return getHttpCityInfo(ip);
}
}
/**
* 根据ip获取详细地址
*/
public static String getHttpCityInfo(String ip) {
String api = String.format(IP_URL, ip);
cn.hutool.json.JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
return object.get("addr", String.class);
}
/**
* 根据ip获取详细地址
*/
public static String getLocalCityInfo(String ip) {
try {
DataBlock dataBlock = new DbSearcher(config, file.getPath())
.binarySearch(ip);
String region = dataBlock.getRegion();
String address = region.replace("0|", "");
char symbol = '|';
if (address.charAt(address.length() - 1) == symbol) {
address = address.substring(0, address.length() - 1);
}
return address.equals(REGION) ? "内网IP" : address;
} catch (Exception e) {
// log.error(e.getMessage(), e);
e.printStackTrace();
}
return "";
}
/**
* 获取 HttpServletRequest 对象
*
* @return {@link HttpServletRequest}
* */
private static HttpServletRequest getRequest(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return servletRequestAttributes.getRequest();
}
/**
* Request 请求方法(类型)
*
* @return {@link String}
* */
public static String getMethod(){
return getRequest().getMethod();
}
/**
* Request 请求头
*
* @param name 名称
* @return {@link String}
* */
public static String getHeader(String name){
return getRequest().getHeader(name);
}
/**
* Request Agent
*
* @return {@link String}
* */
private static String getAgent(){
return getHeader("User-Agent");
}
/**
* Request 浏览器类型
*
* @return {@link String}
* */
public static String getBrowser(){
String browser = "";
String userAgent = getAgent();
if (userAgent.contains("Firefox")) browser = "火狐浏览器";
else if (userAgent.contains("Chrome")) browser = "谷歌浏览器";
else if (userAgent.contains("Trident")) browser = "IE 浏览器";
else browser = "你用啥浏览器";
UserAgent.ImmutableUserAgent parse = userAgentAnalyzer.parse(userAgent);
String value = parse.get(UserAgent.AGENT_NAME_VERSION).getValue();
return browser + "(" + value + ")";
}
/**
* Request 访问来源 ( 客户端类型 )
*
* @return {@link String}
* */
public static String getSystem(){
String userAgent = getAgent();
if (getAgent().toLowerCase().contains("windows" )) return "Windows";
else if (userAgent.toLowerCase().contains("mac" )) return "Mac";
else if (userAgent.toLowerCase().contains("x11" )) return "Unix";
else if (userAgent.toLowerCase().contains("android" )) return "Android";
else if (userAgent.toLowerCase().contains("iphone" )) return "IPhone";
else return "UnKnown, More-Info: " + userAgent;
}
}
2.4 aspect包(核心)
Log 实现 Aop 切面类
package com.example.log.aspect;
import cn.hutool.json.JSONUtil;
import com.example.log.annotation.Log;
import com.example.log.common.context.BaseContext;
import com.example.log.common.enums.Action;
import com.example.log.common.utils.ExceptionUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Log 实现 Aop 切面类
*/
@Aspect
@Component
public class LogAspect {
ThreadLocal<Long> currentTime = new ThreadLocal<>();
/**
* 基 础 上 下 文
*/
@Resource
private BaseContext context;
/**
* 配置切入点(切 面 编 程)
*/
@Pointcut("@annotation(com.example.log.annotation.Log) || @within(com.example.log.annotation.Log)")
public void logPointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
/**
* 处 理 系 统 日 志(配置环绕通知,使用在方法logPointcut()上注册的切入点)
*
* @param joinPoint join point for advice
*/
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
// 记录方法的执行时间
currentTime.set(System.currentTimeMillis());
// 执 行 方 法
result = joinPoint.proceed();
// 注解解析
Log annotation = getAnnotation(joinPoint);
String title = annotation.title();
Action action = annotation.action();
String describe = annotation.describe();
// 获取方法名
String methodName = getMethodName(joinPoint);
// 获取参数
String parameter = getParameterToJson((ProceedingJoinPoint) joinPoint);
// 请求耗时
Long time = System.currentTimeMillis() - currentTime.get();
currentTime.remove();
// 记 录 日 志
context.record(title, methodName, parameter, action, true, time, null);
return result;
}
/**
* 配置异常通知
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "logPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
// 注解解析
Log annotation = getAnnotation((ProceedingJoinPoint) joinPoint);
String title = annotation.title();
Action action = annotation.action();
String describe = annotation.describe();
// 获取方法名
String methodName = getMethodName((ProceedingJoinPoint) joinPoint);
// 获取参数
String parameter = getParameterToJson((ProceedingJoinPoint) joinPoint);
// 请求耗时
Long time = System.currentTimeMillis() - currentTime.get();
currentTime.remove();
// 异常详细
byte[] exceptionDetail = ExceptionUtil.getStackTrace(e).getBytes();
// 记 录 日 志
context.record(title, methodName, parameter, action, false, time, exceptionDetail);
}
/**
* 获 取 注 解
*/
public Log getAnnotation(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Class<? extends Object> targetClass = point.getTarget().getClass();
Log targetLog = targetClass.getAnnotation(Log.class);
if (targetLog != null) {
return targetLog;
} else {
Method method = signature.getMethod();
Log log = method.getAnnotation(Log.class);
return log;
}
}
/**
* 获 取 方法名
*/
public String getMethodName(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
// 方法路径
String methodName = point.getTarget().getClass().getName()+"."+signature.getName()+"()";
return methodName;
}
/**
* 获 取 参数(转换json格式)
*/
public String getParameterToJson(ProceedingJoinPoint point) {
List<Object> argList = new ArrayList<>();
//参数值
Object[] argValues = point.getArgs();
//参数名称
String[] argNames = ((MethodSignature)point.getSignature()).getParameterNames();
if(argValues != null){
for (int i = 0; i < argValues.length; i++) {
Map<String, Object> map = new HashMap<>();
String key = argNames[i];
map.put(key, argValues[i]);
argList.add(map);
map = null;
}
}
if (argList.size() == 0) {
return "";
}
return argList.size() == 1 ? JSONUtil.toJsonStr(argList.get(0)) : JSONUtil.toJsonStr(argList);
}
}
2.5 三层架构
2.5.1 controller
package com.example.log.controller;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.log.annotation.Log;
import com.example.log.service.LogService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/logs")
@Api(tags = "日志管理")
public class LogController {
/** 使用@RequiredArgsConstructor 基于构造函数注入*/
private final LogService logService;
/**
* 查询日志列表
*/
@GetMapping("list")
@ApiOperation(value = "查询日志")
public String list(){
return JSONUtil.parse(logService.list()).toString();
}
/**
* 测试
*/
@GetMapping("demo")
@Log(title = "测试")
@ApiOperation(value = "测试")
public String demo(@RequestParam String param){
return param;
}
}
2.5.2 service
package com.example.log.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.log.domain.SysLog;
public interface LogService extends IService<SysLog> {
}
package com.example.log.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.log.domain.SysLog;
import com.example.log.repository.LogRepository;
import com.example.log.service.LogService;
import org.springframework.stereotype.Service;
@Service
public class LogServiceImpl extends ServiceImpl<LogRepository, SysLog> implements LogService {
}
2.5.2 repository
package com.example.log.repository;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.log.domain.SysLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogRepository extends BaseMapper<SysLog> {
}
3、测试
新增测试 http://127.0.0.1:8080//api/logs/demo?param=123
查看数据 http://127.0.0.1:8080//api/logs/list