springboot2.7.x 集成log4j2配置写入日志到mysql自定义表格
在阅读之前请先查看【springboot集成log4j2】
本文暂不考虑抽象等实现方式,只限于展示如何自定义配置log4j2并写入mysql数据库(自定义结构)
先看下log4j2的配置
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30"> <!-- 配置日志文件输出目录,此配置将日志输出到tomcat根目录下的指定文件夹 -->
<properties>
<property name="LOG_HOME">./logs/demo/log4j2/</property>
</properties>
<!--先定义所有的appender-->
<appenders> <!-- 优先级从高到低分别是 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL -->
<!-- 单词解释: Match:匹配 DENY:拒绝 Mismatch:不匹配 ACCEPT:接受 -->
<!-- DENY,日志将立即被抛弃,不再过其他过滤器; NEUTRAL,有序列表里的下个过滤器过接着处理日志; ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
<!--输出日志的格式 %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间 %p : 日志输出格式 %c : logger的名称 %m : 日志内容,即 logger.info("message") %n : 换行符 %C : Java类名 %L : 日志输出所在行数 %M : 日志输出所在方法名 hostName : 本地机器名 hostAddress : 本地ip地址 -->
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{1.} - %m%n"/>
<!--<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - (%F:%l) - %m%n"/>-->
<!--<PatternLayout pattern="[%d{HH:mm:ss:SSS}] (%F:%L) %m%n" />-->
</console>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<!-- TRACE级别日志 ; 设置日志格式并配置日志压缩格式,压缩文件独立放在一个文件夹内, 日期格式不能为冒号,否则无法生成,因为文件名不允许有冒号,此appender只输出trace级别的数据到trace.log -->
<RollingFile name="RollingFileTrace" immediateFlush="true" fileName="${LOG_HOME}/trace.log"
filePattern="${LOG_HOME}/trace_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20">
<!--这里的age必须和filePattern协调, 后者是精确到HH, 这里就要写成xH, xd就不起作用 另外, 数字最好>2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!-->
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="trace_*.zip"/>
<!-- 保存时间与filePattern相同即可 -->
<!-- 如果filePattern为:yyyy-MM-dd-HH:mm:ss, age也可以为5s,表示日志存活时间为5s -->
<IfLastModified age="168H"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<RollingFile name="RollingFileDebug" immediateFlush="true" fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/debug_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="debug_*.zip"/>
<IfLastModified age="168H"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> <!-- info日志配置 -->
<RollingFile name="RollingFileInfo" immediateFlush="true"
fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/info_%d{yyyy-MM-dd-HH}-%i.log.zip"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter
level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout
pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="info_*.zip"/>
<IfLastModified age="168H"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> <!-- warn日志配置 -->
<RollingFile name="RollingFileWarn"
immediateFlush="true"
fileName="${LOG_HOME}/warn.log" filePattern="${LOG_HOME}/warn_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter
level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout
pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="warn_*.zip"/>
<IfLastModified age="168H"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> <!-- error日志配置 -->
<RollingFile
name="RollingFileError" immediateFlush="true"
fileName="${LOG_HOME}/error.log" filePattern="${LOG_HOME}/error_%d{yyyy-MM-dd-HH}-%i.log.zip">
<ThresholdFilter
level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout
pattern="[%d{HH:mm:ss:SSS}] - [%t] [%p] - %logger{36} - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="error_*.zip"/>
<IfLastModified age="168H"/>
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<JDBC name="db" tableName="logs">
<!-- 已经改造代码配置 -->
<!-- <DriverManager-->
<!-- connectionString="jdbc:mysql://localhost:3306/log4j2"-->
<!-- userName="root"-->
<!-- password="password"-->
<!-- driverClassName="com.mysql.cj.jdbc.Driver"-->
<!-- />-->
<ConnectionFactory
class="nirvana.core.logger.LogConnectionFactory" method="getConnection"/>
<ColumnMapping name="ID" pattern="%u"/>
<ColumnMapping name="TRACE_ID"/>
<ColumnMapping name="DATE_TIME"/>
<ColumnMapping name="CLASS"/>
<ColumnMapping name="LEVEL"/>
<ColumnMapping name="MESSAGE"/>
<ColumnMapping name="EXCEPTION"/>
<ColumnMapping name="IP"/>
<MessageLayout/>
</JDBC>
</appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="INFO"/>
<logger name="org.mybatis" level="INFO"/>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileDebug"/>
<appender-ref ref="RollingFileTrace"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
<logger name="demo.mvc" level="warn">
<appender-ref ref="db"/>
</logger>
</loggers>
</configuration>
注意看如下这段
<ConnectionFactory
class="nirvana.core.logger.LogConnectionFactory" method="getConnection"/>
<ColumnMapping name="ID" pattern="%u"/>
<ColumnMapping name="TRACE_ID"/>
<ColumnMapping name="DATE_TIME"/>
<ColumnMapping name="CLASS"/>
<ColumnMapping name="LEVEL"/>
<ColumnMapping name="MESSAGE"/>
<ColumnMapping name="EXCEPTION"/>
<ColumnMapping name="IP"/>
我们在此处并没有采用官方的jdbc配置方式而是采用了自己的nirvana.core.logger.LogConnectionFactory
nirvana.core.logger.LogConnectionFactory 的代码
package nirvana.core.logger;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
/**
* ConnectionFactory
*
* @author linkanyway
* @version 1.0
* @date 2022/05/26 20:05
*/
public class LogConnectionFactory {
private DataSource dataSource;
private static String PRE_FIX = "spring.datasource.";
/**
* constructor
*/
private LogConnectionFactory() {
ClassPathResource ymlResource = new ClassPathResource ("application.yml");
ClassPathResource propertyResource = new ClassPathResource ("application.properties");
ClassPathResource resource;
if (!(resource = new ClassPathResource ("application.yml")).exists ()) {
resource = new ClassPathResource ("application.properties");
if (!resource.exists ()) {
throw new RuntimeException ("no application configuration file found");
}
}
Yaml yaml = new Yaml ();
YamlPropertySourceLoader loader = new YamlPropertySourceLoader ();
try {
List<PropertySource<?>> list = loader.load ("log-datasource", resource);
PropertySource<?> prop = list.get (0);
Properties props = new Properties ();
props.setProperty ("druid.url", Objects.requireNonNull (prop.getProperty (PRE_FIX + "url")).toString ());
props.setProperty ("druid.username",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "username")).toString ());
props.setProperty ("druid.password",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "password")).toString ());
props.setProperty ("druid.driverClassName",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "driver" + "-class-name")).toString ());
this.dataSource = new DruidDataSource ();
((DruidDataSource) this.dataSource).configFromPropety (props);
} catch (IOException e) {
throw new RuntimeException (e);
}
}
/**
* get connection
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection ();
}
/**
* only used inner
*/
private interface Singleton {
LogConnectionFactory INSTANCE = new LogConnectionFactory ();
}
}
此处只是粗糙的码了一个读取properties和yaml文件的类,结合配置文件内制定的factory即实现了log4j2读取mysql connection的目地
建立自定义的表结构
database sql
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80023
Source Host : localhost:3306
Source Schema : log4j2
Target Server Type : MySQL
Target Server Version : 80023
File Encoding : 65001
Date: 27/05/2022 11:35:19
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for logs
-- ----------------------------
DROP TABLE IF EXISTS `logs`;
CREATE TABLE `logs` (
`ID` varchar(50) NOT NULL,
`TRACE_ID` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`DATE_TIME` timestamp NULL DEFAULT NULL,
`CLASS` varchar(100) DEFAULT NULL,
`LEVEL` varchar(10) DEFAULT NULL,
`MESSAGE` text,
`EXCEPTION` text,
`IP` text,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1;
这个表结构是对应的配置文件中的如下内容
<ColumnMapping name="ID" pattern="%u"/>
<ColumnMapping name="TRACE_ID"/>
<ColumnMapping name="DATE_TIME"/>
<ColumnMapping name="CLASS"/>
<ColumnMapping name="LEVEL"/>
<ColumnMapping name="MESSAGE"/>
<ColumnMapping name="EXCEPTION"/>
<ColumnMapping name="IP"/>
如果想知道表结构的列是如何和日志对上的接着往下看,
核心调用类LoggerManager
package nirvana.core.logger;
import nirvana.core.context.WebContext;
import nirvana.core.utils.NetUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.net.TcpSocketManager;
import org.apache.logging.log4j.message.StringMapMessage;
import org.apache.logging.log4j.util.Strings;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* LogManager
*
* @author linkanyway
* @version 1.0
* @date 2022/05/27 11:06
*/
public class LoggerManager {
// database sql
// /*
// Navicat Premium Data Transfer
//
// Source Server : localhost
// Source Server Type : MySQL
// Source Server Version : 80023
// Source Host : localhost:3306
// Source Schema : log4j2
//
// Target Server Type : MySQL
// Target Server Version : 80023
// File Encoding : 65001
//
// Date: 27/05/2022 11:35:19
//*/
//
// SET NAMES utf8mb4;
// SET FOREIGN_KEY_CHECKS = 0;
//
//-- ----------------------------
// -- Table structure for logs
//-- ----------------------------
// DROP TABLE IF EXISTS `logs`;
// CREATE TABLE `logs` (
// `ID` varchar(50) NOT NULL,
// `TRACE_ID` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
// `DATE_TIME` timestamp NULL DEFAULT NULL,
// `CLASS` varchar(100) DEFAULT NULL,
// `LEVEL` varchar(10) DEFAULT NULL,
// `MESSAGE` text,
// `EXCEPTION` text,
// `IP` text,
// PRIMARY KEY (`ID`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
//
// SET FOREIGN_KEY_CHECKS = 1;
/**
* todo: use normal class
* logger
*/
public Logger logger;
/**
* get logger
*
* @param loggerName
* @return
*/
public static LoggerManager getLogger(String loggerName) {
return new LoggerManager (loggerName);
}
private LoggerManager(String loggerName) {
this.logger = LogManager.getLogger (loggerName);
}
/**
* error
*
* @param message error message
* @param ex exception instance
*/
public void error(String message, @NotNull Exception ex) {
StringMapMessage msg = generate (message, "error", ex);
logger.error (msg);
}
/**
* info
*
* @param message info message
*/
public void info(String message) {
StringMapMessage msg = generate (message, "info");
logger.info (msg);
}
/**
* trace
*
* @param message trace message
*/
public void trace(String message) {
StringMapMessage msg = generate (message, "info");
logger.trace (msg);
}
/**
* debug
*
* @param message debug message
*/
public void debug(String message) {
StringMapMessage msg = generate (message, "debug");
logger.debug (msg);
}
/**
* warn
*
* @param message warn message
*/
public void warn(String message) {
StringMapMessage msg = generate (message, "warn");
logger.warn (msg);
}
/**
* generate map message
*
* @param message message content
* @param level level
* @return StringMapMessage instance
*/
private StringMapMessage generate(String message, String level) {
return generate (message, level, null);
}
/**
* get caller information
*
* @return caller class name
*/
private String getCallerName() {
StackTraceElement[] traces = Thread.currentThread ().getStackTrace ();
for (Integer i = 1; i < traces.length - 1; i++) {
StackTraceElement element = traces[i];
if (element.getClassName ().indexOf (this.getClass ().getPackageName ()) != 0) {
return element.getClassName ();
}
}
return Strings.EMPTY;
}
/**
* generate message
*
* @param message message content
* @param level level
* @param ex exception
* @return StringMapMessage instance
*/
private StringMapMessage generate(String message, String level, Exception ex) {
String sourceClassName = getCallerName ();
StringMapMessage msg = new StringMapMessage ();
if (ex != null) {
// have to deal with the exception to prevent throw again
try {
StringWriter sw = new StringWriter ();
PrintWriter pw = new PrintWriter (sw, true);
ex.printStackTrace (pw);
msg.put ("EXCEPTION", sw.getBuffer ().toString ());
} catch (Exception e) {
throw e;
}
} else {
msg.put ("EXCEPTION", "");
}
msg.put ("LEVEL", level);
msg.put ("ID", UUID.randomUUID ().toString ());
msg.put ("TRACE_ID", WebContext.getRequestId ());
msg.put ("DATE_TIME", LocalDateTime.now ().toString ());
msg.put ("CLASS", sourceClassName);
msg.put ("MESSAGE", message);
msg.put ("IP", NetUtils.getRemoteIp ());
return msg;
}
}
注意代码中这段
msg.put ("LEVEL", level);
msg.put ("ID", UUID.randomUUID ().toString ());
msg.put ("TRACE_ID", WebContext.getRequestId ());
msg.put ("DATE_TIME", LocalDateTime.now ().toString ());
msg.put ("CLASS", sourceClassName);
msg.put ("MESSAGE", message);
msg.put ("IP", NetUtils.getRemoteIp ());
其实我们只是使用了StringMapMessage实现了自定义消息结构
而如下代码则是为了获取调用类,此处只是利用调用堆栈寻取了第一个和当前日志记录类LoggerManagger的package不同包的第一个类,实际对于调用堆栈获取需要的caller还是需要自己按照自己的方式寻找
/**
* get caller information
*
* @return caller class name
*/
private String getCallerName() {
StackTraceElement[] traces = Thread.currentThread ().getStackTrace ();
for (Integer i = 1; i < traces.length - 1; i++) {
StackTraceElement element = traces[i];
if (element.getClassName ().indexOf (this.getClass ().getPackageName ()) != 0) {
return element.getClassName ();
}
}
return Strings.EMPTY;
}
springboot2.7.x 集成log4j2配置写入日志到mysql自定义表格的更多相关文章
- 使用Slf4j集成Log4j2构建项目日志系统的完美解决方案
一.背景 最近因为公司项目性能需要,我们考虑把以前基于的log4j的日志系统重构成基于Slf4j和log4j2的日志系统,因为,使用slf4j可以很好的保证我们的日志系统具有良好的兼容性,兼容当前常见 ...
- log4j2配置按照日志级别将日志输出到不同的文件
背景 在项目中,可能会产生非常多的日志记录,为了方便日志分析,可以将日志按级别输出到指定文件. log4j2.xml配置文件 <!--将info级别的日志单独输出到info.log中--> ...
- springboot集成log4j2 + logstash 异步输出日志
一. spring boot 集成log4j2 1.maven引入jar包 <dependency> <groupId>org.springframework.boot< ...
- 小白的springboot之路(十二)、集成log4j2日志
0.前言 日志记录对系统来说必不可少,spring boot中常用的日志组件有log4j.logback.log4j2,其中logback是spring boot默认的,已自带:选用log4j2就可以 ...
- spring boot:配置druid数据库连接池(开启sql防火墙/使用log4j2做异步日志/spring boot 2.3.2)
一,druid数据库连接池的功能? 1,Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 2,druid的官方站: http ...
- log4j2配置MDC分线程写日志
1.MDC是一个高级一些的工具,可以配置分用户(userid)写日志,也可以分线程 2.方法和道理都是相似的,在写入日志之前配置线程名或者用户id 3.如果将线程名配置为目录,可以将不同线程的日志输送 ...
- log4j2配置ThresholdFilter,让info文件记录error日志
日志级别: 是按严重(重要)程度来分的(如下6种): ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < ...
- NetCore log4net 集成以及配置日志信息不重复显示或者记录
NetCore log4net 集成,这是一个很常见而且网上大批大批的博文了,我写这个博文主要是为了记录我在使用过程中的一点小收获,以前在使用的过程中一直没有注意但是其实网上说的不清不楚的问题. 官方 ...
- Log4j,Log4j2,logback,slf4j日志学习
日志学习笔记 Log4j Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.数据库等:我们也可以控制每一条日志的输出格式:通过定义每一条 ...
随机推荐
- 电机三环pid控制及调试经验
一.伺服电机的双环pid 双环pid在正常底盘运动的控制中已经足够了,但是对于双轴云台的控制来说,双环pid的云台控制的响应速度是远远不够的,所以加入了电流环的控制. 两篇大佬的文章--这是我学习pi ...
- 使用基于Roslyn的编译时AOP框架来解决.NET项目的代码复用问题
理想的代码优化方式 团队日常协作中,自然而然的会出现很多重复代码,根据这些代码的种类,之前可能会以以下方式处理 方式 描述 应用时可能产生的问题 硬编码 多数新手,或逐渐腐坏的项目会这么干,会直接复制 ...
- asp.net core启动源码以及监听,到处理请求响应的过程
摘要 asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过:接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可 ...
- Golang 泛型的简单使用
go 学习泛型,利用泛型编写对数据集合执行操作的方法.
- 2021.12.06 P1450 [HAOI2008]硬币购物(组合数学+抽屉原理+DP)
2021.12.06 P1450 [HAOI2008]硬币购物(组合数学+抽屉原理+DP) https://www.luogu.com.cn/problem/P1450 题意: 共有 44 种硬币.面 ...
- Go 语言接口及使用接口实现链表插入
@ 目录 1. 接口定义 1.1 空接口 1.2 实现单一接口 1.3 接口多方法实现 2. 多态 2.1 为不同数据类型的实体提供统一的接口 2.2 多接口的实现 3. 系统接口调用 4. 接口嵌套 ...
- MyBatisPlus实现分页和查询操作就这么简单
<SpringBoot整合MybatisPlus基本的增删改查,保姆级教程>在这篇文章中,我们详细介绍了分页的具体实现方法.但是,在日常的开发中还需要搜索功能的.下面让我们一起动起手来,实 ...
- crontab和cron表达式详解
引言 我们在定时任务中经常能接触到cron表达式,但是在写cron表达式的时候我们会遇到各种各样版本的cron表达式,比如我遇到过5位.6位甚至7位的cron表达式,导致我一度搞混这些表达式.更严重的 ...
- Android 解析包时出现问题 的解决方案(应用检查更新)
问题描述我们在进行Android开发的时候,一般都会在应用里检测有没有更新,并且从网上下载最新的版本包,覆盖本地的旧版本.在我的项目中,出现了一个问题,就是当安装包下载到本地的时候,产生了" ...
- FinClip 前端之 VUE 核心原理总结
小程序框架有很多,都是支持前端JavaScript语言的,也是支持 vue.js 框架的.FinClip 小程序是兼容各家平台的.所以在学习了框架使用之后的进阶就要熟悉框架的底层原理. 1.数据响应式 ...