Spring Boot - 利用MDC(Mapped Diagnostic Context)实现轻量级同步/异步日志追踪

文章目录

  • Pre
  • 什么是MDC(Mapped Diagnostic Context)
  • Slf4j 和 MDC
  • 基础工程
    • 工程结构
    • POM
    • logback-spring.xml
    • application.yml
    • 同步方式
      • 方式一: 拦截器
        • 自定义日志拦截器
        • 添加拦截器
      • 方式二: 自定义注解 + AOP
        • 自定义注解 TraceLog
        • 切面
      • 测试
    • 异步支持
      • 线程池配置类
      • 自定义线程池任务执行器
      • 线程MDC工具类
      • 模拟业务类@Async
      • 测试
  • 小结

在这里插入图片描述

Pre

每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal


什么是MDC(Mapped Diagnostic Context)

MDC(Mapped Diagnostic Context)是一个在日志框架中常用的概念,主要用于在多线程环境中关联和传递一些上下文信息,以便在日志输出中包含这些信息,从而实现更好的日志记录和调试。

在Java中,常见的日志框架如Log4j、Logback和Log4j2都提供了对MDC的支持。

MDC的主要特点包括:

  1. 线程绑定的上下文信息: MDC允许在多线程环境中将上下文信息与线程相关联。可以在应用程序的不同部分设置一些上下文信息,并确保在同一线程中的后续日志记录中能够访问到这些信息。

  2. 适用于跟踪请求或会话: MDC特别适用于跟踪请求或会话相关的信息,如请求ID、会话ID等。通过在请求开始时设置这些信息,并在请求结束时清理它们,可以确保在整个请求处理过程中,日志都包含了相同的上下文信息,方便排查问题。

  3. 日志格式化支持: MDC的值可以通过特殊的占位符在日志输出格式中引用。这样,在日志输出时,可以直接将MDC中的值包含在日志中,从而让日志更具可读性和可跟踪性。

  4. 避免参数传递的复杂性: 使用MDC可以避免在方法调用链中手动传递上下文信息的复杂性。相反,可以在适当的地方将信息设置到MDC中,在日志输出时框架会自动将这些信息包含在日志中。

简而言之,MDC是一个非常有用的工具,可以帮助开发人员在日志中记录和跟踪关键的上下文信息,提高了调试和排查问题的效率。


Slf4j 和 MDC

在这里插入图片描述

**SLF4J(Simple Logging Facade for Java)**是一个日志门面框架,它提供了一种简单的方式来访问各种日志系统,例如Log4j、Logback、java.util.logging等。SLF4J本身并不是一个日志实现,而是提供了统一的接口,开发人员可以通过它来编写日志代码,而不用关心底层日志系统的具体实现。

**MDC(Mapped Diagnostic Context)**是SLF4J的一个功能,用于在日志输出中关联和传递上下文信息。MDC允许开发人员在代码中设置一些上下文信息,例如请求ID、用户ID等,然后在日志输出时将这些信息包含在日志中,以便于跟踪和调试。

SLF4J和MDC之间的关系可以总结如下:

  1. SLF4J提供了MDC的接口: SLF4J允许开发人员通过其API来使用MDC功能。它提供了一些方法,例如MDC.put(key, value)用于设置上下文信息,MDC.get(key)用于获取上下文信息等。

  2. MDC依赖于底层的日志实现: 虽然MDC是SLF4J提供的功能,但其实现是依赖于底层的日志实现的。不同的日志实现,如Logback、Log4j等,都有自己的MDC实现。因此,开发人员需要确保在使用MDC时,底层的日志实现已经正确配置。

  3. MDC提供了与SLF4J日志框架的集成: MDC的设计目的之一是与SLF4J的日志框架集成得很好。这意味着开发人员可以在使用SLF4J编写的日志代码中,轻松地使用MDC功能,从而在日志中记录和跟踪上下文信息。

