SpringBoot_操作日志记录

mq0036 阅读:43 2022-07-29 11:52:56 评论:0

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

 

 


本文参考链接:https://www.cnblogs.com/mmdz/p/15522775.html
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

搜索
排行榜
关注我们

一个IT知识分享的公众号