Java实现日志全链路追踪.精确到一次请求的全部流程

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 

实现效果如下: 一次查询即可找到所有关键信息.不再被多线程日志进行困扰了.

1:日志打印框架log4j ->  logback

logback是springboot默认自带的日志框架。不仅速度更快,而且内存占用也更小. (如果之前没用过log4j的建议先去学习下怎么使用).

打印日志的配置文件如下:  logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<Target>System.out</Target>
<encoder>
<charset>UTF-8</charset>
<pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
</encoder>
</appender>
<appender name="DAILY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Append>true</Append>
<!--  日志输出路径 -->
<File>/opt/logs/logOut.log</File>
<encoder>
<charset>UTF-8</charset>
<pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/opt/logs/logOut.log.%d{yyyy-MM-dd}</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>/opt/logs/logOut-error.log</File>
<Append>true</Append>
<encoder>
<charset>UTF-8</charset>
<pattern>%d - [%t] %-5p %c:%L %X - %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/opt/logs/logOut-error.log.%d{yyyy-MM-dd}</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
</appender>



<logger name="org.springframework" level="INFO"/>
<root level="INFO">
<appender-ref ref="DAILY_FILE"/>
<appender-ref ref="ROLLING_FILE"/>
</root>
</configuration>

重点是这行代码

<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{traceId}] %-5level %logger{50} - %msg%n</pattern>

此时的配置文件打印只能出现 年月日-线程名这些关键信息.无法获得每次请求的唯一id.所以我们需要创建一个拦截器.将每次请求生成一个id.通过id把本次请求覆盖到每个流程中.

2.1: 编写 http请求 拦截器

public class TraceWebInterceptor extends HandlerInterceptorAdapter {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(TraceWebInterceptor.class);
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("startTime", System.currentTimeMillis());
        //traceOrigin、traceCaller、traceId
        String traceOrigin = request.getHeader(TraceConstants.LOG_TRACE_ORIGIN);
        String traceCaller = request.getHeader(TraceConstants.LOG_TRACE_CALLER);
        String traceId = request.getHeader(TraceConstants.LOG_TRACE_ID);
 
        //如果不存在traceId需要生成
        if (StringUtils.isBlank(traceId)) {
            boolean generate = TraceUtil.loadTraceInfo();
            if(generate) {
                LOGGER.debug("[生成追踪信息]" + TraceUtil.getTraceInfoString());
            }
        }else {
            //设置MDC
            MDC.put(TraceConstants.LOG_TRACE_ORIGIN, traceOrigin);
            MDC.put(TraceConstants.LOG_TRACE_CALLER, traceCaller);
            MDC.put(TraceConstants.LOG_TRACE_ID, traceId);
        }
 
        //IP
        String traceIp = IpUtil.getIp(request);
        MDC.put(TraceConstants.LOG_TRACE_IP, traceIp);
 
        //响应返回
        response.setHeader(TraceConstants.LOG_TRACE_ID, TraceUtil.getTraceId());
 
        return super.preHandle(request, response, handler);
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
        if (LOGGER.isInfoEnabled()) {
            long upmsStartTime = (long) request.getAttribute("startTime");
            long upmsEndTime = System.currentTimeMillis();
            long upmsIntervalTime = upmsEndTime - upmsStartTime;
            LOGGER.info("{} {}接口耗时{}毫秒", request.getRequestURL(), request.getMethod(), upmsIntervalTime);
        }
        MDC.clear();
    }

2.2 编写Config类, 将拦截器TraceWebInterceptor添加到容器

@Configuration
@ConditionalOnClass({HandlerInterceptorAdapter.class, MDC.class, HttpServletRequest.class})
public class TraceWebAutoConfiguration implements WebMvcConfigurer {
 
    private static List<String> EXCLUDE_PATHS = new ArrayList<>();
 
    @Value("${" + TraceConstants.CONFIG_TRACE_EXCLUDE_PATHS + ":}")
    private String excludePaths;
 
    @Bean
    public TraceWebInterceptor traceWebInterceptor() {
        return new TraceWebInterceptor();
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        EXCLUDE_PATHS.add("/error");
        EXCLUDE_PATHS.add("/actuator/**");
 
        if (StringUtils.isNotBlank(excludePaths)) {
            if (excludePaths.contains(",")) {
                String[] split = excludePaths.split(",");
                EXCLUDE_PATHS.addAll(Arrays.asList(split));
            } else {
                EXCLUDE_PATHS.add(excludePaths);
            }
        }
 
        //该方式不能过全部过滤掉
        registry.addInterceptor(traceWebInterceptor()).order(-100).excludePathPatterns(EXCLUDE_PATHS);
    }
}

2.3 编写工具类

import javax.servlet.http.HttpServletRequest;


public class IpUtil {
    private static final String UNKNOWN = "unknown";

    public static String getIp(HttpServletRequest request) {
        if (request == null) {
            return UNKNOWN;
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}

public class TraceConstants {
    public static final String LOG_TRACE_ORIGIN = "traceOrigin";
    public static final String LOG_TRACE_CALLER = "traceCaller";
    public static final String LOG_TRACE_IP = "traceIp";
    public static final String LOG_TRACE_ID = "traceId";

    public static final String CONFIG_TRACE_EXCLUDE_PATHS = "trace.exclude.paths";

    public TraceConstants() {
    }
}

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import java.util.UUID;

public class TraceUtil {
    private static boolean simbaHttpClientInterceptorFlag = true;
    private static boolean sdkInterceptorFlag = false;
    private static String applicationName;

    public TraceUtil() {
    }

    public static void setApplicationName(String applicationName) {
        TraceUtil.applicationName = applicationName;
    }

    public static String getApplicationName() {
        return applicationName;
    }

    public static boolean getSimbaHttpClientInterceptorFlag() {
        return simbaHttpClientInterceptorFlag;
    }

    public static void setSimbaHttpClientInterceptorFlag(boolean simbaHttpClientInterceptorFlag) {
        TraceUtil.simbaHttpClientInterceptorFlag = simbaHttpClientInterceptorFlag;
    }

    public static boolean getSdkInterceptorFlag() {
        return sdkInterceptorFlag;
    }

    public static void setSdkInterceptorFlag(boolean sdkInterceptorFlag) {
        TraceUtil.sdkInterceptorFlag = sdkInterceptorFlag;
    }

    public static void setTraceCaller(String traceCaller) {
        MDC.put("traceCaller", traceCaller);
    }

    public static String getTraceCaller() {
        return MDC.get("traceCaller");
    }

    public static void setTraceOrigin(String traceOrigin) {
        MDC.put("traceOrigin", traceOrigin);
    }

    public static String getTraceOrigin() {
        return MDC.get("traceOrigin");
    }

    public static void setTraceId(String traceId) {
        MDC.put("traceId", traceId);
    }

    public static void removeTraceId() {
        MDC.remove("traceId");
    }

    public static void clearMdc() {
        MDC.clear();
    }

    public static String getTraceId() {
        return MDC.get("traceId");
    }

    public static String genTraceId() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    public static String getTraceIp() {
        return MDC.get("traceIp");
    }

    public static void setTraceIp(String traceIp) {
        MDC.put("traceIp", traceIp);
    }

    public static boolean loadTraceInfo() {
        boolean generate = false;
        String traceId = getTraceId();
        if (StringUtils.isBlank(traceId)) {
            traceId = genTraceId();
            generate = true;
        }

        setTraceId(traceId);
        return generate;
    }

    public static String getTraceInfoString() {
        return "TraceId:" + getTraceId() + ". traceCaller:" + getTraceCaller() + ". traceOrigin:" + getTraceOrigin();
    }
}

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Configuration
@ConditionalOnClass({HandlerInterceptorAdapter.class, MDC.class, HttpServletRequest.class})
public class TraceWebAutoConfiguration implements WebMvcConfigurer {

    private static List<String> EXCLUDE_PATHS = new ArrayList<>();

    @Value("${" + TraceConstants.CONFIG_TRACE_EXCLUDE_PATHS + ":}")
    private String excludePaths;

    @Bean
    public TraceWebInterceptor traceWebInterceptor() {
        return new TraceWebInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        EXCLUDE_PATHS.add("/error");
        EXCLUDE_PATHS.add("/actuator/**");

        if (StringUtils.isNotBlank(excludePaths)) {
            if (excludePaths.contains(",")) {
                String[] split = excludePaths.split(",");
                EXCLUDE_PATHS.addAll(Arrays.asList(split));
            } else {
                EXCLUDE_PATHS.add(excludePaths);
            }
        }

        //该方式不能过全部过滤掉
        registry.addInterceptor(traceWebInterceptor()).order(-100).excludePathPatterns(EXCLUDE_PATHS);
    }
}

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class TraceWebInterceptor extends HandlerInterceptorAdapter {

        private static final Logger LOGGER = LoggerFactory.getLogger(TraceWebInterceptor.class);

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                request.setAttribute("startTime", System.currentTimeMillis());
                //traceOrigin、traceCaller、traceId
                String traceOrigin = request.getHeader(TraceConstants.LOG_TRACE_ORIGIN);
                String traceCaller = request.getHeader(TraceConstants.LOG_TRACE_CALLER);
                String traceId = request.getHeader(TraceConstants.LOG_TRACE_ID);

                //如果不存在traceId需要生成
                if (StringUtils.isBlank(traceId)) {
                        boolean generate = TraceUtil.loadTraceInfo();
                        if (generate) {
                                LOGGER.debug("[生成追踪信息]" + TraceUtil.getTraceInfoString());
                        }
                } else {
                        //设置MDC
                        MDC.put(TraceConstants.LOG_TRACE_ORIGIN, traceOrigin);
                        MDC.put(TraceConstants.LOG_TRACE_CALLER, traceCaller);
                        MDC.put(TraceConstants.LOG_TRACE_ID, traceId);
                }

                //IP
                String traceIp = IpUtil.getIp(request);
                MDC.put(TraceConstants.LOG_TRACE_IP, traceIp);

                //响应返回
                response.setHeader(TraceConstants.LOG_TRACE_ID, TraceUtil.getTraceId());

                return super.preHandle(request, response, handler);
        }

        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
                if (LOGGER.isInfoEnabled()) {
                        long upmsStartTime = (long) request.getAttribute("startTime");
                        long upmsEndTime = System.currentTimeMillis();
                        long upmsIntervalTime = upmsEndTime - upmsStartTime;
                        LOGGER.info("{} {}接口耗时{}毫秒", request.getRequestURL(), request.getMethod(), upmsIntervalTime);
                }
                MDC.clear();
        }
}

import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.*;

public class WrapUtil {

    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);
            }
            TraceUtil.loadTraceInfo();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    public static <T> Callable<T> wrap(final Callable<T> callable) {
        return wrap(callable, MDC.getCopyOfContextMap());
    }

    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            TraceUtil.loadTraceInfo();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }

    public static Runnable wrap(final Runnable runnable) {
        return wrap(runnable, MDC.getCopyOfContextMap());
    }

    public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                                           BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
                                                           RejectedExecutionHandler handler) {
        return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                                           BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                                           BlockingQueue<Runnable> workQueue) {
        return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public static ThreadPoolExecutor newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                                           BlockingQueue<Runnable> workQueue,
                                                           RejectedExecutionHandler handler) {
        return new ThreadPoolExecutorMdcWrapper(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }


    public static ForkJoinPool newForkJoinPool() {
        return new ForkJoinPoolMdcWrapper();
    }

    public static class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
        public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
        }

        public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        }

        public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
                                            RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        }

        @Override
        public void execute(Runnable task) {
            super.execute(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
        }

        @Override
        public <T> Future<T> submit(Runnable task, T result) {
            return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()), result);
        }

        @Override
        public <T> Future<T> submit(Callable<T> task) {
            return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
        }

        @Override
        public Future<?> submit(Runnable task) {
            return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
        }
    }

    public static class ForkJoinPoolMdcWrapper extends ForkJoinPool {
        public ForkJoinPoolMdcWrapper() {
            super();
        }

        public ForkJoinPoolMdcWrapper(int parallelism) {
            super(parallelism);
        }

        public ForkJoinPoolMdcWrapper(int parallelism, ForkJoinWorkerThreadFactory factory,
                                      Thread.UncaughtExceptionHandler handler, boolean asyncMode) {
            super(parallelism, factory, handler, asyncMode);
        }

        @Override
        public void execute(Runnable task) {
            super.execute(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
        }

        @Override
        public <T> ForkJoinTask<T> submit(Runnable task, T result) {
            return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()), result);
        }

        @Override
        public <T> ForkJoinTask<T> submit(Callable<T> task) {
            return super.submit(WrapUtil.wrap(task, MDC.getCopyOfContextMap()));
        }
    }
}

把工具类加上后.此时运行项目. 如果报错就处理下依赖导包. 不报错就说明可以正常使用了. 然后发布代码.运行方法.去查看日志吧. 此时每次请求都已经生成唯一ID了.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据分析入门指南:从基础概念到实际应用(一)

随着数字化时代的来临&#xff0c;数据分析在企业的日常运营中扮演着越来越重要的角色。从感知型企业到数据应用系统的演进&#xff0c;数据驱动的业务、智能优化的业务以及数智化转型成为了企业追求的目标。在这一过程中&#xff0c;数据分析不仅是技术的运用&#xff0c;更是…

竹云助力雁塔城运集团实现西安市城投企业数据资产入表第一单!

近日&#xff0c;雁塔区城运集团联合陕数集团、深圳竹云科技股份有限公司等机构&#xff0c;顺利完成数据资产确权登记和数据资产入表工作&#xff0c;成为西安市首个城投数据资产入表案例&#xff0c;并获得陕西丝路数据交易中心颁发的数据资产登记证书。 近年来&#xff0c;…

使用Vue-cli脚手架创建uni-app项目(Vue2版本)

文章目录 前言准备工作接下来创建我们的 uni-app 项目 前言 uni-app官方说除了HBuilderX可视化界面&#xff0c;也可以使用 cli 脚手架&#xff0c;可以通过 vue-cli 创建 uni-app 项目。 uni-app官网文档 准备工作 需要安装 node.js 与 vue-cli 脚手架 我是用的版本如下 no…

【Python】从基础到进阶(二):了解Python语言基础以及数据类型转换、基础输入输出

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、基本数据类型转换1. 隐式转换2. 显式转换 三、基本输入输出1. 输入&#xff08;input&#xff09;2. 输出&#xff08;print&#xff09;3. 案例&#xff1a;输入姓名、年龄、身高以及体重&#xff0c;计算BMI指…

ICMAN触摸芯片之隔空感应

ICMAN触摸芯片之隔空感应 ICMAN触摸芯片满足工业级设计标准&#xff0c; 可过CS10V&#xff0c;ESD8kV&#xff0c;EFT4kV测试&#xff0c; 有超强稳定性和抗干扰能力 &#xff0c; 多用在普通触摸按键开关、大金属触摸及高灵敏度应用场合。 可根据实际应用&#xff0c;有低…

MWCSH 2024丨美格智能亮相上海世界移动通信大会,加速5G+AIoT应用进程

6月26日—28日全球通信领域最具规模和影响力的通信盛事—2024MWC上海世界移动通信大会在上海新国际博览中心隆重举行。MWC上海是亚洲连接生态系统的风向标&#xff0c;本届大会以“未来先行&#xff08;Future First&#xff09;”为主题&#xff0c;聚焦“超越5G”“人工智能经…

牛客小白月赛97 (个人题解)(待补完)

前言&#xff1a; 前天晚上写的一场牛客上比赛&#xff0c;虽然只写出了三道&#xff0c;但比起之前的成绩感觉自己明显有了一点进步了&#xff0c;继续努力吧&#xff0c; 正文&#xff1a; 链接&#xff1a;牛客小白月赛97_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞…

短信接口API的选择因素?有哪些使用方法?

短信接口API的集成难点是什么&#xff1f;如何保障API安全性&#xff1f; 短信接口API已经成为许多企业和开发者的关键工具&#xff0c;市场上有许多不同的短信接口API可供选择&#xff0c;这使得选择适合的API变得尤为重要。AoKSend将探讨在选择短信接口API时需要考虑的主要因…

vscode的一些使用问题

vscode使用技巧 1、快捷键&#xff08;1&#xff09;打开命令面板&#xff08;2&#xff09;注释&#xff08;3&#xff09;删除行&#xff08;4&#xff09;上下移动光标&#xff08;5&#xff09;光标回退&#xff08;6&#xff09;复制行&#xff08;7&#xff09;插入空白行…

