• SpringBoot使用log4j2将日志记录到文件及自定义数据库


    目录

    一、环境说明     

    二、进行配置

    1、pom.xml

    2、log4j2.xml

    3、CustomDataSourceProperties

    4、ConfigReader

    5、ConnectionFactory 连接工厂类,用于管理数据库连接

    三、进行简单测试配置

    1、LogUtils

    2、LoginUserInfoHelper

    3、LoginLogUtils

    4、写日志


    一、环境说明     

            Spring Boot2+MyBatis-Plus+Log4j2

    二、进行配置

    1、pom.xml

            由于Spring Boot内置的日志框架是logback,会导致和log4j2冲突,所以要先排除项目中logback的依赖。同时引入log4j2。

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starterartifactId>
    4. <exclusions>
    5. <exclusion>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-loggingartifactId>
    8. exclusion>
    9. exclusions>
    10. dependency>
    11. <dependency>
    12. <groupId>org.springframework.bootgroupId>
    13. <artifactId>spring-boot-starter-log4j2artifactId>
    14. dependency>

    2、log4j2.xml

    1. "1.0" encoding="UTF-8"?>
    2. <Configuration status="WARN" monitorInterval="30">
    3. <Properties>
    4. <Property name="baseDir">./logsProperty>
    5. <Property name="logPattern">
    6. %d{yyyy-MM-dd HH:mm:ss} %highlight{%6p} %style{%5pid}{bright,magenta} --- [%15.15t]
    7. %style{%-40.40logger{39}}{bright,cyan} : %m%n
    8. Property>
    9. <Property name="fileLayout">
    10. %d{yyyy-MM-dd HH:mm:ss} %p --- [%t] %logger : %m%n"
    11. Property>
    12. <Property name="fileSize">10MBProperty>
    13. Properties>
    14. <CustomLevels>
    15. <CustomLevel name="CUSTOM_LOG" intLevel="90"/>
    16. <CustomLevel name="EXCEPTION_LOG" intLevel="91"/>
    17. <CustomLevel name="OPERATION_LOG" intLevel="92"/>
    18. <CustomLevel name="LOGIN_LOG" intLevel="93"/>
    19. CustomLevels>
    20. <Appenders>
    21. <Console name="Console" target="SYSTEM_OUT">
    22. <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
    23. <PatternLayout pattern="${logPattern}"/>
    24. Console>
    25. <RollingFile name="DebugAppender" fileName="${baseDir}/app_debug.log"
    26. filePattern="${baseDir}/debug_%d{yyyy-MM-dd}_%i.log">
    27. <Filters>
    28. <ThresholdFilter level="DEBUG"/>
    29. <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
    30. Filters>
    31. <PatternLayout pattern="${fileLayout}"/>
    32. <Policies>
    33. <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    34. <SizeBasedTriggeringPolicy size="${fileSize}"/>
    35. Policies>
    36. RollingFile>
    37. <RollingFile name="InfoAppender" fileName="${baseDir}/app_info.log"
    38. filePattern="${baseDir}/info_%d{yyyy-MM-dd}_%i.log">
    39. <Filters>
    40. <ThresholdFilter level="INFO"/>
    41. <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
    42. Filters>
    43. <PatternLayout pattern="${fileLayout}"/>
    44. <Policies>
    45. <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    46. <SizeBasedTriggeringPolicy size="${fileSize}"/>
    47. Policies>
    48. RollingFile>
    49. <RollingFile name="WarnAppender" fileName="${baseDir}/app_warn.log"
    50. filePattern="${baseDir}/info_%d{yyyy-MM-dd}_%i.log">
    51. <Filters>
    52. <ThresholdFilter level="WARN"/>
    53. <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
    54. Filters>
    55. <PatternLayout pattern="${fileLayout}"/>
    56. <Policies>
    57. <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    58. <SizeBasedTriggeringPolicy size="${fileSize}"/>
    59. Policies>
    60. RollingFile>
    61. <RollingFile name="ErrorAppender" fileName="${baseDir}/app_error.log"
    62. filePattern="${baseDir}/error_%d{yyyy-MM-dd}_%i.log">
    63. <Filters>
    64. <ThresholdFilter level="ERROR"/>
    65. <ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/>
    66. Filters>
    67. <PatternLayout pattern="${fileLayout}"/>
    68. <Policies>
    69. <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
    70. <SizeBasedTriggeringPolicy size="${fileSize}"/>
    71. Policies>
    72. RollingFile>
    73. <JDBC name="LoginDatabase" tableName="sys_log_login">
    74. <ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/>
    75. <Filters>
    76. <ThresholdFilter level="LOGIN_LOG"/>
    77. <ThresholdFilter level="OPERATION_LOG" onMatch="DENY" onMismatch="NEUTRAL"/>
    78. Filters>
    79. <Column name="user_id" pattern="%X{user_id}"/>
    80. <Column name="user_name" pattern="%X{user_name}"/>
    81. <Column name="client_ip" pattern="%X{client_ip}"/>
    82. <Column name="device_info" pattern="%X{device_info}"/>
    83. <Column name="remarks" pattern="%X{remarks}"/>
    84. <Column name="login_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/>
    85. JDBC>
    86. <JDBC name="OperationDatabase" tableName="sys_log_operation">
    87. <ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/>
    88. <Filters>
    89. <ThresholdFilter level="OPERATION_LOG"/>
    90. <ThresholdFilter level="EXCEPTION_LOG" onMatch="DENY" onMismatch="NEUTRAL"/>
    91. Filters>
    92. <Column name="user_id" pattern="%X{user_id}"/>
    93. <Column name="user_name" pattern="%X{user_name}"/>
    94. <Column name="v_before" pattern="%X{v_before}"/>
    95. <Column name="v_after" pattern="%X{v_after}"/>
    96. <Column name="remarks" pattern="%X{remarks}"/>
    97. <Column name="operation_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/>
    98. JDBC>
    99. <JDBC name="ExceptionDatabase" tableName="sys_log_exception">
    100. <ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/>
    101. <Filters>
    102. <ThresholdFilter level="EXCEPTION_LOG"/>
    103. <ThresholdFilter level="CUSTOM_LOG" onMatch="DENY" onMismatch="NEUTRAL"/>
    104. Filters>
    105. <Column name="user_id" pattern="%X{user_id}"/>
    106. <Column name="user_name" pattern="%X{user_name}"/>
    107. <Column name="request_mode" pattern="%X{request_mode}"/>
    108. <Column name="absolute_uri" pattern="%X{absolute_uri}"/>
    109. <Column name="form_data" pattern="%X{form_data}"/>
    110. <Column name="source" pattern="%X{source}"/>
    111. <Column name="message" pattern="%X{message}"/>
    112. <Column name="exception_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/>
    113. JDBC>
    114. Appenders>
    115. <Loggers>
    116. <Root level="DEBUG">
    117. <AppenderRef ref="Console" level="INFO"/>
    118. <AppenderRef ref="ErrorAppender" level="ERROR"/>
    119. <AppenderRef ref="WarnAppender" level="WARN"/>
    120. <AppenderRef ref="InfoAppender" level="INFO"/>
    121. <AppenderRef ref="DebugAppender" level="DEBUG"/>
    122. <AppenderRef ref="LoginDatabase" level="LOGIN_LOG"/>
    123. <AppenderRef ref="OperationDatabase" level="OPERATION_LOG"/>
    124. <AppenderRef ref="ExceptionDatabase" level="EXCEPTION_LOG"/>
    125. Root>
    126. <Logger name="org.apache.logging.log4j" level="DEBUG" additivity="false">
    127. <AppenderRef ref="Console"/>
    128. Logger>
    129. <Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR">
    130. Logger>
    131. <Logger name="org.apache.catalina.util.LifecycleBase" level="ERROR">
    132. Logger>
    133. <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN">
    134. Logger>
    135. <Logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN">
    136. Logger>
    137. <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN">
    138. Logger>
    139. <Logger name="org.crsh.plugin" level="WARN">
    140. Logger>
    141. <Logger name="org.crsh.ssh" level="WARN">
    142. Logger>
    143. <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR">
    144. Logger>
    145. <Logger name="org.hibernate.validator.internal.util.Version" level="WARN">
    146. Logger>
    147. <Logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="WARN">
    148. Logger>
    149. <Logger name="org.springframework.boot.actuate.endpoint.jmx" level="WARN">
    150. Logger>
    151. <Logger name="org.thymeleaf" level="WARN">
    152. Logger>
    153. Loggers>
    154. Configuration>

    3、CustomDataSourceProperties

    1. import lombok.Data;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. import org.springframework.context.annotation.Scope;
    4. import org.springframework.stereotype.Component;
    5. @Data
    6. @Component
    7. @ConfigurationProperties(prefix = "spring.datasource")
    8. @Scope("singleton")
    9. public class CustomDataSourceProperties {
    10. public String url;
    11. public String username;
    12. public String password;
    13. public String driverClassName;
    14. }

    4、ConfigReader

    1. import com.cj.blog.model.base.CustomDataSourceProperties;
    2. import com.fasterxml.jackson.core.type.TypeReference;
    3. import com.fasterxml.jackson.databind.ObjectMapper;
    4. import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
    5. import org.apache.logging.log4j.LogManager;
    6. import org.apache.logging.log4j.Logger;
    7. import java.io.InputStream;
    8. import java.util.Map;
    9. /**
    10. * 用于从YAML文件中读取配置属性的实用工具类。
    11. */
    12. public class ConfigReader {
    13. private static final Logger logger = LogManager.getLogger(ConfigReader.class);
    14. /**
    15. * 从YAML配置文件中获取数据源属性。
    16. *
    17. * @return CustomDataSourceProperties对象,包含数据源属性,如果未找到属性则返回null。
    18. */
    19. public static CustomDataSourceProperties getDataSourceProperties() {
    20. String defaultActive = "dev"; // 默认活动配置
    21. String defaultConfigFile = "/application.yml"; // 默认配置文件名
    22. // 从默认配置文件中获取Spring属性
    23. Map properties = getSpringProperties(defaultActive, defaultConfigFile);
    24. if (properties != null) {
    25. Map springProperties = (Map) properties.get("spring");
    26. if (springProperties != null) {
    27. Map datasourceProperties = (Map) springProperties.get("datasource");
    28. if (datasourceProperties != null) {
    29. CustomDataSourceProperties dataSourceProperties = new CustomDataSourceProperties();
    30. dataSourceProperties.setUrl((String) datasourceProperties.get("url"));
    31. dataSourceProperties.setUsername((String) datasourceProperties.get("username"));
    32. dataSourceProperties.setPassword((String) datasourceProperties.get("password"));
    33. dataSourceProperties.setDriverClassName((String) datasourceProperties.get("driver-class-name"));
    34. logger.info("获取数据源属性成功!");
    35. logger.info("数据源属性:" + dataSourceProperties);
    36. return dataSourceProperties;
    37. }
    38. }
    39. }
    40. return null;
    41. }
    42. /**
    43. * 从指定的配置文件中获取Spring属性。
    44. *
    45. * @param active 要使用的活动配置。
    46. * @param configFile 配置文件的路径。
    47. * @return 包含Spring属性的Map,如果未找到属性则返回null。
    48. */
    49. private static Map getSpringProperties(String active, String configFile) {
    50. // 读取配置文件
    51. Map data = readConfigFile(configFile);
    52. if (data != null) {
    53. Map springProperties = (Map) data.get("spring");
    54. if (springProperties != null) {
    55. Map applicationProperties = (Map) springProperties.get("profiles");
    56. if (applicationProperties != null) {
    57. active = (String) applicationProperties.get("active");
    58. System.out.println("spring.application.active: " + active);
    59. }
    60. }
    61. }
    62. logger.info("spring.application.active: " + active);
    63. // 读取活动配置的配置文件
    64. return readConfigFile("/application-" + active + ".yml");
    65. }
    66. /**
    67. * 读取指定的YAML配置文件并将其解析为Map。
    68. *
    69. * @param fileName YAML文件的路径。
    70. * @return 包含解析后的YAML数据的Map,如果未找到文件或无法解析则返回null。
    71. */
    72. private static Map readConfigFile(String fileName) {
    73. try {
    74. // 创建用于解析YAML文件的ObjectMapper对象
    75. ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
    76. // 使用类加载器加载YAML文件
    77. InputStream inputStream = ConfigReader.class.getResourceAsStream(fileName);
    78. // 读取YAML文件并解析为Map对象
    79. return mapper.readValue(inputStream, new TypeReference>() {
    80. });
    81. } catch (Exception e) {
    82. e.printStackTrace();
    83. return null;
    84. }
    85. }
    86. }

    5、ConnectionFactory 连接工厂类,用于管理数据库连接

    1. import com.cj.blog.common.utils.ConfigReader;
    2. import com.cj.blog.model.base.CustomDataSourceProperties;
    3. import org.apache.commons.dbcp.DriverManagerConnectionFactory;
    4. import org.apache.commons.dbcp.PoolableConnection;
    5. import org.apache.commons.dbcp.PoolableConnectionFactory;
    6. import org.apache.commons.dbcp.PoolingDataSource;
    7. import org.apache.commons.pool.impl.GenericObjectPool;
    8. import org.springframework.stereotype.Component;
    9. import javax.sql.DataSource;
    10. import java.sql.Connection;
    11. import java.sql.SQLException;
    12. import java.util.Properties;
    13. /**
    14. * 连接工厂类,用于管理数据库连接。通过Apache Commons DBCP提供数据库连接池功能。
    15. */
    16. @Component
    17. public class ConnectionFactory {
    18. // 数据源,用于获取数据库连接
    19. private final DataSource dataSource;
    20. /**
    21. * 构造函数,初始化数据库连接池配置,并创建数据源。
    22. *
    23. * @param dataSourceProperties 数据源属性对象,包含数据库连接信息
    24. */
    25. public ConnectionFactory(CustomDataSourceProperties dataSourceProperties) {
    26. // 初始化数据库连接池配置
    27. Properties properties = new Properties();
    28. properties.setProperty("user", dataSourceProperties.getUsername());
    29. properties.setProperty("password", dataSourceProperties.getPassword());
    30. // 创建基于DriverManager的连接工厂和连接池
    31. GenericObjectPool pool = new GenericObjectPool<>();
    32. pool.setMaxActive(10); // 设置最大连接数
    33. pool.setMinIdle(2); // 设置最小空闲连接数
    34. DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(dataSourceProperties.getUrl(), properties);
    35. // 创建可池化的数据库连接工厂
    36. new PoolableConnectionFactory(connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED);
    37. // 创建数据源
    38. this.dataSource = new PoolingDataSource(pool);
    39. }
    40. /**
    41. * 获取数据库连接。
    42. *
    43. * @return 数据库连接
    44. * @throws SQLException 如果获取连接时发生错误
    45. */
    46. public static Connection getDatabaseConnection() throws SQLException {
    47. return Singleton.INSTANCE.dataSource.getConnection();
    48. }
    49. /**
    50. * 单例类,确保只有一个连接工厂实例。
    51. */
    52. private static class Singleton {
    53. static ConnectionFactory INSTANCE;
    54. static {
    55. // TODO 这里需要修改,在创建数据库连接工厂时,需要从配置文件中读取数据源属性
    56. // 从配置文件中读取数据源属性,并创建连接工厂实例
    57. CustomDataSourceProperties dataSourceProperties = ConfigReader.getDataSourceProperties();
    58. dataSourceProperties.setDriverClassName(dataSourceProperties.getDriverClassName());
    59. dataSourceProperties.setUrl(dataSourceProperties.getUrl());
    60. dataSourceProperties.setUsername(dataSourceProperties.getUsername());
    61. dataSourceProperties.setPassword(dataSourceProperties.getPassword());
    62. INSTANCE = new ConnectionFactory(dataSourceProperties);
    63. }
    64. }
    65. }

    三、进行简单测试配置

    1、LogUtils

    1. import org.apache.logging.log4j.ThreadContext;
    2. /**
    3. * 日志工具类
    4. */
    5. public class LogUtils {
    6. /**
    7. * 清除登录日志上下文信息,以便在日志记录操作结束后不保留上下文。
    8. */
    9. public static void clearLogContext() {
    10. ThreadContext.clearMap();
    11. }
    12. }

    2、LoginUserInfoHelper

    1. /**
    2. * 用于存储和获取用户信息的线程局部存储辅助类。
    3. */
    4. public class LoginUserInfoHelper {
    5. // 使用 ThreadLocal 来存储用户ID和用户名
    6. private static final ThreadLocal userId = new ThreadLocal<>();
    7. private static final ThreadLocal userName = new ThreadLocal<>();
    8. /**
    9. * 获取当前线程的用户ID。
    10. *
    11. * @return 用户ID
    12. */
    13. public static Long getUserId() {
    14. return userId.get();
    15. }
    16. /**
    17. * 设置当前线程的用户ID。
    18. *
    19. * @param _userId 用户ID
    20. */
    21. public static void setUserId(Long _userId) {
    22. userId.set(_userId);
    23. }
    24. /**
    25. * 从当前线程中移除用户ID。
    26. */
    27. public static void removeUserId() {
    28. userId.remove();
    29. }
    30. /**
    31. * 获取当前线程的用户名。
    32. *
    33. * @return 用户名
    34. */
    35. public static String getUsername() {
    36. return userName.get();
    37. }
    38. /**
    39. * 设置当前线程的用户名。
    40. *
    41. * @param _username 用户名
    42. */
    43. public static void setUsername(String _username) {
    44. userName.set(_username);
    45. }
    46. /**
    47. * 从当前线程中移除用户名。
    48. */
    49. public static void removeUsername() {
    50. userName.remove();
    51. }
    52. }

    3、LoginLogUtils

    1. import com.cj.blog.common.utils.RequestUtils;
    2. import com.cj.blog.model.auth.LoginUserInfoHelper;
    3. import org.apache.logging.log4j.Level;
    4. import org.apache.logging.log4j.LogManager;
    5. import org.apache.logging.log4j.Logger;
    6. import org.apache.logging.log4j.ThreadContext;
    7. import javax.servlet.http.HttpServletRequest;
    8. import java.util.Objects;
    9. import java.util.Optional;
    10. /**
    11. * 登录日志工具类。
    12. */
    13. public class LoginLogUtils {
    14. private static final Logger logger = LogManager.getLogger(LoginLogUtils.class);
    15. /**
    16. * 设置用户登录日志,根据HttpServletRequest设置日志上下文信息。
    17. *
    18. * @param request 请求对象,用于获取远程地址和用户代理信息。
    19. * @param remarks 登录日志的备注信息。
    20. */
    21. public static void setLogLogin(HttpServletRequest request, String remarks) {
    22. // 使用 LoginUserInfoHelper 中的用户信息设置登录日志
    23. setLogLogin(
    24. LoginUserInfoHelper.getUserId().toString(),
    25. LoginUserInfoHelper.getUsername(),
    26. request.getRemoteAddr(),
    27. RequestUtils.getUserAgent(request),
    28. remarks
    29. );
    30. }
    31. /**
    32. * 设置用户登录日志,根据指定的客户端IP、设备信息和备注信息。
    33. *
    34. * @param clientIp 客户端IP地址。
    35. * @param deviceInfo 设备信息。
    36. * @param remarks 登录日志的备注信息。
    37. */
    38. public static void setLogLogin(String clientIp, String deviceInfo, String remarks) {
    39. // 使用 LoginUserInfoHelper 中的用户信息设置登录日志
    40. setLogLogin(
    41. LoginUserInfoHelper.getUserId().toString(),
    42. LoginUserInfoHelper.getUsername(),
    43. clientIp,
    44. deviceInfo,
    45. remarks
    46. );
    47. }
    48. /**
    49. * 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。
    50. *
    51. * @param userId 用户ID。
    52. * @param userName 用户名。
    53. * @param clientIp 客户端IP地址。
    54. * @param deviceInfo 设备信息。
    55. * @param remarks 登录日志的备注信息。
    56. */
    57. public static void setLogLogin(String userId, String userName, String clientIp, String deviceInfo, String remarks) {
    58. logLogin(userId, userName, clientIp, deviceInfo, remarks);
    59. }
    60. /**
    61. * 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。
    62. *
    63. * @param userId 用户ID。
    64. * @param userName 用户名。
    65. * @param clientIp 客户端IP地址。
    66. * @param deviceInfo 设备信息。
    67. * @param remarks 登录日志的备注信息。
    68. */
    69. public static void logLogin(String userId, String userName, String clientIp, String deviceInfo, String remarks) {
    70. try {
    71. // 设置上下文信息,这些信息将与日志消息关联
    72. ThreadContext.put("user_id", Objects.toString(userId, "0"));
    73. ThreadContext.put("user_name", Objects.toString(userName, "0"));
    74. ThreadContext.put("client_ip", clientIp);
    75. ThreadContext.put("device_info", deviceInfo);
    76. ThreadContext.put("remarks", remarks);
    77. // 使用自定义的 Login 级别记录消息
    78. logger.log(Level.getLevel("LOGIN_LOG"), remarks);
    79. } finally {
    80. LogUtils.clearLogContext(); // 在try块之后清除上下文,确保上下文信息不泄漏
    81. }
    82. }
    83. /**
    84. * 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。
    85. *
    86. * @param clientIp 客户端IP地址。
    87. * @param deviceInfo 设备信息。
    88. * @param remarks 登录日志的备注信息。
    89. * @param userParams 用户ID、用户名。
    90. */
    91. public static void setLogLogin(String clientIp, String deviceInfo, String remarks, String... userParams) {
    92. String userId = Optional.ofNullable(userParams.length > 0 ? userParams[0] : null).orElse("0");
    93. String userName = Optional.ofNullable(userParams.length > 1 ? userParams[1] : null).orElse("0");
    94. try {
    95. // 设置上下文信息,这些信息将与日志消息关联
    96. ThreadContext.put("user_id", userId);
    97. ThreadContext.put("user_name", userName);
    98. ThreadContext.put("client_ip", clientIp);
    99. ThreadContext.put("device_info", deviceInfo);
    100. ThreadContext.put("remarks", remarks);
    101. // 使用自定义的 Login 级别记录消息
    102. logger.log(Level.getLevel("LOGIN_LOG"), remarks);
    103. } finally {
    104. LogUtils.clearLogContext(); // 在try块之后清除上下文,确保上下文信息不泄漏
    105. }
    106. }
    107. }

    4、写日志

    1. LoginLogUtils.setLogLogin(
    2. customUser.getSysUser().getUserId().toString(),
    3. customUser.getUsername(),
    4. request.getRemoteAddr(),
    5. RequestUtils.getUserAgent(request),
    6. "登录成功"
    7. );
  • 相关阅读:
    Android开发跳槽面试,向大厂更进一步
    【TypeScript】带类型语法的JavaScript
    彻底解决 IDC Incast
    git常见命令和操作
    postman在ubuntu下报gpu-disable
    动态dp
    初识html
    安装MySQL8.0及以上版本操作步骤
    方案展示 | RK3588开发板Linux双摄同显方案
    生态系统服务—土壤侵蚀强度空间分布/降雨侵蚀力
  • 原文地址:https://blog.csdn.net/weixin_46759354/article/details/134439260