SpringAOP+Logback+MDC实现traceId全链路日志追踪
这篇文章资料来自于网络是对部分知识整理这里只是记录一下仅供参考在分布式系统或微服务架构中全链路追踪Full-Chain Tracing对诊断和监控系统的性能至关重要。这篇文章我们将详细介绍如何使用 SLF4J 的 MDC 实现全链路的traceId。1. 什么是MDCMDCMapped Diagnostic Context映射诊断上下文是 SLF4J 提供的一种上下文机制它允许在日志记录时附加一些关键的上下文信息如traceId、userId等这些信息可以在日志格式中被引用从而丰富日志内容便于后续的日志分析和追踪。2. 如何实现全链路traceId2.1 生成或提取traceId在请求的入口点通常来说是客户端比如浏览器、移动应用等也可以是 Web 控制器、服务网关等生成一个唯一的traceIdimport org.slf4j.MDC; import java.util.UUID; public void handleRequest(HttpServletRequest request) { String traceId request.getHeader(X-Trace-Id); if (traceId null || traceId.isEmpty()) { traceId UUID.randomUUID().toString(); } MDC.put(traceId, traceId); try { // 处理请求的业务逻辑 } finally { MDC.remove(traceId); // 确保线程安全避免内存泄漏 } }2.2 配置日志格式以包含traceId在日志配置文件如logback.xml或log4j2.xml中将traceId添加到日志输出格式中。例如使用 Logback 时configuration appender nameSTDOUT classch.qos.logback.core.ConsoleAppender encoder pattern[%d{yyyy-MM-dd HH:mm:ss}] [traceId:%X{traceId}] %-5level %logger{36} - %msg%n/pattern /encoder /appender root levelinfo appender-ref refSTDOUT / /root /configuration这样每条日志都会包含traceId便于后续追踪。2.3 传播traceId到下游服务当一个服务调用下游服务时需要确保traceId被传递。通常通过 HTTP 头部传递例如使用X-Trace-Id或者tracestate头import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; public void callDownstreamService() { String traceId MDC.get(traceId); HttpHeaders headers new HttpHeaders(); headers.set(X-Trace-Id, traceId); HttpEntityString entity new HttpEntity(headers); RestTemplate restTemplate new RestTemplate(); restTemplate.exchange(http://downstream-service/api, HttpMethod.GET, entity, String.class); }2.4 在下游服务中设置traceId到 MDC下游服务在接收到请求时需要从请求头中提取traceId并设置到 MDCpublic void handleDownstreamRequest(HttpServletRequest request) { String traceId request.getHeader(X-Trace-Id); if (traceId ! null !traceId.isEmpty()) { MDC.put(traceId, traceId); } else { traceId UUID.randomUUID().toString(); MDC.put(traceId, traceId); } try { // 业务逻辑处理 } finally { MDC.remove(traceId); } }2.5 处理多线程环境下的traceId传播在多线程环境中如使用线程池或异步任务MDC 的上下文默认不会自动传播到子线程。需要手动传递traceIdimport java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public void executeAsyncTask() { String traceId MDC.get(traceId); ExecutorService executor Executors.newFixedThreadPool(10); executor.submit(() - { MDC.put(traceId, traceId); try { // 异步任务逻辑 } finally { MDC.remove(traceId); } }); }为了简化多线程环境下的 MDC 传播可以使用像 MDC Decorator 或自定义Executor来自动管理 MDC。3. 使用拦截器或过滤器统一管理traceId为了避免在每个请求处理方法中手动设置和清理traceId可以使用 Spring 的拦截器Interceptor或 Servlet 过滤器Filter来统一管理示例使用 Spring 的OncePerRequestFilterimport javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.MDC; import org.springframework.web.filter.OncePerRequestFilter; public class TraceIdFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String traceId request.getHeader(X-Trace-Id); if (traceId null || traceId.isEmpty()) { traceId UUID.randomUUID().toString(); } MDC.put(traceId, traceId); try { filterChain.doFilter(request, response); } finally { MDC.remove(traceId); } } }将该过滤器注册到 Spring 容器中即可。4. 示例代码4.1 AOP切面例子创建一个Spring AOP切面来拦截方法调用并在方法调用前后设置和清除MDC中的traceId。Aspect Component public class TraceAspect { Pointcut(execution(* com.yourpackage..*.*(..))) public void allMethods() {} Before(allMethods()) public void beforeAdvice(JoinPoint joinPoint) { // 生成traceId这里只是简单示例实际应用中可能需要更复杂的逻辑 String traceId UUID.randomUUID().toString(); MDC.put(traceId, traceId); } After(allMethods()) public void afterAdvice() { // 清除MDC中的traceId MDC.clear(); } }注意上面的示例使用了Before和After注解来分别在方法调用前和调用后设置和清除MDC中的traceId。但是在真实的应用场景中你可能希望在HTTP请求的开始和结束时设置和清除traceId这时你可能需要使用Around注解并在AOP切面中处理HTTP请求。4.2过滤器例子2定义一个web全局拦截器拦截HTTP请求中携带的traceId如果没有携带就生成一个随机id并在请求前后处理traceId。import org.slf4j.MDC; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Optional; import java.util.UUID; Component public class MDCFilter implements Filter { private static final String REQUEST_ID_HEADER requestId; private static final String REQUEST_ID requestId; Override public void init(FilterConfig filterConfig) { } Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) servletRequest; HttpServletResponse response (HttpServletResponse) servletResponse; String requestId request.getHeader(REQUEST_ID_HEADER); String requestIdMDC Optional.ofNullable(requestId).orElseGet(() - UUID.randomUUID().toString()); response.setHeader(REQUEST_ID_HEADER, requestIdMDC); MDC.put(REQUEST_ID, requestIdMDC); try { filterChain.doFilter(request,response); } finally { MDC.clear(); } } Override public void destroy() { } }4.3 过滤器例子11. 过滤器类import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.MDC; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.UUID; public class TraceIdFilter extends OncePerRequestFilter { private static final String TRACE_ID_HEADER X-Trace-Id; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String traceId request.getHeader(TRACE_ID_HEADER); if (traceId null || traceId.isEmpty()) { traceId UUID.randomUUID().toString(); } MDC.put(traceId, traceId); try { filterChain.doFilter(request, response); } finally { MDC.remove(traceId); } } }2. Spring Boot 中注册过滤器import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.web.servlet.FilterRegistrationBean; Configuration public class WebConfig { Bean public FilterRegistrationBeanTraceIdFilter traceIdFilter() { FilterRegistrationBeanTraceIdFilter registrationBean new FilterRegistrationBean(); registrationBean.setFilter(new TraceIdFilter()); registrationBean.addUrlPatterns(/*); // 过滤所有请求 registrationBean.setOrder(1); // 优先级 return registrationBean; } }3. 日志配置logback.xmlconfiguration appender nameSTDOUT classch.qos.logback.core.ConsoleAppender encoder pattern[%d{yyyy-MM-dd HH:mm:ss}] [traceId:%X{traceId}] %-5level %logger{36} - %msg%n/pattern /encoder /appender root levelinfo appender-ref refSTDOUT / /root /configuration5 多线程情况上述处理没有考虑多线程的场景而MDCMapped Diagnostic Context是基于线程的它为每个线程维护一个独立的映射。这意味着如果在不同的线程中处理相同的请求并且你希望这些线程共享相同的trace ID你需要确保正确地将trace ID从一个线程传递到另一个线程。解决方式采用TaskDecorator的线程任务装饰器方式为线程池的线程提供traceId的传递操作。4.4.1 定义任务装饰器import lombok.NonNull; import org.slf4j.MDC; import org.springframework.core.task.TaskDecorator; import org.springframework.util.ObjectUtils; import java.util.Map; public class MDCTaskDecorator implements TaskDecorator { Override public NonNull Runnable decorate(NonNull Runnable runnable) { // 此时获取的是父线程的上下文数据 MapString, String contextMap MDC.getCopyOfContextMap(); return () - { try { if (!ObjectUtils.isEmpty(contextMap)) { MDC.setContextMap(contextMap); } runnable.run(); } finally { // 清除子线程的避免内存溢出就和ThreadLocal.remove()一个原因 MDC.clear(); } }; } }4.4.2 定义线程池Configuration public class PoolConfig { Bean(taskExecutor) public Executor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); //配置核心线程数 executor.setCorePoolSize(5); //配置最大线程数 executor.setMaxPoolSize(10); //配置队列大小 executor.setQueueCapacity(100); //配置线程池中的线程的名称前缀 executor.setThreadNamePrefix(mdc-trace-); // 异步MDC executor.setTaskDecorator(new MDCTaskDecorator()); //执行初始化 executor.initialize(); return executor; } }4.4.3 使用线程池Resource Qualifier(taskExecutor) private Executor executor; PostMapping(/pool) public String pool() throws Exception{ CompletableFutureString future CompletableFuture.supplyAsync(() - { log.info(线程池里面); try{ Thread.sleep(1000); } catch (Exception ignored){} return ; }, executor); future.thenApplyAsync( value -{ log.info(线程池组合操作); try{ Thread.sleep(1000); } catch (Exception ignored) {} return value 1; }, executor); return future.get(); }或者异步调用时使用Async(taskExecutor)。6 其他MDC使用拦截器Component public class LogInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果有上层调用就用上层的ID String traceId request.getHeader(TraceIdUtil.TRACE_ID); if (StringUtil.isEmpty(traceId)) { TraceIdUtil.setTraceId(TraceIdUtil.generateTraceId()); } else { TraceIdUtil.setTraceId(traceId); } return true; } Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //调用结束后删除 TraceIdUtil.remove(); } }工具类public class TraceIdUtil { public static final String TRACE_ID requestId; public static String getTraceId() { String traceId (String) MDC.get(TRACE_ID); return traceId null ? : traceId; } public static void setTraceId(String traceId) { MDC.put(TRACE_ID,traceId); } public static void remove() { MDC.remove(TRACE_ID); } public static void clear() { MDC.clear(); } public static String generateTraceId() { return UUID.randomUUID().toString().replace(-, ); }日志文件配置property nameLOG_PATTERN value%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n /重点是%X{requestId},requestId和MDC中的键名称保持一致。MDC 存在的问题至此基本的功能已经实现但是存在一下几个问题多线程情况下子线程中打印日志会丢失traceId.HTTP跨服务之间的调用丢失traceId.子线程日志打印丢失traceId解决方案子线程在打印日志的过程中traceId将丢失解决方案为重写线程池(对于直接new Thread 创建线程的情况不考略),实际开发中也需要禁止这种情况。public class ThreadPoolExecutorMdcWrapper extends ThreadPoolTaskExecutor { private static final long serialVersionUID 3940722618853093830L; Override public void execute(Runnable task) { super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } Override public T FutureT submit(CallableT task) { return super .submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } Override public Future? submit(Runnable task) { return super .submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } }因为Spring Boot ThreadPoolTaskExecutor 已经对ThreadPoolExecutor进行封装只需要继承ThreadPoolTaskExecutor重写相关的执行方法即可。public class ThreadMdcUtil { public static void setTraceIdIfAbsent() { if (MDC.get(TraceIdUtil.TRACE_ID) null) { MDC.put(TraceIdUtil.TRACE_ID, TraceIdUtil.generateTraceId()); } } public static T CallableT wrap(final CallableT callable, final MapString, String context) { return () - { if (context null) { MDC.clear(); } else { MDC.setContextMap(context); } setTraceIdIfAbsent(); try { return callable.call(); } finally { MDC.clear(); } }; } public static Runnable wrap(final Runnable runnable, final MapString, String context) { return () - { if (context null) { MDC.clear(); } else { MDC.setContextMap(context); } //设置traceId setTraceIdIfAbsent(); try { runnable.run(); } finally { MDC.clear(); } }; } }代码说明判断当前线程对应MDC的Map是否存在如果存在则设置设置MDC中的traceId值不存在则新生成如果是子线程MDC中traceId不为null执行run方法线程池配置Configuration public class ThreadPoolTaskExecutorConfig { //最大可用的CPU核数 public static final int PROCESSORS Runtime.getRuntime().availableProcessors(); Bean public ThreadPoolExecutorMdcWrapper getExecutor() { ThreadPoolExecutorMdcWrapper executor new ThreadPoolExecutorMdcWrapper(); executor.setCorePoolSize(PROCESSORS *2); executor.setMaxPoolSize(PROCESSORS * 4); executor.setQueueCapacity(50); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix(Task-A); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); return executor; } }重新运行结果发现子线程能够正常获取traceid信息进行跟踪。2022-03-13 13:19:30.688 [Task-A1] INFO [482929425cbc4476a4e7168615af7890] c.s.fw.controller.UserController - child thread:1 2022-03-13 13:19:31.003 [Task-A1] INFO [482929425cbc4476a4e7168615af7890] c.s.fw.service.impl.UserServiceImpl - name:1HTTP调用丢失traceIdHTTP调用第三方服务接口时traceId丢失需要在发送请求时在Request Header中添加traceId在被调用方添加拦截器获取header中的traceId添加到MDC中。HTTP调用有多种方式比较常见的有HttpClient、OKHttp、RestTemplate以RestTemplate调用为例。接口调用方public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor { Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { String traceIdMDC.get(requestId); if(traceId!null) { request.getHeaders().set(requestId, traceId); } else { request.getHeaders().set(requestId, UUID.randomUUID().toString().replace(-, )); } return clientHttpRequestExecution.execute(request, body); } }RestTemplate添加拦截器即可。restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));2.第三方服务需要添加拦截器。Component public class LogInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果有上层调用就用上层的ID String traceId request.getHeader(TraceIdUtil.TRACE_ID); if (StringUtil.isEmpty(traceId)) { TraceIdUtil.setTraceId(TraceIdUtil.generateTraceId()); } else { TraceIdUtil.setTraceId(traceId); } return true; } Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //调用结束后删除 TraceIdUtil.remove(); } }其他HttpClient、OKHttp的实现方式与RestTemplate基本相同这里就不一一列举。7 ThreadContext介绍ThreadContext.put是 Log4j2 中 用于在高并发或多线程场景下追踪请求的核心工具 。它允许你在当前线程中绑定键值对如userId、requestId并将这些上下文信息自动追加到日志输出中。以下是具体的实现步骤与使用规范1. 核心使用步骤在 Java 代码的业务入口如 Web 拦截器或方法开头调用.put存入数据并在日志输出后手动清理import org.apache.logging.log4j.ThreadContext; // 1. 存入上下文信息 ThreadContext.put(userId, 123456); ThreadContext.put(requestId, req-abc-999); logger.info(用户登录成功); // 2. 【务必执行】请求结束时清理上下文防止线程复用导致数据污染 ThreadContext.clearAll();请谨慎使用此类代码。2. 日志配置文件修改PatternLayout要将存入的上下文变量打印出来需在 Log4j2 配置文件中使用%X占位符 [1, 2, 3]打印指定变量%X{userId}打印所有变量%X配置示例PatternLayout pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%X{userId}] [%X{requestId}] - %msg%n /请谨慎使用此类代码。3. 三大避坑指南防止内存泄漏与上下文污染由于现代服务器普遍使用线程池线程复用如果不调用ThreadContext.clearAll()或ThreadContext.remove(key)后续分配到该线程的其他请求可能会打印出旧请求的数据。Web 请求拦截建议在微服务或 Web 应用中推荐在请求拦截器Interceptor的preHandle中put并在afterCompletion中clearAll。子线程传递默认的ThreadContext在主子线程间无法共享。如需在异步或多线程间传递上下文需使用log4j2.component.properties开启对应的继承机制8. 注意事项在使用一项技术或者一个框架时我们都需要知道有什么弊端这样才能更好的帮助我们做好技术决策下面是使用MDC需要注意的事项线程安全在多线程环境下确保MDC的设置和清理以避免traceId泄漏到其他线程。性能影响频繁操作MDC可能略微影响性能需权衡日志详细程度和性能需求。日志存储确保日志存储系统支持和索引traceId以便高效查询和分析。异常处理在异常情况下确保traceId仍能正确记录在日志中有助于快速定位问题。9. 总结本文我们分析了如何使用MDC实现全链路追踪通过合理使用 SLF4J的MDC可以在分布式系统中实现全链路的traceId追踪提升日志的可读性和追踪能力。但是对于更复杂的需求我们还是建议结合专门的分布式追踪工具比如 Spring Cloud Sleuth、Zipkin 或 Jaeger以获得更全面的追踪能力。10 参考https://juejin.cn/post/7074461710030995492https://juejin.cn/post/7384740010896424979https://juejin.cn/post/7461206556317237286

相关新闻

天津市河东区知名的公司注销财税公司哪家专业

天津市河东区知名的公司注销财税公司哪家专业

在天津河东区,公司注销业务已成为不少企业主头疼的难题。据统计,2024年天津市企业注销数量同比增长约15%,其中河东区占比显著。许多老板因选择不靠谱的财税公司,导致流程拖延、成本激增,甚至卷入税务风险。那么&#x…

2026/10/1 16:06:58 阅读更多 →
2026济南黄金回收白银回收铂金回收中检持证鉴定师铂金银饰高价回收门店联系方式推荐

2026济南黄金回收白银回收铂金回收中检持证鉴定师铂金银饰高价回收门店联系方式推荐

2026济南黄金白银铂金回收实测榜单|公安工商双备案中检认证无损测金无折旧费门店 济南贵金属回收店铺遍地丛生,行业套路层出不穷,不少市民变现遭遇虚高报价、克扣损耗、未经同意熔金压价等问题。为帮助本地居民规避消费陷阱,小编实…

2026/9/30 9:35:56 阅读更多 →
别再把 AI Agent 理解成“大模型加工具”:一文讲透 Token、Skill、RAG、MCP、SDD 与 Harness

别再把 AI Agent 理解成“大模型加工具”:一文讲透 Token、Skill、RAG、MCP、SDD 与 Harness

摘要很多人理解 AI Agent,停留在“大模型负责思考,工具负责执行,知识库负责提供资料”这一层。这个理解可以解释一个 Agent Demo 是如何搭建出来的,却解释不了一个 Agent 为什么会在真实业务中重复调用工具、丢失任务状态、引用错…

2026/9/27 23:24:04 阅读更多 →

最新新闻

javax.lang.model实战:为DeepSeek补齐Java类型上下文,让AI生成代码一次编过

javax.lang.model实战:为DeepSeek补齐Java类型上下文,让AI生成代码一次编过

前段时间我重构一版内部业务系统时,让 DeepSeek 帮忙写接口实现类,发现一个特别典型的毛病:它生成的代码经常引用不存在的类型、把泛型参数放错位置、甚至调用根本没定义过的方法。这不是模型能力不行,而是它根本看不到整个工程的…

2026/10/13 18:38:41 阅读更多 →
强化学习在炼钢动态调度中的工业落地实践

强化学习在炼钢动态调度中的工业落地实践

简介:本资源是一套面向工业智能优化领域的强化学习实践方案,聚焦炼钢生产过程中的动态调度难题,适用于具备Python基础与机器学习背景的算法工程师、自动化专业研究生及智能制造方向研究者。资源通过构建马尔可夫决策过程建模炼钢工序时序约束…

2026/10/13 18:38:41 阅读更多 →
React Native鸿蒙开发实战:模拟智能音响从搭建到调试

React Native鸿蒙开发实战:模拟智能音响从搭建到调试

做鸿蒙应用开发一段时间后,身边问得最多的问题往往不是ArkUI怎么用,而是“React Native能不能跑鸿蒙”。我的答案是能,但工程搭建和调试链路比起普通Android确实要绕一点。为了把这条链路彻底摸透,我给自己找了一个不大不小的练习…

2026/10/13 18:38:41 阅读更多 →
告别文献堆砌:用AI工具高效完成文献综述的实战流程

告别文献堆砌:用AI工具高效完成文献综述的实战流程

写文献综述写到崩溃,多半不是读得不够多,而是读完后不知道怎么安放这些文献。我见过太多人把综述写成“某某认为……某某发现……”的流水账,最后被导师批一句“这只是文献堆砌,不是综述”。我自己也踩过这个坑,直到用…

2026/10/13 18:38:41 阅读更多 →
React Native鸿蒙开发实战:从0到1实现模拟智能音响App

React Native鸿蒙开发实战:从0到1实现模拟智能音响App

做鸿蒙原生开发有一段时间的朋友,应该都能感受到这两年跨端方案在鸿蒙生态里的热度。React Native 这套老牌跨平台框架完成鸿蒙适配之后,让 iOS、Android、鸿蒙三端共用一套业务代码变成了现实。我最近用这套方案做了一个"模拟智能音响"的入门…

2026/10/13 18:38:41 阅读更多 →
AI芯片软硬件协同设计:从算法到硅片的系统级工程实践

AI芯片软硬件协同设计:从算法到硅片的系统级工程实践

1. 这不是“芯片AI”的简单拼凑,而是系统级协同的硬核工程“AI 芯片的软硬件设计”这八个字,最近在技术社区、招聘JD和高校课题申报里高频出现,但很多人一听到就下意识觉得——这是半导体厂或大厂研究院的专属领地,离自己很远。其…

2026/10/13 18:37:40 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →