news 2026/9/18 3:23:34

ASP.NET Core中间件与请求管道:从原理到实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASP.NET Core中间件与请求管道:从原理到实战全解析

刚在调试一个接口慢查询,发现耗时全卡在一个不起眼的中间件里,这让我又一次体会到:ASP.NET Core 里最容易被低估、也最容易出问题的,就是中间件与请求管道。这个09-中间件与请求管道的主题,我在面试里问过不少人,也在生产环境里踩过不少坑。如果你正在学 C# 中间件有哪些、想搞懂管道是怎么串起来的,或者准备中间件面试,这篇文章应该能帮你把这些事一次理顺。

我用一个真实项目的视角来拆解:从最基础的概念讲起,到自定义中间件的几种写法,再到注册顺序、内置中间件清单、常见故障排查,最后顺手把面试里那些高频问题也一起消化掉。每个部分都会给出可以直接抄的代码和配置,也会说明为什么这么做、不这么做会踩什么坑。

1. 内容整体设计与思路拆解

1.1 核心需求解析:中间件到底解决了什么问题

先说人话。你在浏览器里输入一个地址,请求到服务器后,不是直接进 Controller 的。它会先经过一条管道,管道里站着一排“检查员”,每个检查员只看自己负责的那点事:有的管 HTTPS 跳转、有的管静态文件、有的管身份验证、有的管 CORS、最后才轮到你的业务代码。这些“检查员”在 ASP.NET Core 里就叫中间件(Middleware)。

为什么要设计成这样?最直接的原因是:横切关注点需要统一处理。日志、异常、认证、缓存这些逻辑,如果散落在每个 Controller 里,代码会变成灾难。中间件把公共逻辑抽到管道里,每个请求进来都自动走一遍,干净又统一。而且它是可插拔的,想加就加,想换就换,不影响业务代码。

这也是面试里最喜欢问的一个点:中间件的好处是什么。标准回答思路就是:解耦横切关注点、请求处理可组合、管道顺序可控、组件可复用。

1.2 请求管道的执行模型:洋葱模型与短路机制

中间件管道最经典的理解方式是“洋葱模型”。一个请求从最外层中间件进入,一层一层往里走,走到最内层的业务代码(比如 Controller),然后响应再从内层一层一层往外返。

请求 -> M1(进入) -> M2(进入) -> 业务代码 -> M2(返回) -> M1(返回) -> 响应

每个中间件可以在调用next()之前做“请求阶段”的处理,在next()之后做“响应阶段”的处理。所以日志中间件可以在请求进来时记录“开始处理”,在响应出去时记录“处理完成”,两次记录自然环绕着整条管道。

还有一个非常关键的机制叫短路。中间件可以不调用next(),直接返回响应,这样后面所有的中间件都不会执行。典型的例子是静态文件中间件:如果请求的是favicon.ico之类的静态资源,它直接返回文件,根本不会进 Controller。Run方法就是一个永不调用next()的终止型中间件。这个机制在实现“黑名单”“接口开关”“请求限流”时特别好用,后面实操部分我会给出代码。

1.3 方案选型:为什么用管道组装而不是逐个 if 嵌套

你可能会有疑问:这些逻辑我用if嵌套也能实现啊,比如先判断异常、再判断静态文件、再判断认证……为什么不直接嵌套呢?

这里面有三个现实原因。第一,顺序和职责会耦合。用 if 嵌套,逻辑一多,代码就是一座大粪山,想调整顺序就得动整个结构。用中间件管道,每个功能是一个独立组件,增减、排序都通过配置完成,可维护性完全不在一个量级。第二,组件复用性差。你的项目如果想复用一套日志逻辑,if 嵌套得复制粘贴,中间件则可以在多个项目里直接引用。第三,框架生态就是这样设计的。ASP.NET Core 的认证、授权、CORS、静态文件、路由全部都是中间件,你不用这个模型,就等于自己重新写一套框架,还得跟框架打架。

所以,思路就是从“面向过程”切换成“面向管道”,把每一步当成管道上的一个节点。

2. 核心细节解析与实操要点:中间件的实现方式

2.1 约定式中间件类:最传统也最底层的写法

先看最简单的自定义中间件长什么样。约定式中间件是一个普通类,构造函数接收RequestDelegate,类里有一个InvokeAsync方法,参数是HttpContext或者HttpContext加其他依赖服务。

public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var start = DateTime.UtcNow; _logger.LogInformation("Request started: {Method} {Path}", context.Request.Method, context.Request.Path); await _next(context); var elapsed = DateTime.UtcNow - start; _logger.LogInformation("Request finished: {Method} {Path} with {StatusCode}, took {Elapsed} ms", context.Request.Method, context.Request.Path, context.Response.StatusCode, elapsed.TotalMilliseconds); } }

用法是注册到管道里:

app.UseMiddleware<RequestLoggingMiddleware>();

有几个细节值得注意。RequestDelegate是管道里“下一个节点”的委托,不调用_next就直接返回的话,后面的中间件就不会执行——这就是短路。另外,构造函数里不能注入 Scoped 服务,因为中间件实例本身是单例的(App 启动时创建),Scoped 服务的生命周期跟请求走,注入会出问题。要注入 Scoped 服务,可以把服务作为InvokeAsync的参数,由框架在请求时从 DI 容器解析。

2.2 基于工厂的中间件:解决依赖注入的优雅方案

如果你注册的是app.UseMiddleware<MyMiddleware>(),框架默认会用ActivatorUtilities来创建中间件实例,构造函数里的依赖除了RequestDelegate之外,都从 DI 容器取。这在大部分场景下够用了。

但如果你需要更精细的控制,比如每次请求都重新创建中间件实例,或者想用自己写好的工厂类来构建中间件,就可以用IMiddlewareFactoryIMiddleware

public class CustomMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // 处理请求 await next(context); // 处理响应 } } // 在 DI 里注册 builder.Services.AddScoped<CustomMiddleware>();

注意这里的区别:IMiddleware是需要注册到 DI 的,而且建议用AddScoped或者AddTransient,因为它可以被请求生命周期管理。相比之下,UseMiddleware<T>的默认行为更接近单例(中间件实例只创建一次)。这一点在面试里如果答得出来,会很加分。

2.3 内联中间件与 Use/Map/When 扩展方法:快速原型神器

不想为了一个小功能单独建一个类?没问题,直接用Use内联写入:

app.Use(async (context, next) => { // 请求进入时做的事情 Console.WriteLine($"Before: {context.Request.Path}"); await next(); // 响应返回时做的事情 Console.WriteLine($"After: {context.Response.StatusCode}"); });

除了Use,还有几个专门做分支的扩展方法:

  • Map:根据请求路径前缀分叉管道,比如/admin走管理员中间件链,其他走主链。
  • MapWhen:当满足某个条件时,走另一条管道。
  • UseWhen:当满足条件时,在现有管道中额外插入一段中间件,但执行完还会回到主管道继续往下走,注意和MapWhen的区别。

Map写分支的典型例子:

app.Map("/health", healthApp => { healthApp.Run(async context => { await context.Response.WriteAsync("Healthy"); }); });

这种行为在我的实践中很常用,尤其是健康检查接口,根本不用写 Controller,一个Run就搞定了。

2.4 到底该用哪种写法:决策对比与推荐

为了让你看得更清楚,我整理了一张对比表:

方式适用场景依赖注入请求作用域复杂度
app.Use(lambda)简单日志、响应头、短小逻辑无,直接闭包捕获每次请求执行
UseMiddleware<T>约定式常规中间件,逻辑较多构造注入 Singleton/Transient,Invoke 注入 Scoped实例单例,Invoke 每次调用
IMiddleware + AddScoped需要请求级实例、需要工厂全部由 DI 管理请求级中高
Map/MapWhen/UseWhen按路径、按条件分叉管道无需专门类正常

我的个人经验是:80% 的场景用UseMiddleware<T>约定式就够了。只有当你确定中间件里依赖的服务需要请求级实例,或者你要做单元测试、想通过 DI 替换中间件实现时,才去考虑IMiddleware。内联 lambda 适合非常轻量的逻辑,写多了Program.cs会变得很丑,而且没法复用。

3. 实操过程与核心环节实现:注册顺序与内置中间件配置

3.1 注册顺序为什么决定生死:从一次 404 事故说起

我印象特别深的一次事故:有个同事在app里先写了app.UseRouting(),然后紧接着写了一个自定义中间件,中间件里想访问路由数据,结果context.GetRouteData()永远拿不到值。当时他百思不得其解。

原因很简单:中间件是按注册顺序执行的。路由数据的填充发生在UseRouting()这个节点之后,你如果在此之前去取,自然取不到。同理,如果你想在某个中间件里拿到User(认证后的用户信息),那个中间件必须注册在UseAuthentication()之后。

这个问题的本质是,请求管道是有“状态推进”的,每一步可能在前一步的基础上补充一些数据。你把自己放的中间件当成管道里的一个工位,前一个工位没做完的事,你这里就看不到。所以注册顺序不是“建议”,而是功能能否正常工作的前提

3.2 ASP.NET Core 内置中间件全景:C# 中间件有哪些

面试里经常被问“C# 中间件有哪些”,其实标准答案就是 ASP.NET Core 内置的那一串。我按标准的推荐顺序列一下:

顺序中间件作用
1UseExceptionHandler异常处理,生产环境避免暴露异常堆栈
2UseHsts强制 HTTPS 安全策略头
3UseHttpsRedirectionHTTP 重定向到 HTTPS
4UseStaticFiles处理静态文件
5UseRouting路由匹配,确定请求对应的端点
6UseCors跨域
7UseAuthentication身份认证
8UseAuthorization授权
9UseEndpoints/MapControllers执行业务端点
10UseResponseCaching/UseResponseCompression响应缓存 / 压缩

这只是推荐顺序,不是死规矩。比如你用了UseResponseCompression(响应压缩),它通常应该放在靠前的位置,因为在响应返回到客户端之前要把数据压缩好。如果你加了UseStaticFiles之后想给静态文件也做压缩,那可能要调整顺序。关键还是要理解每个中间件做了什么、它需要前置哪些信息。

一个比较常见的疑问是.NET 7之后的简化写法,比如WebApplication模板里你可能看不到UseRoutingUseEndpoints的显式调用,而是直接app.MapControllers()。框架层面会自动把路由相关的中间件放到合适的位置。但这不意味着顺序不重要,你自定义中间件依然要遵守“需要什么数据就必须在对应中间件之后”的原则。

3.3 手写三个高复用自定义中间件:异常处理、响应头、接口耗时统计

实操要有东西能落地。我建议你跟着写这三个,覆盖日常开发最常用的场景。

第一个是全局异常处理中间件。注意我这里说的是“手动版的异常处理”,和UseExceptionHandler有区别,但更能体现中间件思想:

public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly IHostEnvironment _env; public ExceptionHandlingMiddleware(RequestDelegate next, IHostEnvironment env) { _next = next; _env = env; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; var response = new { Message = "服务器内部错误", Detail = _env.IsDevelopment() ? ex.Message : null }; await context.Response.WriteAsJsonAsync(response); } } }

注册的时候记得放在最前面,因为它要兜住后面所有中间件抛出的异常:

app.UseMiddleware<ExceptionHandlingMiddleware>();

第二个是自定义响应头中间件,用来输出X-Request-Id或者安全响应头:

public class SecurityHeadersMiddleware { private readonly RequestDelegate _next; public SecurityHeadersMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { context.Response.OnStarting(() => { context.Response.Headers["X-Content-Type-Options"] = "nosniff"; context.Response.Headers["X-Frame-Options"] = "DENY"; return Task.CompletedTask; }); await _next(context); } }

这里有个很重要的知识点:在_next(context)之前直接设置响应头,其实是有风险的,因为响应一旦开始发送,Header 就定死了,不能再改。用OnStarting注册回调,框架会在响应将要发送之前执行这个回调,这时候改 Header 才是安全可靠的。

第三个是接口耗时统计,这个配合日志中间件一起用特别方便,还能输出性能数据到监控系统:

public class PerformanceMiddleware { private readonly RequestDelegate _next; private readonly ILogger<PerformanceMiddleware> _logger; public PerformanceMiddleware(RequestDelegate next, ILogger<PerformanceMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var sw = Stopwatch.StartNew(); await _next(context); sw.Stop(); if (sw.ElapsedMilliseconds > 500) { _logger.LogWarning("Slow request: {Method} {Path} took {Elapsed} ms", context.Request.Method, context.Request.Path, sw.ElapsedMilliseconds); } } }

这三个中间件加起来就形成了一个非常实用的基础管道:异常处理在最外层兜底、性能中间件记录耗时、响应头中间件给输出加安全头、日志中间件记录访问明细。

3.4 检查管道执行轨迹:按步骤打印中间件顺序

新手经常困惑“代码明明写了,为什么没执行”。这时候最高效的排查方法,就是写一个临时的中间件,在管道里打印每个节点的执行轨迹。我一般这样搞:

app.Use(async (context, next) => { Console.WriteLine($"==> 中间件: {context.Request.Path}"); await next(); Console.WriteLine($"<== 返回: {context.Response.StatusCode}"); });

放到任意位置,就能看到它前后发生了什么。把所有自定义中间件都加上这种调试日志,跑一次请求,管道顺序一目了然。我曾经用这个方法,在五分钟内定位了一个 CORS 中间件放错位置导致跨域失败的问题——当时请求已经进了 CORS,但因为注册顺序在授权之后,浏览器预检请求没被正确处理。

4. 常见问题与排查技巧实录

4.1 中间件不执行:可能栽在 Rrun 型节点上

一个典型的坑:你在管道中间放了一个Run中间件,后面的东西全不执行了。很多人会忽略Run的本质——它不调用next,直接短路管道。

举个例子:

app.UseMiddleware<RequestLoggingMiddleware>(); app.Run(async context => { await context.Response.WriteAsync("Hello"); }); app.UseMiddleware<SecurityHeadersMiddleware>();

这个SecurityHeadersMiddleware永远不会执行,因为Run已经画上了管道终点。你在项目里如果看到某个中间件“不生效”,第一件事检查它前面是不是有个Run或者有哪个中间件有条件地没有调用next

4.2 读取请求体两次会卡死:Body 只能读一次

这是一个非常经典的中间件问题。你在一个中间件里读取请求体context.Request.Body,读完之后发现 Controller 里绑定参数全部变成 null 了。原因是请求体是一个流,流被读完之后,位置停留在末尾,再读就什么都读不到了。框架去绑定参数的时候读到的就是空。

解决办法是开启请求体缓冲,然后在读取后重新将位置重置为 0:

public async Task InvokeAsync(HttpContext context) { context.Request.EnableBuffering(); using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true)) { var body = await reader.ReadToEndAsync(); // 处理 body 内容 } context.Request.Body.Position = 0; await _next(context); }

注意两点:EnableBuffering()必须放在第一次读取之前;StreamReader构造参数leaveOpen: true保证读完不释放底层流。这两个细节漏一个都会在运行时给你惊喜。

4.3 响应阶段改 Header 报错:OnStarting 的正确用法

我见过有人这么写:在_next(context)之后、还没返回之前,直接设置context.Response.Headers["X-Foo"] = "bar"。大多数时候没问题,但遇到大响应体或者流式响应时,可能还没执行到那行代码,响应头就已经发出去了,然后框架抛异常“Headers are read-only, response has already started”。

这是响应管道里最容易踩的坑。像我在 3.3 节里写的那样,要改响应头,推荐用context.Response.OnStarting注册回调。这个回调会在响应发送之前同步执行,是框架留给中间件“最后修改 Header 的机会”。如果是IResult或者 MVC 里动态设置,也要注意时机,尽量在业务代码返回前处理。

4.4 Scoped 服务注入中间件失败:两种解决方案对照

中间件的生命周期问题用一句话总结就是:中间件本身倾向于单例,但你可能需要请求作用域的服务。那么在约定式中间件里怎么拿到 Scoped 服务呢?

方案一,在InvokeAsync方法里注入:

public async Task InvokeAsync(HttpContext context, IUserService userService)

它会被框架从当前请求的 DI 作用域里解析出来,该是 Scoped 就给你 Scoped 实例。这是官方推荐的方式。

方案二,用IServiceScopeFactory手动创建一个小作用域:

public async Task InvokeAsync(HttpContext context) { using (var scope = _scopeFactory.CreateScope()) { var userService = scope.ServiceProvider.GetRequiredService<IUserService>(); // 使用 userService } await _next(context); }

方案二有点“绕过框架”的意思,一般用于你需要在中间件内部做多线程任务,或者从某个服务里拿数据必须拥有独立作用域时。日常代码优先用方案一,简洁且不会引起作用域混乱。

4.5 性能陷阱:中间件里的同步阻塞 IO

中间件是跑在请求线程上的,如果你是同步调用.Result.Wait()去等待一个异步方法,在高并发下很可能把线程池耗尽。这不仅是中间件的问题,但中间件因为“每个请求必定经过”,放大了性能风险。

经验准则是:asyncasync,同步Task.Run也不要乱用,IO 操作一定要用await。我的性能监控中间件就曾经把调用第三方 HttpClient 的同步写法暴露出去了,压测 100 并发时,线程池直接打爆,换成了await httpClient.GetAsync()之后,P95 延迟下降了接近一半。这种问题不压测根本发现不了,所以要提前预防。

4.6 中间件常见问题速查表

我整理了日常排查问题的一个快速索引:

现象可能原因排查建议
中间件整体没执行前面有Run或短路分支检查管道里是否有无条件终止节点
读不到路由数据中间件顺序在UseRouting之前调后顺序,或使用UseRouting之后的分支
Controller 接收 body 为 null中间件读了 Body 没重置 Position使用EnableBuffering+Position = 0
设置 Header 抛异常响应已开始发送改用OnStarting回调
Scoped 服务注入报错中间件构造函数注入 Scoped改为InvokeAsync参数注入
接口跨域失败CORS 中间件顺序不对确保UseCorsUseAuthorization之前
权限校验不生效中间件在授权之前短路了检查是否有中间件吞掉请求

这张表我和团队同学一起完善过很多次,基本上能覆盖日常开发 90% 的中间件问题。

5. 中间件面试高频问题与深度扩展

5.1 面试官到底在考什么:三个维度拆解

面试里问中间件,通常有三个层次。

第一层,概念题。比如“什么是中间件”“中间件和过滤器的区别”。这一层考察你是否理解请求管道模型。中间件是管道级、关注横切逻辑;过滤器(Filter)是 MVC 生命周期内的概念,更关注 Action 执行前后的行为。两者不是替代关系,而是层级不同的机制。

第二层,原理题。比如“Use 和 Run 区别”“如何自定义中间件”“中间件顺序的影响”。这一层考察你是否有真实项目经验,能不能讲清楚RequestDelegate的本质和next调用的意义。

第三层,实践题。比如“压测发现接口很慢,如何用中间件定位”“如何实现一个通用的接口耗时监控”“如何设计一个网关的限流中间件”。这一层考察你的设计能力和排障能力,有实际踩坑经验的人会明显更有优势。

5.2 两个容易被忽略的扩展点:MapWhen 分支与终端中间件

除了基础概念,想要在面试里秀一把,可以主动聊聊两个进阶用法。

第一个是MapWhen实现按条件切分支管道:

app.MapWhen(context => context.Request.Query.ContainsKey("internal"), builder => { builder.UseMiddleware<InternalApiKeyMiddleware>(); });

比如你有一些内部接口需要单独的 API Key 校验,正常接口走认证中间件链,内部接口走自己的 Key 校验链。用分支管道,可以把这两种校验逻辑完全隔离,不会互相污染。

第二个是“终端中间件”设计模式。网关类项目经常用:管道走到你的自定义中间件后,它不是简单调用next,而是直接作为请求的终点,把请求通过 HttpClient 转发到下游服务,再把下游响应原样返回。这种模式在 YARP 这类反向代理库里非常常见,理解了这个,你对中间件的掌控能力就上一个台阶。

5.3 一段能背的面试回答模板

面试官问“谈谈你对中间件的理解”,可以按这个思路答:

中间件是 ASP.NET Core 请求管道的基本组成单元。每个中间件接收一个RequestDelegate,代表管道的下一步。它可以在调用下一步之前处理请求,也可以在返回后处理响应,还可以选择不调用下一步,实现请求短路。框架内置了一系列中间件,比如静态文件、路由、认证、授权等,我们通过UseMapWhen等方式组合它们。自定义中间件时,要注意注册顺序对功能的影响,也要注意生命周期问题——构造函数不能注入 Scoped 服务,但InvokeAsync参数可以。响应阶段修改 Header 或者读取请求体,都要在正确的时间点操作,否则会遇到运行时错误。

这个回答涵盖了定义、原理、内置列表、自定义要点、常见坑,面试官一般会顺着往下深挖其中一两点,你就自然进入自己擅长的领域了。

5.4 写在最后的经验之谈

说了这么多,最想强调的一点是:中间件虽然简单,但它决定了你整个应用的请求边界。很多系统在前期开发时感觉中间件没什么用,等到排查跨域、认证、慢请求、日志格式时,才发现全部积攒在管道这一层。

我自己现在设计新项目的第一件事,就是把中间件管道画出来:异常处理放哪、日志放哪、认证放哪、自定义业务中间件放哪,一条条列清楚。花十分钟做这件事,能省掉后期好几个小时的排查时间。另一个实用的习惯是给中间件加简单的 “Pipeline 预览” 方法,在启动日志里打出当前生效的中间件列表,每次启动都能确认当前环境到底跑了哪些中间件,避免配置不同导致的环境差异问题。这个习惯帮助我多次在生产环境快速定位到“为什么这里生效、那里不生效”的环境类问题。建议你也试着在自己的项目里这么做一次,实测下来你会回来感谢这个习惯的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 3:23:21

SAP PS项目参数文件OPSA基本控制页签配置深度解析

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

作者头像 李华
网站建设 2026/9/18 3:22:21

拆 Opus 5 与 Astra 开销,TaoToken 只留复杂任务

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

作者头像 李华
网站建设 2026/9/18 3:21:42

谐振接地系统单相接地故障:暂态量选线与行波定位

简介&#xff1a;谐振接地系统中复杂单相接地故障的选线与定位&#xff0c;长期是配电网故障检测的难点之一。这份资源面向电力系统保护与控制领域的研究人员、工程师及配电网运维人员&#xff0c;聚焦两点同相接地和高阻接地两种特殊故障类型&#xff0c;基于零序等效网络与高…

作者头像 李华
网站建设 2026/9/18 3:20:09

供应商质量评分自动化:MySQL+Python实现CPK、PPM与8D闭环

简介&#xff1a;围绕供应商质量管理与控制这一供应链核心议题&#xff0c;这份文档面向采购、质量与供应链管理人员&#xff0c;梳理了可落地的管控方法与实施要点。内容从制定联合质量计划切入&#xff0c;分经济、技术、管理三个维度展开&#xff0c;涵盖价值分析、成本与质…

作者头像 李华
网站建设 2026/9/18 3:20:02

ToDesk清理后必须重启吗?Linux远程工具故障排查实战

如果你在成规模的 Linux 机器上部署过 ToDesk&#xff0c;大概率遇到过这种场景&#xff1a;清理了缓存目录、删掉旧版本、甚至把 /opt/todesk 整个目录移除后重装&#xff0c;结果客户端要么不弹窗&#xff0c;要么命令行报错&#xff0c;要么远程连上去一直转圈。这时候群里的…

作者头像 李华
网站建设 2026/9/18 3:18:04

毕夏AI官网的AIPPT:当“做PPT”变成“说PPT”

毕夏AI官网 www.bixiaai.com 毕夏AI写作官网 www.bixiaai.com 毕夏官网 www.bixiaai.com 毕夏智能写作官网 www.bixiaai.com 你有没有算过一笔账&#xff1f; 一篇开题报告论文写完&#xff0c;可能花了你三周。但从论文到能站在答辩现场的那份PPT&#xff0c;你还要再花…

作者头像 李华