SLF4J和MDC是紧密相关的,MDC是SLF4J的一个功能,用于在日志输出中传递上下文信息,而SLF4J提供了使用MDC功能的接口。这使得在使用SLF4J编写的日志代码中,可以方便地利用MDC来增强日志的可读性和可追踪性。


基础工程

工程结构

在这里插入图片描述


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><artifactId>boot2</artifactId><groupId>com.artisan</groupId><version>0.0.1-SNAPSHOT</version></parent><groupId>com.artisan</groupId><artifactId>boot-trace</artifactId><version>0.0.1-SNAPSHOT</version><name>boot-trace</name><description>boot-trace</description><properties><java.version>8</java.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-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></dependency><!--lombok配置--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- ULID--><dependency><groupId>com.github.f4b6a3</groupId><artifactId>ulid-creator</artifactId><version>5.1.0</version></dependency><dependency><groupId>de.huxhorn.sulky</groupId><artifactId>de.huxhorn.sulky.ulid</artifactId><version>8.3.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

logback-spring.xml

使用SLF4J门面是一个很好的实践,特别是在与logback等日志实现框架集成时。SLF4J提供了一个统一的接口,使得应用代码与具体的日志实现解耦,从而可以轻松地切换和替换底层的日志实现,而无需修改应用代码。

通过使用SLF4J门面,可以在应用程序中使用SLF4J的API编写日志代码,例如Logger接口中的方法,而不用关心底层的日志实现是logback、Log4j还是其他日志框架。这使得代码更具灵活性和可维护性,可以根据需要随时替换底层的日志实现,而不会影响应用程序的其他部分。

对于大多数Java项目来说,使用SLF4J门面和logback作为底层的日志实现是一个非常常见的选择。这样做不仅提供了对日志功能的灵活控制,还能够保持与标准的Java日志习惯的兼容性。同时,SLF4J和logback之间的集成也非常完善,可以充分发挥它们之间的协作优势。

这里我们使用logback ,其他日志组件均可无缝替换。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--日志存储路径--><property name="log" value="D:/log" /><!-- 控制台输出 --><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--输出格式化--><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}[%10method,%line] - %msg%n</pattern></encoder></appender><!-- 按天生成日志文件 --><appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件名--><FileNamePattern>${log}/%d{yyyy-MM-dd}.log</FileNamePattern><!--保留天数--><MaxHistory>30</MaxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><pattern>[%X{TRACE_ID}]  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>10MB</MaxFileSize></triggeringPolicy></appender><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="console" /><appender-ref ref="file" /></root>
</configuration>

application.yml

server:port: 9999
logging:config: classpath:logback-spring.xml

同步方式

方式一: 拦截器

自定义日志拦截器
package com.artisan.boottrace.interceptor;import com.artisan.boottrace.utils.TraceIdGenerator;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* <p>* MDC(Mapped Diagnostic Context)诊断上下文映射,是@Slf4j提供的一个支持动态打印日志信息的工具*/
public class TraceLogInterceptor implements HandlerInterceptor {// 追踪ID在MDC中的键名private static final String TRACE_ID = "TRACE_ID";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 生成追踪ID,如果客户端传入了追踪ID,则使用客户端传入的追踪ID,否则使用默认的ULID生成String tid = TraceIdGenerator.ulid();if (StringUtils.hasLength(request.getHeader(TRACE_ID))) {tid = request.getHeader(TRACE_ID);}// 将追踪ID放入MDC中MDC.put(TRACE_ID, tid);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) {// 请求处理完成后,从MDC中移除追踪IDMDC.remove(TRACE_ID);}
}

在请求处理前后设置和清理MDC中的追踪ID的请求追踪日志拦截器。

  • preHandle方法中,它从请求头中获取追踪ID,如果不存在则使用默认的ULID生成器生成一个新的追踪ID,并将其放入MDC中。
  • afterCompletion方法中,它简单地移除MDC中的追踪ID,以确保不影响后续请求的日志记录。

