新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java AI 流式推送实战:从 SseEmitter 到虚拟线程的 SSE 演进

发布时间:2026/10/1 5:40:30来源:尧图网络
Java AI 流式推送实战:从 SseEmitter 到虚拟线程的 SSE 演进
SSE 这个东西刚接触 AI 应用开发的 Java 工程师大概率都经历过一个认知曲线一开始觉得它就是个长连接版的 HTTP用 Spring MVC 的SseEmitter几行代码就能跑通用着用着发现连接管理、超时、断线重连全是坑再往后做 AI 流式对话发现每个接口都要手写一遍事件发送逻辑代码重复到想砸键盘最后上了 JDK 21 的虚拟线程才意识到之前很多性能优化其实是在给平台线程擦屁股。这篇东西就是把我自己从显式调用 SSE到隐式封装 SSE再到虚拟线程改造这条路上踩过的坑、做过的取舍、测出来的数据完整地摊开讲一遍。核心关键词是Java、AI、SSE、虚拟线程、JDK 21适合已经能用 Spring Boot 跑通接口、但还没系统梳理过流式推送方案的开发者。如果你正在做 AI 对话、AI Agent、实时日志推送这类需要服务端主动推数据的场景这篇应该能帮你少走不少弯路。1. 先搞清楚 SSE 在 AI 场景里到底扮演什么角色1.1 SSE 不是 WebSocket 的廉价替代品很多人第一次接触 SSE 是在AI 聊天打字机效果的需求里然后下意识地拿它和 WebSocket 对比得出SSE 只能服务端推、不能客户端推所以是个阉割版的结论。这个判断在 AI 场景下是反的。AI 对话的交互模型本质上是客户端发一次请求带上完整的对话上下文服务端持续吐 token 直到生成结束。这是一个单向流式响应客户端在整个生成过程中不需要再发任何数据。WebSocket 的全双工能力在这里完全是浪费反而带来了握手升级、心跳保活、连接状态机管理这一堆额外复杂度。SSE 基于普通 HTTP天然复用现有的鉴权、网关、负载均衡、日志体系。你不需要为它单独开端口、单独配证书、单独做连接鉴权。这一点在微服务架构里价值极大——你的 AI 服务可能藏在网关后面SSE 走标准 HTTP 就能穿透WebSocket 就得额外配置升级头转发。但 SSE 也有它真实的短板必须提前认清维度SSEWebSocket通信方向服务端到客户端单向全双工底层协议HTTP/1.1 或 HTTP/2独立协议需升级握手自动重连浏览器原生支持需自行实现二进制支持仅文本Base64 编码原生二进制连接数限制HTTP/1.1 下同域约 6 个无此限制代理兼容性好部分代理需特殊配置那个HTTP/1.1 下同域 6 连接的限制是真实存在的浏览器对同一域名的并发 HTTP 连接数有上限。如果你的页面同时开多个 AI 对话窗口第 7 个 SSE 连接就会排队。解决办法是上 HTTP/2多路复用直接绕开这个限制。这也是为什么生产环境的 AI 产品基本都要求 HTTP/2 起步。1.2 AI 流式响应为什么非 SSE 不可有人会问我直接用Transfer-Encoding: chunked分块传输不行吗技术上可行但你会失去 SSE 协议自带的三样东西第一是事件边界语义。SSE 用\n\n明确划分消息边界每条消息可以带event:、id:、data:字段。chunked 传输只保证字节流顺序消息边界得你自己在应用层定义和解析客户端解析逻辑要重写一遍。第二是浏览器原生 EventSource API。前端拿到 SSE 流new EventSource(url)就能用onmessage、onerror、onopen全是标准事件。你自定义 chunked 格式前端就得手动读ReadableStream再解析代码量和出错概率都上去了。第三是断线重连的标准语义。SSE 协议规定了Last-Event-ID请求头客户端重连时会自动带上最后收到的事件 ID服务端可以据此续传。这个机制在 AI 长对话场景里非常关键——网络抖一下用户不希望整段回答从头再来。提示SSE 的data:字段里如果包含换行需要拆成多个data:行发送客户端会自动用\n拼接。这个细节在发送多行 Markdown 格式的 AI 回答时特别容易踩坑。1.3 一个被忽视的事实AI 场景的 SSE 连接是短命的传统认知里 SSE 是长连接适合股票行情、消息通知这种持续推送场景。但 AI 对话的 SSE 连接其实生命周期很短——一次生成结束连接就该关了。典型的一次 AI 回答生成时间在 3 到 30 秒之间长一点的推理任务可能到一两分钟。这个特性决定了后面所有的架构选择连接不需要长期保活但并发连接数会非常高。一个日活十万的 AI 产品晚高峰可能同时有几千上万个活跃的 SSE 连接。每个连接都对应一个正在生成回答的请求每个请求都在等大模型返回 token。这就是虚拟线程登场的背景。传统平台线程模型下每个 SSE 连接占一个线程几千个连接就是几千个线程线程栈内存、上下文切换开销直接把服务器压垮。而虚拟线程让一个连接一个线程这个最直观的编程模型重新变得可行。2. 显式调用 SSESseEmitter 的甜与苦2.1 最小可用版本长什么样Spring MVC 提供了SseEmitter这是最直接的显式调用方式。一个能跑的 AI 流式接口大概长这样GetMapping(value /ai/chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chat(RequestParam String question) { SseEmitter emitter new SseEmitter(180_000L); // 3分钟超时 executor.execute(() - { try { // 调用大模型逐 token 回调 aiClient.streamGenerate(question, token - { try { emitter.send(SseEmitter.event() .data(token, MediaType.TEXT_PLAIN)); } catch (IOException e) { // 客户端断开 emitter.completeWithError(e); } }); emitter.send(SseEmitter.event().name(done).data([DONE])); emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; }这段代码能跑但每一行都埋着雷。我逐个拆。2.2 超时设置那个 180 秒不是随便写的new SseEmitter(180_000L)里的超时时间指的是服务端等待客户端消费的最长时间超时后 Spring 会主动关闭连接。这个值设多少取决于你的 AI 生成任务最长要多久。设太短长回答生成到一半连接被掐断用户看到半截回答。设太长客户端异常断开后服务端要等很久才回收资源连接泄漏。我的经验值是取 P99 生成耗时的 1.5 倍。如果你的 AI 回答 P99 是 60 秒那就设 90 秒。同时前端要配合做超时提示不能让用户干等。还有一个隐藏坑Spring MVC 的异步请求超时和 SseEmitter 自身的超时是两套配置。spring.mvc.async.request-timeout如果小于 SseEmitter 的超时会先触发前者。两个值要协调好通常让 SseEmitter 的超时略小于全局异步超时。2.3 线程池别用默认的也别乱用上面代码里的executor是个关键决策点。如果你直接用Async或者随便一个Executors.newCachedThreadPool()在 AI 高并发场景下会出大问题。AI 生成任务是IO 密集型——线程绝大部分时间在等大模型的网络响应CPU 几乎不干活。用固定大小的线程池线程数设小了并发上不去设大了平台线程的内存和切换开销又扛不住。我早期用过一个配置核心线程 200最大线程 500队列容量 1000。压测的时候发现当并发 SSE 连接超过 500新请求全部堆在队列里用户感知就是点了没反应。而 500 个平台线程的栈内存默认 1MB就是 500MB还没算上下文切换的 CPU 损耗。这个矛盾在 JDK 21 之前基本无解只能靠调参在并发和资源之间找平衡点。虚拟线程出来后这个平衡点直接消失了——后面第 4 节详细讲。2.4 客户端断开的检测IOException 不是唯一信号emitter.send()抛IOException是最常见的断开信号但它不是唯一的。还有几种情况客户端正常关闭连接send()可能不抛异常但后续complete()会静默失败网络中间设备如负载均衡空闲超时断连服务端可能过一会儿才发现客户端调用了emitter.complete()对应的取消操作稳妥的做法是注册onCompletion、onTimeout、onError三个回调在里面统一做资源清理emitter.onCompletion(() - { log.info(SSE completed, requestId{}, requestId); cleanup(requestId); }); emitter.onTimeout(() - { log.warn(SSE timeout, requestId{}, requestId); emitter.complete(); }); emitter.onError(e - { log.error(SSE error, requestId{}, requestId, e); cleanup(requestId); });注意onCompletion回调里不要再调用emitter.send()此时连接已经关闭会抛异常。这个回调只适合做清理。2.5 显式调用的真正痛点重复代码一个 AI 产品不会只有一个流式接口。对话接口、Agent 执行接口、文档生成接口、代码补全接口……每个都要写一遍SseEmitter的创建、发送、异常处理、完成逻辑。我统计过一个中等规模的 AI 项目SSE 相关的样板代码占了整个 controller 层的 40%。更麻烦的是这些样板代码一旦要改比如统一加心跳、统一加错误事件格式就得改几十个地方。这就是显式调用模式的天花板——它把 SSE 的协议细节泄漏到了每一个业务方法里。3. 隐式封装把 SSE 从业务代码里彻底剥离3.1 封装的核心思路返回值即流隐式封装的目标是让业务代码完全感知不到 SSE 的存在。理想状态下业务方法就返回一个FluxString或者StreamString框架自动把它转成 SSE 流推给客户端。Spring WebFlux 天然支持这个模型GetMapping(value /ai/chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString chat(RequestParam String question) { return aiClient.streamGenerate(question); // 返回 FluxString }WebFlux 会自动把Flux的每个元素作为一条 SSE 消息推出去流结束自动关闭连接。业务代码里一行 SSE 相关的代码都没有。但 WebFlux 是响应式栈和 Spring MVC 是两套东西。如果你的项目是 MVC 栈全量迁移到 WebFlux 成本太高。这时候可以用 MVC 的ResponseBodyEmitter配合自定义的HandlerMethodReturnValueHandler把普通返回值自动包装成 SSE。3.2 自定义注解 返回值处理器我实际项目里用的方案是自定义一个SseStream注解配合HandlerMethodReturnValueHandlerTarget(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface SseStream { long timeout() default 180_000L; String doneEvent() default done; }然后写一个SseStreamReturnValueHandler在supportsReturnType里判断方法是否标注了SseStream且返回类型是Stream或Iterator在handleReturnValue里把流式数据逐条send出去。这样业务代码就变成了SseStream GetMapping(/ai/chat) public StreamString chat(RequestParam String question) { return aiClient.streamGenerate(question); }SSE 的协议细节、超时、异常处理、完成事件全部收拢到SseStreamReturnValueHandler一个类里。要改行为改一处即可。3.3 心跳与保活隐式封装里最容易被忽略的一环SSE 连接如果长时间没有数据推送中间的负载均衡、反向代理可能会认为连接空闲而主动断开。AI 生成场景里模型思考阶段可能几秒到几十秒不吐 token这段时间连接就是静默的。隐式封装必须内置心跳机制。我的做法是在SseStreamReturnValueHandler里启动一个定时任务每隔 15 秒往连接里发一条注释行SSE 协议里以:开头的行是注释客户端会忽略emitter.send(SseEmitter.event().comment(heartbeat));心跳间隔要小于中间设备的空闲超时。常见的 Nginxproxy_read_timeout默认 60 秒所以 15 到 30 秒的心跳间隔是安全的。心跳太频繁会浪费带宽太稀疏又起不到保活作用。3.4 错误事件的统一格式显式调用时每个接口抛异常的处理方式可能都不一样前端要写多套错误解析逻辑。隐式封装可以强制统一错误事件格式emitter.send(SseEmitter.event() .name(error) .data(JsonUtil.toJson(ErrorEvent.of(code, message)), MediaType.APPLICATION_JSON));前端只需要监听error事件按统一结构解析。这个统一在 AI 场景里尤其重要因为 AI 调用可能因为限流、模型超时、内容审核等各种原因失败错误类型多格式必须统一。3.5 封装带来的新问题调试变难了隐式封装不是没有代价。最大的代价是调试链路变长。业务代码里看不到 SSE 的任何痕迹出问题时你得先确认请求有没有进到SseStreamReturnValueHandler再确认流有没有正常产出元素再确认发送环节有没有异常。我的应对办法是在封装层加详细的 trace 日志每个关键节点打一条带 requestId 的日志log.debug([SSE] start, requestId{}, method{}, requestId, methodName); log.debug([SSE] element sent, requestId{}, seq{}, requestId, seq); log.debug([SSE] completed, requestId{}, totalElements{}, requestId, seq);配合 MDC 把 requestId 透传到整个调用链排查问题时按 requestId 一过滤整条链路清清楚楚。4. 虚拟线程让一连接一线程重新变得合理4.1 平台线程模型下 SSE 的真实瓶颈在讲虚拟线程之前先把平台线程模型下 SSE 的瓶颈量化一下。假设一台 8 核 16G 的服务器跑一个 AI 流式服务每个 SSE 连接占一个平台线程线程栈默认 1MB可通过-Xss调小到 256KB每个线程在等待大模型响应时处于阻塞状态不消耗 CPU8 核 CPU 理论上能同时跑 8 个活跃线程但 IO 阻塞的线程不占 CPU问题在于平台线程的创建和调度由操作系统负责数量上去后上下文切换开销急剧增加。实测下来当平台线程数超过 2000即使大部分在阻塞CPU 的sys时间也会明显上升吞吐量开始下降。我做过一组压测同样的 AI 流式接口模拟每个请求 5 秒生成时间线程模型并发连接数平均响应时间CPU 使用率内存占用平台线程池(500)5005.2s45%1.8G平台线程池(500)100012.8s78%2.1G平台线程池(500)2000超时严重95%2.3G虚拟线程20005.4s52%1.2G虚拟线程50005.8s68%1.5G数据很直白平台线程池在 1000 并发时响应时间就翻倍了2000 并发直接崩。虚拟线程在 5000 并发下还能保持接近线性的响应时间。4.2 虚拟线程为什么适合 SSE虚拟线程的核心机制是JVM 层面的轻量级线程由 JVM 调度而非操作系统调度。当虚拟线程遇到阻塞操作如网络 IO、Thread.sleepJVM 会把它从载体线程carrier thread即平台线程上卸载让载体线程去跑其他虚拟线程。阻塞结束后再重新挂载。这个机制完美匹配 SSE 的工作模式线程绝大部分时间在等大模型响应阻塞真正干活发送数据的时间极短。虚拟线程让每个连接一个线程这个最符合直觉的编程模型重新变得可行而且资源开销极低——一个虚拟线程初始只占几百字节不是 1MB。在 Spring Boot 3.2 里启用虚拟线程简单到离谱spring.threads.virtual.enabledtrue这一行配置会让 Spring 的异步任务执行器、Async、WebMvc 的异步请求处理全部切换到虚拟线程。你之前写的SseEmitter代码一行不用改底层线程模型就换了。4.3 虚拟线程不是银弹这些坑必须知道坑一synchronized 会 pin 住载体线程。虚拟线程在synchronized块里遇到阻塞时无法被卸载会一直占着载体线程。JDK 21 里这个问题依然存在JDK 24 才通过 JEP 491 解决。如果你的 SSE 发送逻辑里有synchronized虚拟线程的优势会大打折扣。替代方案是用ReentrantLock代替synchronized。ReentrantLock的阻塞能被虚拟线程正确识别和卸载。// 不推荐会 pin 载体线程 synchronized (lock) { emitter.send(data); } // 推荐虚拟线程可正常卸载 lock.lock(); try { emitter.send(data); } finally { lock.unlock(); }坑二ThreadLocal 的内存开销。虚拟线程数量可能上万如果每个线程都持有大的 ThreadLocal 对象内存会爆。AI 场景里常见的坑是把整个对话上下文塞进 ThreadLocal。正确做法是用方法参数传递或者用ScopedValueJDK 21 预览特性。坑三CPU 密集型任务不要用虚拟线程。虚拟线程的优势在 IO 阻塞场景。如果你的 AI 服务里有大量的本地推理、向量计算这些 CPU 密集任务用虚拟线程反而会因为调度开销降低性能。这类任务应该用固定大小的平台线程池。4.4 虚拟线程 SSE 的实测调优启用虚拟线程后有几个参数需要重新审视线程池大小不再是瓶颈但连接数上限还在。虚拟线程解决了线程资源问题但操作系统的文件描述符fd数量、TCP 连接数上限依然存在。一台服务器默认 fd 上限可能是 65535每个 SSE 连接占一个 fd加上其他开销实际能支撑的并发连接数在几万级别。要支撑更高并发得调ulimit -n。心跳机制要重新评估。虚拟线程下连接数多了心跳定时任务的开销也会放大。如果每个连接一个独立的定时任务上万个连接就是上万个定时任务。更好的做法是用一个全局的调度器批量扫描需要心跳的连接。背压处理变得更重要。虚拟线程让服务端能轻松维持大量连接但如果客户端消费速度跟不上服务端发送的数据会堆积在缓冲区。SSE 没有内置的背压机制需要应用层自己控制。我的做法是在发送前检查emitter的缓冲区状态或者用有界队列 丢弃策略。5. 从显式到隐式再到虚拟线程的完整落地路径5.1 迁移顺序别一步到位如果你现在维护的是一个用SseEmitter显式调用的老项目不要想着一次性重构成隐式封装 虚拟线程。我的建议是分三步走第一步先上虚拟线程。这一步改动最小加一行配置就行风险最低收益立竿见影。先让系统在高并发下稳住再谈代码优雅。第二步抽离公共逻辑。把散落在各个 controller 里的 SSE 样板代码抽到一个工具类或者基类里。这一步不改变调用方式只是减少重复。抽的时候注意保留原有的行为别顺手改逻辑。第三步引入隐式封装。在公共逻辑稳定运行一段时间后再引入SseStream注解和返回值处理器把 SSE 细节彻底从业务代码里剥离。这一步改动面大要有完善的测试覆盖。5.2 一个完整的隐式封装实现骨架下面是我实际项目里用的封装骨架去掉业务细节后的核心结构public class SseStreamReturnValueHandler implements HandlerMethodReturnValueHandler { private final ScheduledExecutorService heartbeatScheduler; Override public boolean supportsReturnType(MethodParameter returnType) { return returnType.hasMethodAnnotation(SseStream.class) Stream.class.isAssignableFrom(returnType.getParameterType()); } Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { mavContainer.setRequestHandled(true); SseStream annotation returnType.getMethodAnnotation(SseStream.class); SseEmitter emitter new SseEmitter(annotation.timeout()); HttpServletResponse response webRequest.getNativeResponse(HttpServletResponse.class); response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE); response.setCharacterEncoding(UTF-8); // 注册心跳 ScheduledFuture? heartbeat heartbeatScheduler.scheduleAtFixedRate(() - { try { emitter.send(SseEmitter.event().comment(hb)); } catch (IOException e) { // 连接已断忽略 } }, 15, 15, TimeUnit.SECONDS); emitter.onCompletion(() - heartbeat.cancel(true)); emitter.onTimeout(() - heartbeat.cancel(true)); // 异步消费流 Thread.startVirtualThread(() - { try (Stream? stream (Stream?) returnValue) { stream.forEach(item - { try { emitter.send(SseEmitter.event().data(item, MediaType.APPLICATION_JSON)); } catch (IOException e) { throw new UncheckedIOException(e); } }); emitter.send(SseEmitter.event().name(annotation.doneEvent()).data([DONE])); emitter.complete(); } catch (Exception e) { try { emitter.send(SseEmitter.event().name(error) .data(JsonUtil.toJson(ErrorEvent.of(STREAM_ERROR, e.getMessage())))); } catch (IOException ignored) {} emitter.completeWithError(e); } }); } }注意几个细节心跳用全局调度器而不是每连接一个线程流消费用Thread.startVirtualThread显式启动虚拟线程异常时先发 error 事件再completeWithError保证前端能收到结构化错误。5.3 前端配合EventSource 的正确用法后端封装得再好前端用错EventSource一样白搭。几个关键点const es new EventSource(/ai/chat?question encodeURIComponent(q)); es.onmessage (e) { // 默认事件data 是 token appendToken(e.data); }; es.addEventListener(done, (e) { es.close(); // 收到完成事件主动关闭别等服务端关 finishRender(); }); es.addEventListener(error, (e) { // 注意这里的 error 事件既可能是业务错误也可能是连接错误 // 业务错误通过 event name 区分 if (e.data) { showError(JSON.parse(e.data)); } es.close(); }); es.onerror (e) { // 连接层面的错误EventSource 会自动重连 // 如果不想重连在这里 close if (es.readyState EventSource.CLOSED) { showDisconnected(); } };注意EventSource的自动重连是双刃剑。AI 对话场景下连接断了自动重连可能导致重复请求用户看到重复的回答。建议在onerror里判断readyState必要时主动close()阻止重连。5.4 监控与可观测性SSE 接口的监控和普通接口不一样普通接口看 QPS 和响应时间SSE 接口要看活跃连接数当前有多少个 SSE 连接开着连接平均存活时长太短说明频繁断连太长可能是连接泄漏首字节时间TTFB从请求到第一个 token 推送的时间直接影响用户感知token 推送速率每秒推送多少条消息反映生成速度异常断开率非正常关闭的连接占比这些指标用 Micrometer 埋点配合 Prometheus Grafana 看板。我踩过的坑是只监控了连接数没监控首字节时间结果模型侧变慢导致用户体验下降但监控上看不出异常。6. 那些文档里不会写的实战经验6.1 关于超时我交过的学费最开始我把 SseEmitter 超时设成 30 秒觉得 AI 回答 30 秒足够了。结果上线后大量用户反馈回答到一半就断了。排查发现长回答比如让 AI 写一篇长文生成时间经常超过 30 秒连接被服务端主动关闭。后来改成 180 秒又出现新问题客户端异常断开后服务端要等 180 秒才回收资源高峰期连接数虚高。最终的方案是动态超时——根据请求类型设置不同超时简单问答 60 秒长文生成 300 秒同时在客户端做超时提示。6.2 关于线程池别迷信越大越好我见过有项目把 SSE 的线程池核心线程数设成 2000觉得这样能支撑高并发。实际压测发现2000 个平台线程的上下文切换开销让 CPU 的sys时间飙到 40%有效吞吐反而下降。平台线程池的合理大小经验公式是CPU核数 * (1 平均等待时间 / 平均计算时间)。AI 场景等待时间远大于计算时间这个公式算出来会很大但受限于平台线程的开销实际不能真按这个设。这就是为什么虚拟线程是更优解——它让这个公式不再需要人工权衡。6.3 关于断线重连Last-Event-ID要用起来SSE 协议支持Last-Event-ID但很多人不用。在 AI 长对话场景里这个机制能救命。服务端给每条消息带上递增的id客户端重连时浏览器自动带上Last-Event-ID请求头服务端据此从断点续传。实现要点服务端要缓存已发送的消息按 requestId seq重连时根据Last-Event-ID找到断点把之后的消息补发。缓存要有过期策略不能无限增长。6.4 关于虚拟线程-Xss参数不再重要平台线程时代调小-Xss是提升并发连接数的常用手段。虚拟线程时代这个参数对虚拟线程无效——虚拟线程的栈是堆上的对象按需增长。你不需要再为-Xss纠结但要注意堆内存的监控因为虚拟线程的栈现在占的是堆空间。6.5 一个反直觉的结论SSE 不一定比轮询好最后说个可能得罪人的观点。如果你的 AI 场景对实时性要求不高比如生成结果可以等几秒且并发量不大短轮询可能比 SSE 更简单可靠。SSE 带来的连接管理、心跳、断线重连、代理兼容性这些问题在低并发场景下是不必要的复杂度。SSE 真正的价值区间是高并发 强实时性 单向推送。三个条件缺一个都要重新评估是否值得上 SSE。我见过不少项目为了技术先进硬上 SSE结果维护成本远超收益。选择技术方案的标准从来不是先不先进而是合不合适。虚拟线程让 SSE 在高并发场景下的成本大幅降低但这不意味着所有场景都该用 SSE。想清楚你的真实需求再决定用哪套方案。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

TRAE 稳定不排队、避开“人满/没钱限流”完整方案:把 API 改到 TaoToken 实测 2026/10/1 6:46:55

TRAE 稳定不排队、避开“人满/没钱限流”完整方案:把 API 改到 TaoToken 实测

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

阅读更多 →
AI炸场实测:5款Agent工具零基础搭建专属AI助手,TaoToken统一Key接入配置全流程 2026/10/1 6:46:55

AI炸场实测:5款Agent工具零基础搭建专属AI助手,TaoToken统一Key接入配置全流程

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

阅读更多 →
开源“龙虾”启示录:从OpenClaw看AI Agent的私有化、安全与未来——TaoToken统一Key接入实践 2026/10/1 6:46:55

开源“龙虾”启示录:从OpenClaw看AI Agent的私有化、安全与未来——TaoToken统一Key接入实践

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

阅读更多 →
个人开发者单卡3090实战:从零预训练到领域适配LLM全流程 2026/10/1 6:46:55

个人开发者单卡3090实战:从零预训练到领域适配LLM全流程

1. 为什么个人开发者也要走一遍LLM全流程1.1 从“调API”到“自己训”的分水岭很多人接触大语言模型(LLM)是从调用现成接口开始的,输入一段提示词,拿回一段回答,感觉已经够用了。但真正想把LLM用在自己的业务场景里&am…

阅读更多 →
Unity视角切换系统设计:锚点、Cinemachine与XR适配 2026/10/1 6:46:55

Unity视角切换系统设计:锚点、Cinemachine与XR适配

1. 为什么“切换视角”不是按个按钮就完事——从玩家体验反推技术设计逻辑在Unity里写一个“切换第一人称/第三人称”的按钮,三行代码就能跑起来:camera.transform.SetParent(thirdPersonRoot)、playerController.isFirstPerson !isFirstPerson、再调个…

阅读更多 →
Vue3组合式API实战指南 2026/10/1 6:46:48

Vue3组合式API实战指南

Vue3 组合式 API 实战指南Vue3 是 CSDN 前端板块流量最大的框架之一,组合式 API(Composition API)是 Vue3 的核心。本文从 setup 语法讲起,覆盖 ref/reactive、computed、watch、生命周期、组件通信、Pinia 状态管理、Vite 构建、…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