联邦的基础配置

一、联邦的定义 联邦&#xff1a;在AS内部部署全互联的IBGP对等体可以很好解决IBGP路由传递的问题&#xff0c;但是扩展性低&#xff0c;大型网络中会带来沉重负担&#xff0c;针对此问题可以用路由反射器解决&#xff0c;也可以利用联邦解决&#xff0c;联邦也被称为联盟。大…

干货分享:Spring中经常使用的工具类(提示开发效率)

环境&#xff1a;Spring5.3…30 1、资源工具类 ResourceUtils将资源位置解析为文件系统中的文件的实用方法。 读取classpath下文件 File file ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX "logback.xml") ; // ...读取文件系统文件 file Resou…

ABAP 新语法-ITAB[ idx ]、ITAB[ key ]

这段ABAP代码主要演示了使用新的ABAP语法内表表达式的用法&#xff0c;其中核心点如下&#xff1a; 索引和关键字读取&#xff1a; 使用gt_student[1]进行索引读取&#xff0c;获取内表的第一个元素。使用gt_student[id 0000000005 age 15]进行关键字读取&#xff0c;根据指…

电子战学习笔记01:电子战概论

0、写在文前 本人在学习电子战相关理论知识时&#xff0c;一直感觉无从下手&#xff0c;之后在老师的推荐下购买了《EW101&#xff1a;电子战基础》纸质书籍学习&#xff0c;所以将自己的学习笔记在CSDN上记录一下&#xff0c;也供有需要的同学参考。 1、电子战定义 电子战&…

惠海100V 15A HC070N10L TO-252封装 N沟道MOS管 打火机/BMS电源板应用

MOS管的工作原理是基于在P型半导体与N型半导体之间形成的PN结&#xff0c;通过改变栅极电压来调整沟道内载流子的数量&#xff0c;从而改变沟道电阻和源极与漏极之间的电流大小。由于MOS管具有输入电阻高、噪声小、功耗低等优点&#xff0c;它们在大规模和超大规模集成电路中得…

ESP32-C3(基本信息)

ESP32-C3 是一款低功耗、高集成度的 MCU 系统级芯片 (SoC)&#xff0c;它集成了 2.4 GHz Wi-Fi 和低功耗蓝牙 (Bluetooth LE) 无线通信功能&#xff0c;并拥有丰富的外设接口和先进的电源管理机制。 主要特性&#xff1a; 无线通信&#xff1a; 支持 2.4 GHz Wi-Fi (802.11b/…

AI音乐革命:创新的门槛降低与产业未来的挑战

文章目录 每日一句正能量前言整体介绍人机合作AI在音乐创作中的辅助作用人机共同创作的模式实现人机共同创作的可能性伦理和法律考量 伦理道德AI与人类创造力的关系技术发展与人类创造力的平衡社会和文化影响结论 后记AI与音乐的未来交响创新的双刃剑版权与伦理的探讨人机合作的…

GaussDB关键技术原理:高性能(三)

GaussDB关键技术原理&#xff1a;高性能&#xff08;二&#xff09;从查询处理综述对GaussDB的高性能技术进行了解读&#xff0c;本篇将从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行分享。 目录 3 高性能…

深入理解Java核心技术模块化局部变量类型推断

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

【C语言】23.文件操作

由于要对数据进行持久化保存&#xff0c;我们就有了文件。 一、程序文件与数据文件 磁盘&#xff08;硬盘&#xff09;上的文件是文件。 但是在程序设计中&#xff0c;我们⼀般谈的文件有两种&#xff1a;程序文件、数据文件&#xff08;从文件功能的角度来分类的&#xff09…

pdf压缩,pdf压缩在线网页版,在线压缩pdf网站

在数字化时代&#xff0c;pdf文件已经成为我们工作、学习和生活中不可或缺的一部分。然而&#xff0c;pdf文件往往体积庞大&#xff0c;传输效率低下&#xff0c;还占用大量存储空间。如何在不影响文件质量的前提下&#xff0c;减小pdf文件的大小呢&#xff1f;今天&#xff0c…