添加拦截器
package com.artisan.boottrace.interceptor;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {/*** 注册TraceLogInterceptor拦截器*/@Beanpublic TraceLogInterceptor logInterceptor() {return new TraceLogInterceptor();}/*** 添加拦截器到拦截器链*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor());// 可以具体指定哪些需要拦截,哪些不拦截,其实也可以使用自定义注解更灵活完成// .addPathPatterns("/**")// .excludePathPatterns("/xxx.yyy");}
}

这个配置类是WebMvcConfigurer接口的实现类,用于配置拦截器。它注册了TraceLogInterceptor拦截器,并将其添加到拦截器链中。

可以通过addInterceptors方法来指定哪些请求需要被拦截,哪些请求不需要被拦截。通过这种方式,可以灵活地控制拦截器的应用范围,以满足不同的业务需求.


方式二: 自定义注解 + AOP

自定义注解 TraceLog
package com.artisan.boottrace.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author artisan*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceLog {
}
切面
package com.artisan.boottrace.aspect;import com.artisan.boottrace.utils.TraceIdGenerator;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Aspect
@Component
public class TraceLogAspect {private static final String TRACE_ID = "TRACE_ID";@Before("@annotation(com.artisan.boottrace.annotations.TraceLog)")public void beforeMethodExecution() {String tid = TraceIdGenerator.ulid();MDC.put(TRACE_ID, tid);}@After("@annotation(com.artisan.boottrace.annotations.TraceLog)")public void afterMethodExecution() {MDC.remove(TRACE_ID);}
}

测试

拦截器方式

package com.artisan.boottrace.controller;import com.artisan.boottrace.service.IArtisanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j
@RestController
public class ArtisanTestController {@Autowiredprivate IArtisanService artisanService;@GetMapping("/testTrace")public String testTrace(@RequestParam("name") String name) throws InterruptedException {log.info("testTrace  name={}", name);doSomething1();// 异步任务// artisanService.addArtisan();log.info("testTrace Call Over name={}", name);return "Hello," + name;}private void doSomething1() {log.info("doSomething1  info  log");doSomething2();log.error("doSomething1 error log");}private void doSomething2() {log.info("doSomething2  info log");}
}

启动应用,访问:http://127.0.0.1:9999/testTrace?name=artisan

在这里插入图片描述


验证 AOP的方式

在这里插入图片描述

	@TraceLog@GetMapping("/testTrace2")public String testTraceByAnno(@RequestParam("name") String name) throws InterruptedException {log.info("======================");log.info("testTraceByAnno  name={}", name);doSomething1(); log.info("testTraceByAnno Call Over name={}", name);return "Hello," + name;}

在这里插入图片描述


异步支持

日常开发中,异步处理必不可少,我们来看看如何实现一个轻量的异步日志追踪吧 。

思路: 将父线程的trackId传递下去给子线程

线程池配置类

package com.artisan.boottrace.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;import java.util.concurrent.Executor;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* <p>* 线程池配置类,用于配置异步任务执行器*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {/*** 声明一个线程池** @return 执行器*/@Bean("ArtisanExecutor")public Executor asyncExecutor() {final int cpuSize = Runtime.getRuntime().availableProcessors();ArtisanThreadPoolTaskExecutor executor = new ArtisanThreadPoolTaskExecutor();executor.setCorePoolSize(cpuSize);executor.setMaxPoolSize(2 * cpuSize);executor.setQueueCapacity(500);executor.setKeepAliveSeconds(60);executor.setThreadNamePrefix("asyncArtisan-");executor.initialize();return executor;}
}

用于配置异步任务执行器,声明了一个名为"ArtisanExecutor"的Bean,返回一个自定义的ArtisanThreadPoolTaskExecutor执行器实例。在这个执行器中,配置了线程池的各种参数,如核心线程数、最大线程数、队列容量等。这样就创建了一个具有自定义配置的线程池执行器,用于执行异步任务。


自定义线程池任务执行器

package com.artisan.boottrace.config;import com.artisan.boottrace.utils.ThreadMdcUtil;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Callable;
import java.util.concurrent.Future;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* <p>* 自定义线程池任务执行器,用于在任务执行时传递父线程的MDC上下文信息到子线程中。*/
public class ArtisanThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {public ArtisanThreadPoolTaskExecutor() {super();}/*** 执行任务,传递父线程的MDC上下文信息到子线程中*/@Overridepublic void execute(Runnable task) {super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}/*** 提交带有返回值的任务,传递父线程的MDC上下文信息到子线程中*/@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}/*** 提交无返回值的任务,传递父线程的MDC上下文信息到子线程中*/@Overridepublic Future<?> submit(Runnable task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}
}

继承自SpringThreadPoolTaskExecutor,在执行任务时使用了ThreadMdcUtil工具类来传递父线程的MDC上下文信息到子线程中。通过这种方式,可以确保异步任务在执行过程中能够访问到父线程的MDC上下文信息,从而实现了日志的跟踪。


线程MDC工具类

package com.artisan.boottrace.utils;import org.slf4j.MDC;import java.util.Map;
import java.util.concurrent.Callable;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* <p>* 线程MDC工具类,用于在多线程环境中传递MDC上下文信息*/
public class ThreadMdcUtil {private static final String TRACE_ID = "TRACE_ID";/*** 生成唯一的追踪ID*/public static String generateTraceId() {return TraceIdGenerator.ulid();}/*** 如果当前MDC中不存在追踪ID,则设置追踪ID*/public static void setTraceIdIfAbsent() {if (MDC.get(TRACE_ID) == null) {MDC.put(TRACE_ID, generateTraceId());}}/*** 用于在父线程向线程池中提交任务时,将父线程的MDC上下文信息复制给子线程** @param callable 要执行的任务* @param context  父线程的MDC上下文信息* @param <T>      任务返回类型* @return 复制了父线程MDC上下文信息的任务*/public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {return callable.call();} finally {MDC.clear();}};}/*** 用于在父线程向线程池中提交任务时,将父线程的MDC上下文信息复制给子线程** @param runnable 要执行的任务* @param context  父线程的MDC上下文信息* @return 复制了父线程MDC上下文信息的任务*/public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}
}

在多线程环境中传递MDC上下文信息。提供了两个静态方法wrap,用于在父线程向线程池中提交任务时,将父线程的MDC上下文信息复制给子线程。这样可以确保在异步任务中也能够访问到父线程设置的MDC上下文信息,实现了日志的跟踪。


模拟业务类@Async

package com.artisan.boottrace.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
@Slf4j
@Service
public class ArtisanService implements IArtisanService {@Async("ArtisanExecutor")@Overridepublic void addArtisan() throws InterruptedException {TimeUnit.SECONDS.sleep(1);log.info("线程名称: {} ,执行方式:Async ---->  addArtisan ", Thread.currentThread().getName());}}

测试

package com.artisan.boottrace.controller;import com.artisan.boottrace.annotations.TraceLog;
import com.artisan.boottrace.service.IArtisanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j
@RestController
public class ArtisanTestController {@Autowiredprivate IArtisanService artisanService;@GetMapping("/testTrace")public String testTrace(@RequestParam("name") String name) throws InterruptedException {log.info("testTrace  name={}", name);doSomething1();// 异步任务artisanService.addArtisan();log.info("testTrace Call Over name={}", name);return "Hello," + name;}private void doSomething1() {log.info("doSomething1  info  log");doSomething2();log.error("doSomething1 error log");}private void doSomething2() {log.info("doSomething2  info log");}@TraceLog@GetMapping("/testTrace2")public String testTraceByAnno(@RequestParam("name") String name) throws InterruptedException {log.info("======================");log.info("testTraceByAnno  name={}", name);doSomething1();// 异步任务artisanService.addArtisan();log.info("testTraceByAnno Call Over name={}", name);return "Hello," + name;}}

http://127.0.0.1:9999/testTrace?name=artisan
http://127.0.0.1:9999/testTrace2?name=artisan222

在这里插入图片描述


小结

通过合理地利用MDC、拦截器、自定义线程池和工具类等技术手段,可以很好地实现 Spring Boot 应用的日志链路追踪,从而更方便地定位和排查问题,提升应用的可维护性和可靠性。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/814954.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES查询和监控

es安装 参考https://blog.csdn.net/okiwilldoit/article/details/137107087 再安装kibana&#xff0c;在它的控制台里写es查询语句。 es指南 es权威指南-中文版&#xff1a; kibana用户手册-中文版&#xff1a; es中文社区 es参考手册API es客户端API es查询语句 # 查询e…

Spring Boot:Web开发之三大组件的整合

Spring Boot 前言Spring Boot 整合 ServletSpring Boot 整合 FilterSpring Boot 整合 Listener前言 在 Web 开发中,Servlet 、Filter 和 Listener 是 Java Web 应用中的三大组件。Servlet 是 Java 代码,通过 Java 的 API 动态的向客户端输出内容。Filter 是处于客户端与服务…

74HC595引脚图时序图工作原理

74HC595和74hc164一样是在单片机系统中常用的芯片之一他的作用就是把串行的信号转为并行的信号&#xff0c;常用在各种数码管以及点阵屏的驱动芯片&#xff0c; 使用74HC595可以节约单片机mcu的io口资源&#xff0c;用3个io就可以控制8个数码管的引脚&#xff0c;他还具有一定的…

3D-Aware Multi-Class Image-to-Image Translation with NeRFs

3D-Aware Multi-Class Image-to-Image Translation with NeRFs 利用NeRFs实现3D感知的多类图像到图像的翻译 Senmao Li1  Joost van de Weijer2  Yaxing Wang1 李森茂 1 范德维杰 2 王亚兴 1  Fahad Shahbaz Khan3,4  Meiqin Liu5  Jian Yang1 法哈德夏巴兹汗 3,4 刘梅琴 …

CPU架构之---SMP、NUMA

一、缩略词&#xff1a; 缩略词全称含义SMPSymmetric Multi processing对称多处理器&#xff08;UMA&#xff09;NUMA(Non-Uniform Memory Access)非一致性内存访问UMAUniform Memory Architecture一致性内存访问 二、SMP简述和框架 2.1 smp简述 SMP&#xff08;Symmetric M…

随动系统同步性问题(跟随给定和跟随反馈的区别)

1、运动控制比例随动 运动控制比例随动系统_正运动随动系统-CSDN博客文章浏览阅读1.4k次,点赞2次,收藏5次。PLC如何测量采集编码器的位置数据,不清楚的可以参看我的另一篇博文:三菱FX3U PLC高速计数器应用(附代码)_RXXW_Dor的博客-CSDN博客本文主要以三菱FX3U系列的高速…

vue2创建项目的两种方式,配置路由vue-router,引入element-ui

提示&#xff1a;vue2依赖node版本8.0以上 文章目录 前言一、创建项目基于vue-cli二、创建项目基于vue/cli三、对吧两种创建方式四、安装Element ui并引入五、配置路由跳转四、效果五、参考文档总结 前言 使用vue/cli脚手架vue create创建 使用vue-cli脚手架vue init webpack创…

使用Redis实现用户最近浏览记录

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Redis是一个key-va…

使用ROCm的HIP API向量加法程序

一、向量加法程序 Radeon Open Compute (ROCm) 是一个开源平台&#xff0c;用于加速高性能计算 (HPC) 和机器学习应用程序。它支持包括GPUs在内的多种硬件&#xff0c;并提供HIP (Heterogeneous-compute Interface for Portability) 作为CUDA代码的便捷转换工具。为了提供一个…

[spring] rest api security

[spring] rest api security 之前的 rest api CRUD 都没有实现验证&#xff08;authentication&#xff09;和授权&#xff08;Authorization&#xff09;&#xff0c;这里使用 Spring security 进行补全 spring security 是一个非常灵活、可延伸的实现方式&#xff0c;比较简…

C语言 | Leetcode C语言题解之第19题删除链表的倒数第N个结点

题目&#xff1a; 题解&#xff1a; struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {struct ListNode* dummy malloc(sizeof(struct ListNode));dummy->val 0, dummy->next head;struct ListNode* first head;struct ListNode* second dummy;f…

安装 Kali NetHunter (完整版、精简版、非root版)、实战指南、ARM设备武器化指南、andrax、安卓渗透drozer

From&#xff1a;https://www.kali.org/docs/nethunter/ NetHunter 实战指南&#xff1a;https://www.vuln.cn/6430 乌云 存档&#xff1a;https://www.vuln.cn/wooyundrops 1、Kali NetHunter Kali NetHunter 简介 Net&#xff08;网络&#xff09;&#xff0c;hunter&#x…

uniapp开发h5端使用video播放mp4格式视频黑屏,但有音频播放解决方案

mp4格式视频有一些谷歌播放视频黑屏&#xff0c;搜狗浏览器可以正常播放 可能和视频的编码格式有关&#xff0c;谷歌只支持h.264编码格式的视频播放 将mp4编码格式修改为h.264即可 转换方法&#xff1a; 如果是自己手动上传文件可以手动转换 如果是后端接口调取的地址就需…

【亲测】国内如何支付Overleaf?Overleaf如何升级标准版专业版?Overleaf升级保姆级教程

0. 【必看】开通步骤简述 升级Overleaf的步骤简要总结如下&#xff1a; 使用虚拟信用卡平台WildCard开通虚拟信用卡&#xff08;从链接进入可以优惠15元人民币哦&#xff09;。开卡后&#xff0c;进入WIldcard找到卡片信息进入Overleaf绑定卡片并支付&#xff0c;完成支付后就…

南京观海微电子----快速判断出三极管的好坏

三极管其作用是把微弱信号放大成幅度值较大的信号。可分为硅NPN和锗PNP两种三极管。它有三个极&#xff0c;即基极B&#xff0c;集电极C&#xff0c;发射极E。 我们判断测量三极管时有一个最简易的方法&#xff0c;就是把所有三极管看成两个二极管组成。 可以把NPN管看着两个…

Typora导入功能使用详细

一、 pandoc安装&#xff08;导入需要的插件&#xff09; 1. 首次安装完typora&#xff0c;是没法导入的&#xff0c;需要安装pandoc&#xff0c;首先我们先在文件夹里面新建一个Typora文件&#xff0c;然后再找到导入功能点击就可以弹出安装的地址了 2. 点击文件可以找到导入…

毅速:3D打印技术助推压铸模效率飞跃

压铸模&#xff0c;作为压铸件成型的核心工具&#xff0c;其重要性不言而喻。如今&#xff0c;随着3D打印技术的崛起&#xff0c;压铸模的制造迎来了前所未有的革新&#xff0c;特别是在随形水路设计方面的应用&#xff0c;更是让制造效率实现了质的飞跃。 在传统压铸模制造中&…

springboot3整合consul实现服务注册和配置管理快速入门

服务注册&#xff1a; 配置管理&#xff1a; 注册中心的比较&#xff1a; 在微服务的世界中&#xff0c;服务注册是必不可少的。现在比较流行的也就是Consul和Nacos&#xff0c;Zookeeper没有管理界面&#xff0c;一般不建议使用&#xff0c;而Eureka已经处于停更&#xff0…

论文复现 MSE 均方误差 MAR 平均绝对值误差

1、均方误差&#xff08;L2损失&#xff09; 均方误差(MSE)是最常用的回归损失函数&#xff0c;计算方法是求预测值与真实值之间距离的平方和&#xff0c;公式如下&#xff1a; 2、平均绝对值误差&#xff08;L1损失&#xff09; 平均绝对误差&#xff08;MAE&#xff09;是另…

Python学习笔记13 - 元组

什么是元组 元组的创建方式 为什么要将元组设计为不可变序列&#xff1f; 元组的遍历