<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>无尘阁日记</title><link>https://www.jiangtian.top/</link><description>无尘阁，红尘炼心。君子性非异也，善假于物也。</description><item><title>SSE 协议小白指南：服务器主动&amp;quot;喊&amp;quot;你一嗓子</title><link>https://www.jiangtian.top/?id=16367</link><description>&lt;p&gt;如果你做过网页，一定熟悉这种模式：浏览器问服务器要数据，服务器给了，对话就结束了。那如果服务器想主动告诉你”有新消息了”，怎么办？&lt;/p&gt;
&lt;p&gt;刷新页面？太蠢。轮询（每隔几秒问一次）？浪费流量。WebSocket？有点重。&lt;/p&gt;
&lt;p&gt;这时候就该 &lt;strong&gt;SSE（Server-Sent Events，服务器发送事件）&lt;/strong&gt; 出场了。它专门解决一个简单问题：&lt;strong&gt;让服务器能主动、持续地给浏览器推消息&lt;/strong&gt;，而且实现起来比 WebSocket 简单十倍。&lt;/p&gt;
&lt;p&gt;下面用 6 个核心要点 + 6 段可直接运行的代码，带你彻底搞懂 SSE。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;它是个单行道只能从服务器流向浏览器&quot;&gt;1. 它是个”单行道”：只能从服务器流向浏览器&lt;/h2&gt;
&lt;p&gt;SSE 最大的特点就是&lt;strong&gt;单向&lt;/strong&gt;。浏览器连上之后，服务器可以随时往浏览器发消息，但浏览器不能通过这条通道回传数据（要回传得另开一条 HTTP 请求）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;类比：SSE 像广播电台，服务器是电台，浏览器是收音机。电台可以一直播，收音机只能听，不能对着电台喊话。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;例子一个最简单的-sse-服务端node.js&quot;&gt;例子：一个最简单的 SSE 服务端（Node.js）&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb1&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb1-1&quot;&gt;&lt;a href=&quot;#cb1-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; http &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;pp&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;http&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-2&quot;&gt;&lt;a href=&quot;#cb1-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-3&quot;&gt;&lt;a href=&quot;#cb1-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;http&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createServer&lt;/span&gt;((req&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; res) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb1-4&quot;&gt;&lt;a href=&quot;#cb1-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;co&quot;&gt;// 1. 设置响应头，这是 SSE 的&amp;quot;身份证&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-5&quot;&gt;&lt;a href=&quot;#cb1-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;writeHead&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb1-6&quot;&gt;&lt;a href=&quot;#cb1-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;text/event-stream&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-7&quot;&gt;&lt;a href=&quot;#cb1-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;#39;Cache-Control&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;no-cache&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-8&quot;&gt;&lt;a href=&quot;#cb1-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;#39;Connection&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;keep-alive&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-9&quot;&gt;&lt;a href=&quot;#cb1-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-10&quot;&gt;&lt;a href=&quot;#cb1-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-11&quot;&gt;&lt;a href=&quot;#cb1-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;co&quot;&gt;// 2. 每隔 2 秒推一条消息&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-12&quot;&gt;&lt;a href=&quot;#cb1-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;let&lt;/span&gt; count &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-13&quot;&gt;&lt;a href=&quot;#cb1-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; timer &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;pp&quot;&gt;setInterval&lt;/span&gt;(() &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb1-14&quot;&gt;&lt;a href=&quot;#cb1-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    count&lt;span class=&quot;op&quot;&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-15&quot;&gt;&lt;a href=&quot;#cb1-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u7b2c${&lt;/span&gt;count&lt;span class=&quot;sc&quot;&gt;}\u6761\u6d88\u606f\uff0c\u65f6\u95f4\uff1a${&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Date&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toLocaleTimeString&lt;/span&gt;()&lt;span class=&quot;sc&quot;&gt;}\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-16&quot;&gt;&lt;a href=&quot;#cb1-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; (count &lt;span class=&quot;op&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;5&lt;/span&gt;) {&lt;/span&gt;
&lt;span id=&quot;cb1-17&quot;&gt;&lt;a href=&quot;#cb1-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;pp&quot;&gt;clearInterval&lt;/span&gt;(timer)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-18&quot;&gt;&lt;a href=&quot;#cb1-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;end&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-19&quot;&gt;&lt;a href=&quot;#cb1-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    }&lt;/span&gt;
&lt;span id=&quot;cb1-20&quot;&gt;&lt;a href=&quot;#cb1-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  }&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;2000&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-21&quot;&gt;&lt;a href=&quot;#cb1-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb1-22&quot;&gt;&lt;a href=&quot;#cb1-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;3000&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; () &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;SSE &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u670d\u52a1\u8dd1\u5728&lt;/span&gt;&lt;span class=&quot;st&quot;&gt; http://localhost:3000&amp;#39;&lt;/span&gt;))&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;关键看第 9 行&lt;/strong&gt;：&lt;code&gt;Content-Type: text/event-stream&lt;/code&gt;，只要带上这个头，浏览器就知道”哦，这是 SSE，我要用 EventSource 来接收”。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;消息格式极其死板必须以-data-开头&quot;&gt;2. 消息格式极其死板：必须以 &lt;code&gt;data:&lt;/code&gt; 开头&lt;/h2&gt;
&lt;p&gt;SSE 的消息不是随便写的，它有一套固定格式。每条消息以 &lt;code&gt;data:&lt;/code&gt; 打头，以两个换行符 &lt;code&gt;\n\n&lt;/code&gt; 结尾。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data: \u8fd9\u662f\u4e00\u6761\u6d88\u606f\n\n&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你要发多行，每行都要加 &lt;code&gt;data:&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data: \u7b2c\u4e00\u884c\ndata: \u7b2c\u4e8c\u884c\n\n&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器收到后，会自动把多行拼接成一个字符串，中间用换行符连接。&lt;/p&gt;
&lt;h3 id=&quot;例子服务端发多行数据&quot;&gt;例子：服务端发多行数据&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb4&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb4-1&quot;&gt;&lt;a href=&quot;#cb4-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;writeHead&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb4-2&quot;&gt;&lt;a href=&quot;#cb4-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;st&quot;&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;text/event-stream&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-3&quot;&gt;&lt;a href=&quot;#cb4-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-4&quot;&gt;&lt;a href=&quot;#cb4-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-5&quot;&gt;&lt;a href=&quot;#cb4-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: {&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-6&quot;&gt;&lt;a href=&quot;#cb4-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data:   &amp;quot;user&amp;quot;: &amp;quot;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u6c5f\u6960&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;&amp;quot;,&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-7&quot;&gt;&lt;a href=&quot;#cb4-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data:   &amp;quot;action&amp;quot;: &amp;quot;login&amp;quot;,&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-8&quot;&gt;&lt;a href=&quot;#cb4-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data:   &amp;quot;time&amp;quot;: &amp;quot;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;Date&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toISOString&lt;/span&gt;()&lt;span class=&quot;sc&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb4-9&quot;&gt;&lt;a href=&quot;#cb4-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: }&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;浏览器 EventSource 收到的事件中，&lt;code&gt;event.data&lt;/code&gt; 会是：&lt;/p&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb5&quot;&gt;&lt;pre class=&quot;sourceCode json&quot;&gt;&lt;code class=&quot;sourceCode json&quot;&gt;&lt;span id=&quot;cb5-1&quot;&gt;&lt;a href=&quot;#cb5-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-2&quot;&gt;&lt;a href=&quot;#cb5-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;\u6c5f\u6960&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-3&quot;&gt;&lt;a href=&quot;#cb5-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;quot;action&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;login&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-4&quot;&gt;&lt;a href=&quot;#cb5-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;quot;2026-08-04T12:34:56.789Z&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb5-5&quot;&gt;&lt;a href=&quot;#cb5-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;fu&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;浏览器用-eventsource-接收三行代码搞定&quot;&gt;3. 浏览器用 &lt;code&gt;EventSource&lt;/code&gt; 接收，三行代码搞定&lt;/h2&gt;
&lt;p&gt;浏览器原生提供了 &lt;code&gt;EventSource&lt;/code&gt; 对象，不需要任何库。&lt;/p&gt;
&lt;h3 id=&quot;例子前端接收-sse&quot;&gt;例子：前端接收 SSE&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb6&quot;&gt;&lt;pre class=&quot;sourceCode html&quot;&gt;&lt;code class=&quot;sourceCode html&quot;&gt;&lt;span id=&quot;cb6-1&quot;&gt;&lt;a href=&quot;#cb6-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;!DOCTYPE&lt;/span&gt; html&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-2&quot;&gt;&lt;a href=&quot;#cb6-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-3&quot;&gt;&lt;a href=&quot;#cb6-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-4&quot;&gt;&lt;a href=&quot;#cb6-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;SSE \u5b9e\u65f6\u6d88\u606f&lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-5&quot;&gt;&lt;a href=&quot;#cb6-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;ot&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;quot;list&amp;quot;&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;ul&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-6&quot;&gt;&lt;a href=&quot;#cb6-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-7&quot;&gt;&lt;a href=&quot;#cb6-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-8&quot;&gt;&lt;a href=&quot;#cb6-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// \u53ea\u9700\u8981\u8fd9\u4e00\u884c\uff0c\u8fde\u63a5\u670d\u52a1\u7aef&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-9&quot;&gt;&lt;a href=&quot;#cb6-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; es &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;EventSource&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;http://localhost:3000&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-10&quot;&gt;&lt;a href=&quot;#cb6-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-11&quot;&gt;&lt;a href=&quot;#cb6-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// \u6bcf\u6b21\u6536\u5230\u6d88\u606f\u5c31\u89e6\u53d1&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-12&quot;&gt;&lt;a href=&quot;#cb6-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    es&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;onmessage&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; (&lt;span class=&quot;bu&quot;&gt;event&lt;/span&gt;) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb6-13&quot;&gt;&lt;a href=&quot;#cb6-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; li &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createElement&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;li&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-14&quot;&gt;&lt;a href=&quot;#cb6-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      li&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-15&quot;&gt;&lt;a href=&quot;#cb6-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;bu&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;list&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;appendChild&lt;/span&gt;(li)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-16&quot;&gt;&lt;a href=&quot;#cb6-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    }&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-17&quot;&gt;&lt;a href=&quot;#cb6-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-18&quot;&gt;&lt;a href=&quot;#cb6-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;co&quot;&gt;// \u51fa\u9519\u5904\u7406&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-19&quot;&gt;&lt;a href=&quot;#cb6-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    es&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; (err) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb6-20&quot;&gt;&lt;a href=&quot;#cb6-20&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;bu&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;log&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u8fde\u63a5\u65ad\u5f00\uff0c\u6b63\u5728\u91cd\u8fde&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;...&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-21&quot;&gt;&lt;a href=&quot;#cb6-21&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    }&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-22&quot;&gt;&lt;a href=&quot;#cb6-22&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-23&quot;&gt;&lt;a href=&quot;#cb6-23&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb6-24&quot;&gt;&lt;a href=&quot;#cb6-24&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;dt&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;kw&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;dt&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;就这么简单。&lt;code&gt;new EventSource(url)&lt;/code&gt; 创建连接，&lt;code&gt;onmessage&lt;/code&gt; 收消息，&lt;code&gt;onerror&lt;/code&gt; 处理断开。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;支持自定义事件名不只是-message&quot;&gt;4. 支持自定义事件名：不只是 &lt;code&gt;message&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;默认情况下，所有消息都触发 &lt;code&gt;onmessage&lt;/code&gt;。但你可以给消息分类，比如有的叫 &lt;code&gt;price&lt;/code&gt;，有的叫 &lt;code&gt;notice&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;例子服务端发送带事件名的消息&quot;&gt;例子：服务端发送带事件名的消息&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb7&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb7-1&quot;&gt;&lt;a href=&quot;#cb7-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// \u63a8\u9001\u80a1\u7968\u4ef7\u683c&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-2&quot;&gt;&lt;a href=&quot;#cb7-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`event: price&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-3&quot;&gt;&lt;a href=&quot;#cb7-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: 38.50&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-4&quot;&gt;&lt;a href=&quot;#cb7-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-5&quot;&gt;&lt;a href=&quot;#cb7-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// \u63a8\u9001\u7cfb\u7edf\u901a\u77e5&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-6&quot;&gt;&lt;a href=&quot;#cb7-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`event: notice&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb7-7&quot;&gt;&lt;a href=&quot;#cb7-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u60a8\u7684\u8ba2\u5355\u5df2\u53d1\u8d27\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;前端用-addeventlistener-监听不同事件&quot;&gt;前端用 &lt;code&gt;addEventListener&lt;/code&gt; 监听不同事件&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb8&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb8-1&quot;&gt;&lt;a href=&quot;#cb8-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; es &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kw&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;bu&quot;&gt;EventSource&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;http://localhost:3000&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-2&quot;&gt;&lt;a href=&quot;#cb8-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-3&quot;&gt;&lt;a href=&quot;#cb8-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;es&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; (e) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb8-4&quot;&gt;&lt;a href=&quot;#cb8-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;bu&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;getElementById&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;price&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-5&quot;&gt;&lt;a href=&quot;#cb8-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-6&quot;&gt;&lt;a href=&quot;#cb8-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-7&quot;&gt;&lt;a href=&quot;#cb8-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;es&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;addEventListener&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;notice&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; (e) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb8-8&quot;&gt;&lt;a href=&quot;#cb8-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;fu&quot;&gt;alert&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u7cfb\u7edf\u901a\u77e5\uff1a&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;&lt;/span&gt; &lt;span class=&quot;op&quot;&gt;+&lt;/span&gt; e&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;data&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb8-9&quot;&gt;&lt;a href=&quot;#cb8-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样业务逻辑可以分门别类处理，代码更清爽。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;自带断线重连浏览器比你更操心&quot;&gt;5. 自带断线重连：浏览器比你更操心&lt;/h2&gt;
&lt;p&gt;SSE 有一个非常贴心的机制：&lt;strong&gt;如果连接断了，浏览器会自动帮你重连&lt;/strong&gt;，默认间隔约 3 秒。你什么都不用写。&lt;/p&gt;
&lt;p&gt;更妙的是，服务端可以控制重连间隔。比如你发一条 &lt;code&gt;retry: 10000&lt;/code&gt;，浏览器就会在断开后等 10 秒再重连。&lt;/p&gt;
&lt;h3 id=&quot;例子服务端设置重连间隔-消息-id&quot;&gt;例子：服务端设置重连间隔 + 消息 ID&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb9&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb9-1&quot;&gt;&lt;a href=&quot;#cb9-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;co&quot;&gt;// \u544a\u8bc9\u6d4f\u89c8\u5668\uff1a\u65ad\u5f00\u540e 10 \u79d2\u518d\u8bd5&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-2&quot;&gt;&lt;a href=&quot;#cb9-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`retry: 10000&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-3&quot;&gt;&lt;a href=&quot;#cb9-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-4&quot;&gt;&lt;a href=&quot;#cb9-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;let&lt;/span&gt; id &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-5&quot;&gt;&lt;a href=&quot;#cb9-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;pp&quot;&gt;setInterval&lt;/span&gt;(() &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb9-6&quot;&gt;&lt;a href=&quot;#cb9-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  id&lt;span class=&quot;op&quot;&gt;++;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-7&quot;&gt;&lt;a href=&quot;#cb9-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;co&quot;&gt;// \u6bcf\u6761\u6d88\u606f\u90fd\u5e26\u4e0a ID\uff0c\u65ad\u7ebf\u540e\u670d\u52a1\u7aef\u53ef\u4ee5\u4ece\u8fd9\u4e2a ID \u540e\u7eed\u4f20&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-8&quot;&gt;&lt;a href=&quot;#cb9-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`id: &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;id&lt;span class=&quot;sc&quot;&gt;}\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-9&quot;&gt;&lt;a href=&quot;#cb9-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\u6d88\u606f\u7f16\u53f7${&lt;/span&gt;id&lt;span class=&quot;sc&quot;&gt;}\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb9-10&quot;&gt;&lt;a href=&quot;#cb9-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;}&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dv&quot;&gt;2000&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;浏览器重连时，会在请求头里带上 &lt;code&gt;Last-Event-ID: 5&lt;/code&gt;，服务端读到后就知道”哦，客户端已经收到第 5 条了，我从第 6 条开始发”，&lt;strong&gt;实现断点续传&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;sse-vs-websocket别拿大炮打蚊子&quot;&gt;6. SSE vs WebSocket：别拿大炮打蚊子&lt;/h2&gt;
&lt;p&gt;很多人一听到”实时推送”就想到 WebSocket。但 SSE 在单工场景下更轻量。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;通信方向&lt;/td&gt;
&lt;td&gt;服务器→客户端（单工）&lt;/td&gt;
&lt;td&gt;全双工&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;协议&lt;/td&gt;
&lt;td&gt;HTTP（兼容性好）&lt;/td&gt;
&lt;td&gt;ws/wss（独立协议）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自动重连&lt;/td&gt;
&lt;td&gt;705 原生支持&lt;/td&gt;
&lt;td&gt;74c 需自己写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;消息 ID/断点续传&lt;/td&gt;
&lt;td&gt;705 原生支持&lt;/td&gt;
&lt;td&gt;74c 需自己实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;二进制支持&lt;/td&gt;
&lt;td&gt;74c 只能传文本&lt;/td&gt;
&lt;td&gt;705 支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;实时游戏/双向高频通信&lt;/td&gt;
&lt;td&gt;不合适&lt;/td&gt;
&lt;td&gt;合适&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;股票推送/日志流/通知&lt;/td&gt;
&lt;td&gt;完美&lt;/td&gt;
&lt;td&gt;过重&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;例子日志实时推送sse-比-websocket-更合适&quot;&gt;例子：日志实时推送（SSE 比 WebSocket 更合适）&lt;/h3&gt;
&lt;div class=&quot;sourceCode&quot; id=&quot;cb10&quot;&gt;&lt;pre class=&quot;sourceCode javascript&quot;&gt;&lt;code class=&quot;sourceCode javascript&quot;&gt;&lt;span id=&quot;cb10-1&quot;&gt;&lt;a href=&quot;#cb10-1&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;pp&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-2&quot;&gt;&lt;a href=&quot;#cb10-2&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; http &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;pp&quot;&gt;require&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;http&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-3&quot;&gt;&lt;a href=&quot;#cb10-3&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-4&quot;&gt;&lt;a href=&quot;#cb10-4&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;http&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createServer&lt;/span&gt;((req&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; res) &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb10-5&quot;&gt;&lt;a href=&quot;#cb10-5&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;writeHead&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb10-6&quot;&gt;&lt;a href=&quot;#cb10-6&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;text/event-stream&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-7&quot;&gt;&lt;a href=&quot;#cb10-7&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;st&quot;&gt;&amp;#39;Cache-Control&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;st&quot;&gt;&amp;#39;no-cache&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-8&quot;&gt;&lt;a href=&quot;#cb10-8&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-9&quot;&gt;&lt;a href=&quot;#cb10-9&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-10&quot;&gt;&lt;a href=&quot;#cb10-10&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;co&quot;&gt;// \u5b9e\u65f6\u8ffd\u8e2a app.log \u6587\u4ef6&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-11&quot;&gt;&lt;a href=&quot;#cb10-11&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;createReadStream&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;/var/log/app.log&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; { &lt;span class=&quot;dt&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;:&lt;/span&gt; fs&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;statSync&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;/var/log/app.log&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;at&quot;&gt;size&lt;/span&gt; })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-12&quot;&gt;&lt;a href=&quot;#cb10-12&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  stream&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;on&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;data&amp;#39;&lt;/span&gt;&lt;span class=&quot;op&quot;&gt;,&lt;/span&gt; chunk &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb10-13&quot;&gt;&lt;a href=&quot;#cb10-13&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    &lt;span class=&quot;kw&quot;&gt;const&lt;/span&gt; lines &lt;span class=&quot;op&quot;&gt;=&lt;/span&gt; chunk&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;toString&lt;/span&gt;()&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;split&lt;/span&gt;(&lt;span class=&quot;st&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;st&quot;&gt;&amp;#39;&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-14&quot;&gt;&lt;a href=&quot;#cb10-14&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    lines&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;forEach&lt;/span&gt;(line &lt;span class=&quot;kw&quot;&gt;=&amp;gt;&lt;/span&gt; {&lt;/span&gt;
&lt;span id=&quot;cb10-15&quot;&gt;&lt;a href=&quot;#cb10-15&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;      &lt;span class=&quot;cf&quot;&gt;if&lt;/span&gt; (line) res&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;write&lt;/span&gt;(&lt;span class=&quot;vs&quot;&gt;`data: &lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;${&lt;/span&gt;line&lt;span class=&quot;sc&quot;&gt;}\n\n&lt;/span&gt;&lt;span class=&quot;vs&quot;&gt;`&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-16&quot;&gt;&lt;a href=&quot;#cb10-16&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;    })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-17&quot;&gt;&lt;a href=&quot;#cb10-17&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;  })&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-18&quot;&gt;&lt;a href=&quot;#cb10-18&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id=&quot;cb10-19&quot;&gt;&lt;a href=&quot;#cb10-19&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;/a&gt;})&lt;span class=&quot;op&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;fu&quot;&gt;listen&lt;/span&gt;(&lt;span class=&quot;dv&quot;&gt;3000&lt;/span&gt;)&lt;span class=&quot;op&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;前端几行代码就能看实时日志，无需任何第三方库，也不需要处理 WebSocket 的握手和心跳。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;一句话总结&quot;&gt;一句话总结&lt;/h2&gt;
&lt;p&gt;SSE 就是&lt;strong&gt;基于 HTTP 的服务器单向推送协议&lt;/strong&gt;。格式简单（&lt;code&gt;data:&lt;/code&gt; + &lt;code&gt;\n\n&lt;/code&gt;），浏览器原生支持（&lt;code&gt;EventSource&lt;/code&gt;），自带重连和断点续传。如果你只需要服务器往浏览器发消息，完全没必要上 WebSocket，SSE 才是那把顺手的螺丝刀。&lt;/p&gt;</description><pubDate>Tue, 04 Aug 2026 18:53:46 +0800</pubDate></item><item><title>DeepSeek V4 Pro 到底强到什么程度？7月31日那次更新，把&amp;quot;参数神话&amp;quot;打穿了</title><link>https://www.jiangtian.top/?id=16365</link><description>&lt;p&gt;2026 过半了。DeepSeek V4 Pro 正式版还没影，但整个圈子已经坐不住了。&lt;/p&gt;
&lt;p&gt;为什么？因为 7 月 31 日，DeepSeek 在 API 文档的更新日志里，低调地挂出了一个叫 V4-Flash-0731 的东西。没有发布会，没有热搜通稿。但它干了一件让所有人愣住的事——一个总参数只有 Pro 六分之一的小模型，在智能体和代码基准上，把自家的 1.6 万亿参数旗舰预览版，反超了。&lt;/p&gt;
&lt;p&gt;我先说结论，省得你往下划：V4-Pro 现在就是开源模型的天花板，逼近闭源旗舰；但真正炸裂的，是这次 Flash 的后训练飞跃，它把”堆参数等于变强”这条行业铁律，踩了一脚刹车。&lt;/p&gt;
&lt;h2 id=&quot;v4-pro-本身已经够吓人&quot;&gt;V4-Pro 本身，已经够吓人&lt;/h2&gt;
&lt;p&gt;4 月 24 日 Preview 出来时，规格就摆在那：1.6 万亿总参数，每次只激活 490 亿；统一一百万 token 上下文。关键不在大，在省。新架构（CSA+HCA 混合稀疏注意力，加上 mHC 超连接和 Muon 优化器）把一百万 token 下的单 token 算力，压到 V3.2 的三成到一成，KV Cache 降到原来的十分之一不到。以前跑不动的超长任务，现在能跑了。&lt;/p&gt;
&lt;p&gt;能力上，SWE-bench Verified 拿到 80.6（V4-Pro-Max 版），跟 Claude Opus 4.6/4.7 一个身位；Apex Shortlist 90.2，这个分数已经压过顶级闭源；Terminal Bench 2.0 也干过了 GLM-5.1 Thinking 和 Kimi K2.6 Thinking。Artificial Analysis 给它的智能指数 52 分，比 V3.2 的 42 涨了 10 分，是仅次于 Kimi K2.6 的第二大开源推理模型。&lt;/p&gt;
&lt;p&gt;这些数字什么意思？意思是你花闭源三十分之一的钱，能拿到接近顶级闭源的交付质量。&lt;/p&gt;
&lt;h2 id=&quot;月-31-日那一下才是重点&quot;&gt;7 月 31 日那一下，才是重点&lt;/h2&gt;
&lt;p&gt;V4-Flash-0731，参数一点没加（还是 2840 亿总参、激活 130 亿），纯粹靠后训练重练了一遍。结果 Terminal Bench 2.1 从预览版的 61.8 直接拉到 82.7，反超了 V4-Pro 预览版的 72.1。九项智能体加代码基准，它全赢了自家的 Pro 预览版；DeepSWE 一项，分差 41.6 分；最离谱的是 ALE 基准，跟全球顶级的 Claude Opus 4.8 只差半分。&lt;/p&gt;
&lt;p&gt;换句话说：一个小了六倍的模型，把大哥按在地上摩擦。&lt;/p&gt;
&lt;p&gt;这事为什么重要？因为过去两年行业默认”大力出奇迹”——参数越大越聪明。阿里 Qwen 3.8-Max 堆到 2.4 万亿，Kimi K3 堆到 2.8 万亿。结果 DeepSeek 用一版纯后训练，告诉市场：推理时的后训练和工程打磨，可能比堆参数更值钱。这跟 2025 年春天那次”DeepSeek 时刻”是一个逻辑——又一次正面刚了规模法则。&lt;/p&gt;
&lt;h2 id=&quot;成本那块更狠&quot;&gt;成本那块更狠&lt;/h2&gt;
&lt;p&gt;V4-Flash 定价 0.14 美元/百万输入、0.28 美元/百万输出，缓存命中能到 0.0028 美元，比 Anthropic 便宜 99% 以上。一个顶级 coding 能力，价格只要人家的 0.6%。7 月 31 日这版还首次原生支持 Responses API、适配了 Codex，老的 deepseek-chat 和 deepseek-reasoner 接口要在 10 月 24 日之前迁走。&lt;/p&gt;
&lt;h2 id=&quot;普通人能得到什么&quot;&gt;普通人能得到什么&lt;/h2&gt;
&lt;p&gt;三点。第一，顶级代码能力便宜到可以忽略成本，中小团队和个人开发者直接能用。第二，一百万 token 上下文，等于能把一整个中等代码库塞进一次对话。第三，华为昇腾、寒武纪 Day 0 适配，国产算力跑得起万亿模型，这件事本身比模型分数更有分量。&lt;/p&gt;
&lt;p&gt;时间点记一下：V4-Pro 正式版（GA）据报 8 月 10 到 20 日来，卡在内部 Harness 编程工具的封闭测试；V4-Flash 的 MIT 开源权重也在往 HuggingFace 上放。&lt;/p&gt;
&lt;h2 id=&quot;我说点真心话&quot;&gt;我说点真心话&lt;/h2&gt;
&lt;p&gt;我自己有点矛盾。一方面，开源模型逼近闭源，价格塌到这个程度，对用 AI 干活的人是天大的好事。另一方面，知识截止还停在 2025 年、暂时没有视觉能力——它强，但还不是全能。别被”强到什么程度”的标题带节奏，它强在工程和性价比，不是样样第一。&lt;/p&gt;
&lt;p&gt;我的建议就一句：现在就去 API 里把 V4-Flash-0731 接上试试，别等 Pro 正式版。便宜、快、够用，这才是大多数人的真实需求。&lt;/p&gt;
&lt;p&gt;我是江楠。这期就聊到这，下期我拿它真跑几个 Agent 任务给你看。&lt;/p&gt;</description><pubDate>Tue, 04 Aug 2026 15:57:59 +0800</pubDate></item><item><title>让 AI 帮我发博客：一条只有踩过坑才写得出来的封装实录</title><link>https://www.jiangtian.top/?id=16363</link><description>&amp;lt;blockquote&amp;gt;
&amp;lt;p&amp;gt;需求很简单：以后我可以在本机任意一个 agent 会话里说一句”把这个发到博客上”，文章就自动发到 jiangtian.top。 但真做起来，从”能登上后台”到”稳定发一篇格式正确、分类正确的文章”，藏着的坑远比想象多。这是一篇不含滤镜的真实排障记录。&amp;lt;/p&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;h2 id=&amp;quot;一先说结论&amp;quot;&amp;gt;一、先说结论&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;我用 OpenClaw 做了一个叫 &amp;lt;code&amp;gt;blog-publish&amp;lt;/code&amp;gt; 的 skill，放在本机 &amp;lt;code&amp;gt;~/.agents/skills/blog-publish/&amp;lt;/code&amp;gt; 下。&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;以后无论哪个 agent 会话，只要用户说”发文章到博客”，它就会自动： 1. 登录后台（自动识别图形验证码，失败自动重试） 2. &amp;lt;strong&amp;gt;自动把 Markdown 转成 HTML&amp;lt;/strong&amp;gt;（因为 ZBlog 编辑器是”HTML / 可视化”双模切换，传裸 Markdown 会原样显示成 &amp;lt;code&amp;gt;# 标题&amp;lt;/code&amp;gt; 这种源码） 3. &amp;lt;strong&amp;gt;拉取全量分类，让大模型判断内容归属，选正确分类&amp;lt;/strong&amp;gt; 4. 返回文章链接&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;我发了好几篇&amp;lt;strong&amp;gt;草稿&amp;lt;/strong&amp;gt;反复验证格式和分类，确认无误后才敢上线。这篇就是用它自己发出来的。&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;quot;二技术栈侦察先搞清楚对手是谁&amp;quot;&amp;gt;二、技术栈侦察：先搞清楚对手是谁&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;博客 &amp;lt;code&amp;gt;jiangtian.top&amp;lt;/code&amp;gt; 用的是 &amp;lt;strong&amp;gt;Z-BlogPHP 1.7.3&amp;lt;/strong&amp;gt;（nginx 服务器）。第一步不是写代码，而是摸清它的登录和发布机制。用 &amp;lt;code&amp;gt;curl&amp;lt;/code&amp;gt; 一通探测：&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;后台登录页：&amp;lt;code&amp;gt;/zb_system/login.php&amp;lt;/code&amp;gt;，正常&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;发布入口：&amp;lt;code&amp;gt;/zb_system/cmd.php&amp;lt;/code&amp;gt;，未登录时返回 500&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;xmlrpc.php&amp;lt;/code&amp;gt;：返回的竟然是首页 HTML —— &amp;lt;strong&amp;gt;说明这个 ZBlog 没启用 XML-RPC 扩展&amp;lt;/strong&amp;gt;，想走 WordPress 兼容 API 的路直接堵死&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;p&amp;gt;所以只能走&amp;lt;strong&amp;gt;原生 cmd.php 接口&amp;lt;/strong&amp;gt;，这就牵扯出好几个硬骨头。&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;quot;三踩坑实录这才是干货&amp;quot;&amp;gt;三、踩坑实录（这才是干货）&amp;lt;/h2&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-1登录-post-到哪密码还要-md5&amp;quot;&amp;gt;坑 1：登录 POST 到哪？密码还要 MD5&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;一开始按直觉 POST 到 &amp;lt;code&amp;gt;login.php&amp;lt;/code&amp;gt;，一直 500。抓了登录页的 JS 才看清：&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;sourceCode&amp;quot; id=&amp;quot;cb1&amp;quot;&amp;gt;&amp;lt;pre class=&amp;quot;sourceCode javascript&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;sourceCode javascript&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;cb1-1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb1-1&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;$&amp;lt;/span&amp;gt;(&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;form&amp;amp;quot;&amp;lt;/span&amp;gt;)&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;attr&amp;lt;/span&amp;gt;(&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;action&amp;amp;quot;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;,&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;cmd.php?act=verify&amp;amp;quot;&amp;lt;/span&amp;gt;)&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;span id=&amp;quot;cb1-2&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb1-2&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;$&amp;lt;/span&amp;gt;(&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;#password&amp;amp;quot;&amp;lt;/span&amp;gt;)&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;val&amp;lt;/span&amp;gt;(&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;MD5&amp;lt;/span&amp;gt;(strPassWord))&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;;&amp;lt;/span&amp;gt;   &amp;lt;span class=&amp;quot;co&amp;quot;&amp;gt;// 密码要 MD5&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;span id=&amp;quot;cb1-3&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb1-3&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;$&amp;lt;/span&amp;gt;(&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;#username&amp;amp;quot;&amp;lt;/span&amp;gt;)&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;.&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;fu&amp;quot;&amp;gt;val&amp;lt;/span&amp;gt;(strUserName)&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;真相： - 登录不是提交给 &amp;lt;code&amp;gt;login.php&amp;lt;/code&amp;gt;，而是 &amp;lt;code&amp;gt;cmd.php?act=verify&amp;lt;/code&amp;gt; - 密码&amp;lt;strong&amp;gt;要先 MD5 加密&amp;lt;/strong&amp;gt;，填到隐藏域 &amp;lt;code&amp;gt;password&amp;lt;/code&amp;gt; - 用户名填到隐藏域 &amp;lt;code&amp;gt;username&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-2csrftoken-有两个用错一个就非法访问&amp;quot;&amp;gt;坑 2：csrfToken 有两个，用错一个就”非法访问”&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;这是最大的坑。登录页会给你一个 &amp;lt;code&amp;gt;csrfToken&amp;lt;/code&amp;gt;（藏在隐藏 input 里）。但&amp;lt;strong&amp;gt;登录之后&amp;lt;/strong&amp;gt;，你再想发布，用回这个 token 就报”非法访问”。&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;正确的 token 在&amp;lt;strong&amp;gt;后台首页&amp;lt;/strong&amp;gt;里：&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;sourceCode&amp;quot; id=&amp;quot;cb2&amp;quot;&amp;gt;&amp;lt;pre class=&amp;quot;sourceCode html&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;sourceCode html&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;cb2-1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb2-1&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;co&amp;quot;&amp;gt;&amp;amp;lt;!-- /zb_system/admin/index.php 里的 meta 标签 --&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;span id=&amp;quot;cb2-2&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb2-2&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;kw&amp;quot;&amp;gt;meta&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;ot&amp;quot;&amp;gt; name&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;csrfToken&amp;amp;quot;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;ot&amp;quot;&amp;gt; content&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;d73e305e9608258a48c9818d5b99b638&amp;amp;quot;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;注意它是 &amp;lt;code&amp;gt;content=&amp;lt;/code&amp;gt; 而不是 &amp;lt;code&amp;gt;value=&amp;lt;/code&amp;gt;，正则要先匹配对。我的做法：&amp;lt;strong&amp;gt;登录成功后，从后台首页 meta 标签重新取 token&amp;lt;/strong&amp;gt;，用它来发布。这一步修完，发布立刻从”非法访问”变成”成功跳转”。&amp;lt;/p&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-3zblog-的-status-语义和你想的相反&amp;quot;&amp;gt;坑 3：ZBlog 的 Status 语义和你想的相反&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;发布要传 &amp;lt;code&amp;gt;Status&amp;lt;/code&amp;gt;。我的第一反应是 &amp;lt;code&amp;gt;1=发布, 0=草稿&amp;lt;/code&amp;gt;。结果用 &amp;lt;code&amp;gt;status=&amp;quot;draft&amp;quot;&amp;lt;/code&amp;gt; 传 &amp;lt;code&amp;gt;Status:0&amp;lt;/code&amp;gt; 发出去的竟是&amp;lt;strong&amp;gt;公开&amp;lt;/strong&amp;gt;文章！&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;去后台文章管理页看了一眼下拉框才明白：&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;sourceCode&amp;quot; id=&amp;quot;cb3&amp;quot;&amp;gt;&amp;lt;pre class=&amp;quot;sourceCode html&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;sourceCode html&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;cb3-1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb3-1&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;kw&amp;quot;&amp;gt;option&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;ot&amp;quot;&amp;gt; value&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;0&amp;amp;quot;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt;公开&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;lt;/&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;kw&amp;quot;&amp;gt;option&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;span id=&amp;quot;cb3-2&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb3-2&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;lt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;kw&amp;quot;&amp;gt;option&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;ot&amp;quot;&amp;gt; value&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;=&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;st&amp;quot;&amp;gt;&amp;amp;quot;1&amp;amp;quot;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt;草稿&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;lt;/&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;kw&amp;quot;&amp;gt;option&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;dt&amp;quot;&amp;gt;&amp;amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;ZBlog 的 Status：0=公开（已发布），1=草稿&amp;lt;/strong&amp;gt;，正好反了。这个不发现，以后每次”存草稿”都会误发成公开文章。&amp;lt;/p&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-4正文必须是-html不是-markdown&amp;quot;&amp;gt;坑 4：正文必须是 HTML，不是 Markdown&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;ZBlog 后台编辑器的正文是一个 &amp;lt;strong&amp;gt;“HTML / 可视化”双模切换的富文本区域&amp;lt;/strong&amp;gt;——它存的是 HTML，可视化模式是拿这段 HTML 渲染出来的。&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;如果你把 Markdown 源码（&amp;lt;code&amp;gt;# 标题&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;**加粗**&amp;lt;/code&amp;gt;）直接塞进 Content 字段： - 可视化模式下会看到一堆 &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;**&amp;lt;/code&amp;gt; 星号，格式全乱 - “HTML 模式”里倒是能看到源码，但那不是人看的&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;解决办法&amp;lt;/strong&amp;gt;：发布前用 pandoc 把 Markdown 转成标准 HTML：&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;quot;sourceCode&amp;quot; id=&amp;quot;cb4&amp;quot;&amp;gt;&amp;lt;pre class=&amp;quot;sourceCode bash&amp;quot;&amp;gt;&amp;lt;code class=&amp;quot;sourceCode bash&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;cb4-1&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;#cb4-1&amp;quot; aria-hidden=&amp;quot;true&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;span class=&amp;quot;ex&amp;quot;&amp;gt;pandoc&amp;lt;/span&amp;gt; &amp;lt;span class=&amp;quot;at&amp;quot;&amp;gt;-f&amp;lt;/span&amp;gt; markdown &amp;lt;span class=&amp;quot;at&amp;quot;&amp;gt;-t&amp;lt;/span&amp;gt; html &amp;lt;span class=&amp;quot;at&amp;quot;&amp;gt;--wrap&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;op&amp;quot;&amp;gt;=&amp;lt;/span&amp;gt;none input.md&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;得到 &amp;lt;code&amp;gt;&amp;amp;lt;h1&amp;amp;gt;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;amp;lt;p&amp;amp;gt;&amp;amp;lt;strong&amp;amp;gt;&amp;lt;/code&amp;gt;、&amp;lt;code&amp;gt;&amp;amp;lt;ul&amp;amp;gt;&amp;lt;/code&amp;gt; 这种规范结构，ZBlog 就能正确渲染成带格式的文章。这一步我一开始也漏了，导致第一版发出去的文章在可视化模式下是乱的。&amp;lt;/p&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-5图形验证码纯-ocr-不够得多引擎投票-重试&amp;quot;&amp;gt;坑 5：图形验证码，纯 OCR 不够，得”多引擎投票 + 重试”&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;ZBlog 登录要图形验证码：5 位字符（大小写字母+数字混合），背景/字符颜色随机，还有斜向干扰线，JPEG 压缩还严重。&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;先测各种 OCR： - tesseract：对这张图”顾此失彼”，不同参数下时而准时而空 - macOS Vision：部分图完美，部分图空或串符号&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;结论：没有任何单一引擎能稳定全对。&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;最终方案是&amp;lt;strong&amp;gt;多引擎投票 + 自动重试循环&amp;lt;/strong&amp;gt;： - 同时跑 tesseract（多个分段模式）和 Vision，收集候选 - 取出现次数最多的候选 - 验证码错了就&amp;lt;strong&amp;gt;换一张新的、重新识别、重新登录&amp;lt;/strong&amp;gt;，直到成功&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;实测登录通常 1~3 次尝试搞定，最多 10 来次内必成。ZBlog 这里&amp;lt;strong&amp;gt;没有防爆破次数限制&amp;lt;/strong&amp;gt;（连试 7 次错误都不锁），所以重试策略完全可行。&amp;lt;/p&amp;gt;
&amp;lt;h3 id=&amp;quot;坑-6分类不能硬编码要拉全量让大模型选&amp;quot;&amp;gt;坑 6：分类不能硬编码，要拉全量让大模型选&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;第一版我图省事，把”技术笔记”硬编码成 &amp;lt;code&amp;gt;category=3&amp;lt;/code&amp;gt; 传进去。这有两个问题： 1. 分类 ID 是站点相关的，换个博客就全对不上 2. 内容归属应该由理解语义的模型来判断，而不是写死&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;正确做法&amp;lt;/strong&amp;gt;：发布前先调用 &amp;lt;code&amp;gt;get_categories()&amp;lt;/code&amp;gt; 把全量分类（我这边有 60 多个）拉下来，然后让大模型读文章内容、判断它最该进哪个分类，再传对应 ID。这样”这篇技术文章进技术笔记、那篇读书心得进读书笔记”，才是真智能。&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;quot;四最终发布的协议存个档&amp;quot;&amp;gt;四、最终发布的协议（存个档）&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;理顺之后，完整调用协议如下：&amp;lt;/p&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;【登录】
GET  /zb_system/login.php                 # 取 csrfToken 和会话 cookie
GET  /zb_system/script/c_validcode.php?id=login&amp;amp;amp;time=m   # 下载验证码
POST /zb_system/cmd.php?act=verify
     字段: csrfToken, username, password(MD5), verify, savedate

【发布】
1. get_categories()   # 拉全量分类, 大模型选出正确分类
2. pandoc md-&amp;amp;gt;html    # 正文转 HTML
3. GET  /zb_system/admin/index.php        # 重新取后台 csrfToken (meta标签)
4. POST /zb_system/cmd.php?act=ArticlePst
     字段: ID=0(新建), Title, Content(HTML), Cate_ID, Tag, Status(0公开/1草稿),
            Alias, Intro, csrfToken

【删除】
GET  /zb_system/cmd.php?act=ArticleDel&amp;amp;amp;id=X&amp;amp;amp;csrfToken=Y&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&amp;lt;h2 id=&amp;quot;五几个值得记住的通用经验&amp;quot;&amp;gt;五、几个值得记住的通用经验&amp;lt;/h2&amp;gt;
&amp;lt;ol type=&amp;quot;1&amp;quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;动手前先”侦察”&amp;lt;/strong&amp;gt;：用 curl 看响应头、抓 JS、看表单字段，搞清楚生态再写代码。你以为的”标准接口”往往是错的。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;500 不一定是服务器挂了&amp;lt;/strong&amp;gt;：ZBlog 用”HTTP 500 + 错误页”来优雅表达”验证码错误”这类业务失败。别被状态码吓到，去看响应体。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;登录成功 ≠ 能发文章&amp;lt;/strong&amp;gt;：cookie 有了、后台进了，但发布用错 csrfToken 照样”非法访问”。每种操作的鉴权上下文要分开测。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;富文本编辑器一律收 HTML&amp;lt;/strong&amp;gt;：凡是”HTML/可视化”切换的编辑器，Content 字段只认 HTML，别图省事塞 Markdown。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;字段语义务必实测&amp;lt;/strong&amp;gt;：Status 的 0/1 我完全想反了。&amp;lt;strong&amp;gt;一定要发一篇草稿实测&amp;lt;/strong&amp;gt;，别轻信直觉。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;分类让模型选，别硬编码&amp;lt;/strong&amp;gt;：拉全量分类 → 大模型判断归属 → 提交，可迁移、更智能。&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;测试垃圾随手清理&amp;lt;/strong&amp;gt;：我发的那几篇测试文章最后都删掉了（404 确认），没给博客留脏数据。&amp;lt;/li&amp;gt;
&amp;lt;/ol&amp;gt;
&amp;lt;h2 id=&amp;quot;六成果&amp;quot;&amp;gt;六、成果&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;以后我从任何 agent 会话里说一句”把这篇发到技术笔记”，文章就会自动以正确的 HTML 格式、正确的分类落到 jiangtian.top。这篇就是用它自己发出来的第一篇文章。&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;（注：中途所有带”测试”的草稿均已删除，无残留。）&amp;lt;/p&amp;gt;
&amp;lt;hr /&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;如果你也在折腾 ZBlog / 自建博客的自动化，希望这些坑能帮你少走弯路。&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;</description><pubDate>Tue, 04 Aug 2026 15:09:57 +0800</pubDate></item><item><title>两台 mac 电脑直接互传文本</title><link>https://www.jiangtian.top/?id=16356</link><description>&lt;p&gt;你这个界面&lt;strong&gt;不能直接粘贴文本发送&lt;/strong&gt;。Finder 里的“隔空投送”主要是用来&lt;strong&gt;发现设备、拖文件发送&lt;/strong&gt;的，不是聊天框。&lt;/p&gt;&lt;h2&gt;方法一：把文本变成 txt 文件再发&lt;/h2&gt;&lt;p&gt;最稳。&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;打开 &lt;strong&gt;文本编辑&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;粘贴你的文本&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;保存成一个文件，比如：&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;临时文本.txt&lt;/pre&gt;&lt;ol start=&quot;4&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;回到你这个“隔空投送”窗口&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;把这个 txt 文件拖到对方 Mac 头像上&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;或者右键文件：&lt;/p&gt;&lt;pre&gt;共享&amp;nbsp;→&amp;nbsp;隔空投送&amp;nbsp;→&amp;nbsp;选择另一台&amp;nbsp;Mac&lt;/pre&gt;&lt;h2&gt;方法二：从某些 App 里直接共享文本&lt;/h2&gt;&lt;p&gt;比如你在 Safari、备忘录、文本编辑里选中一段文字：&lt;/p&gt;&lt;pre&gt;选中文字&amp;nbsp;→&amp;nbsp;右键&amp;nbsp;→&amp;nbsp;共享&amp;nbsp;→&amp;nbsp;隔空投送&lt;/pre&gt;&lt;p&gt;但这个不一定每个 App 都支持，所以不如 txt 文件稳定。&lt;/p&gt;&lt;h2&gt;方法三：装 LocalSend，专门传文本&lt;/h2&gt;&lt;p&gt;如果你经常两台 Mac 互发文字，建议用 &lt;strong&gt;LocalSend&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;它可以直接发：&lt;/p&gt;&lt;pre&gt;文字
文件
文件夹
图片&lt;/pre&gt;&lt;p&gt;不需要登录账号，不走云端，同一个 Wi-Fi 下就行。&lt;/p&gt;&lt;h2&gt;你现在最快的做法&lt;/h2&gt;&lt;p&gt;直接这样：&lt;/p&gt;&lt;pre&gt;文本编辑&amp;nbsp;→&amp;nbsp;粘贴内容&amp;nbsp;→&amp;nbsp;保存成&amp;nbsp;txt&amp;nbsp;→&amp;nbsp;拖到隔空投送里发&lt;/pre&gt;&lt;p&gt;一句话：&lt;strong&gt;AirDrop 不是微信聊天框，想发文本片段，最好先保存成 txt 文件再投送。&lt;/strong&gt;&lt;/p&gt;</description><pubDate>Thu, 04 Jun 2026 14:49:07 +0800</pubDate></item><item><title>Windows 安装 DeepSeek-TUI 踩坑：不是 PATH 问题，是它改名了</title><link>https://www.jiangtian.top/?id=16355</link><description>&lt;p&gt;
    这次踩的坑，表面看是 Windows 找不到命令，实际根因是：&lt;strong&gt;DeepSeek-TUI 已经改名为 CodeWhale 了。&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
    我一开始按老教程安装：
&lt;/p&gt;
&lt;pre&gt;npm install -g deepseek-tui --registry=https://registry.npmmirror.com&lt;/pre&gt;
&lt;p&gt;
    终端提示安装成功：
&lt;/p&gt;
&lt;pre&gt;changed 1 package&lt;/pre&gt;
&lt;p&gt;
    看起来没问题。
&lt;/p&gt;
&lt;p&gt;
    但执行：
&lt;/p&gt;
&lt;pre&gt;deepseek&lt;/pre&gt;
&lt;p&gt;
    却提示：
&lt;/p&gt;
&lt;pre&gt;deepseek 不是内部或外部命令，也不是可运行的程序&lt;/pre&gt;
&lt;p&gt;
    于是第一反应就是：环境变量没配好。
&lt;/p&gt;
&lt;h2&gt;
    一、第一层误判：以为是 PATH 没加
&lt;/h2&gt;
&lt;p&gt;
    Windows 下 npm 全局命令一般会安装到这个目录：
&lt;/p&gt;
&lt;pre&gt;C:\Users\Administrator\AppData\Roaming\npm&lt;/pre&gt;
&lt;p&gt;
    所以我先把这个路径加进系统环境变量。
&lt;/p&gt;
&lt;p&gt;
    PowerShell 临时加 PATH 的写法是：
&lt;/p&gt;
&lt;pre&gt;$env:Path += &amp;quot;;C:\Users\Administrator\AppData\Roaming\npm&amp;quot;&lt;/pre&gt;
&lt;p&gt;
    注意，这里不能写成：
&lt;/p&gt;
&lt;pre&gt;set PATH=%PATH%;%APPDATA%\npm&lt;/pre&gt;
&lt;p&gt;
    这个是 CMD 写法，不是 PowerShell 写法。
&lt;/p&gt;
&lt;p&gt;
    如果你在 PowerShell 里执行 CMD 的写法，就会报类似这样的错误：
&lt;/p&gt;
&lt;pre&gt;无法加载模块 %APPDATA%\npm&lt;/pre&gt;
&lt;p&gt;
    所以这里第一个坑是：
&lt;/p&gt;
&lt;p&gt;
    &lt;strong&gt;PowerShell 用 &lt;code inline=&quot;&quot;&gt;$env:变量名&lt;/code&gt;，CMD 才用 &lt;code inline=&quot;&quot;&gt;%变量名%&lt;/code&gt;。&lt;/strong&gt;
&lt;/p&gt;
&lt;h2&gt;
    二、第二层排查：PATH 加好了，还是找不到
&lt;/h2&gt;
&lt;p&gt;
    后来我专门写了一个脚本，把 npm 全局路径永久加入系统 Path。
&lt;/p&gt;
&lt;p&gt;
    脚本执行后显示：
&lt;/p&gt;
&lt;pre&gt;已成功加入系统环境变量 Path:
C:\Users\Administrator\AppData\Roaming\npm&lt;/pre&gt;
&lt;p&gt;
    说明环境变量已经加成功了。
&lt;/p&gt;
&lt;p&gt;
    但是继续检测：
&lt;/p&gt;
&lt;pre&gt;Get-Command deepseek
Get-Command deepseek-tui
Get-Command codewhale&lt;/pre&gt;
&lt;p&gt;
    结果还是：
&lt;/p&gt;
&lt;pre&gt;未找到命令：deepseek
未找到命令：deepseek-tui
未找到命令：codewhale&lt;/pre&gt;
&lt;p&gt;
    这时候就可以判断了：
&lt;/p&gt;
&lt;p&gt;
    &lt;strong&gt;这已经不是 PATH 的问题了。&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
    如果 PATH 有问题，脚本不会显示加入成功。
&lt;/p&gt;
&lt;p&gt;
    如果 PATH 已经成功，但命令还是找不到，那就说明 npm 安装的包本身没有生成对应的命令入口。
&lt;/p&gt;
&lt;p&gt;
    也就是说：
&lt;/p&gt;
&lt;pre&gt;npm install -g deepseek-tui&lt;/pre&gt;
&lt;p&gt;
    虽然执行成功，但它未必还会生成 &lt;code inline=&quot;&quot;&gt;deepseek&lt;/code&gt; 这个命令。
&lt;/p&gt;
&lt;h2&gt;
    三、真正原因：DeepSeek-TUI 改名成 CodeWhale
&lt;/h2&gt;
&lt;p&gt;
    真正原因是：&lt;strong&gt;DeepSeek-TUI 项目已经改名为 CodeWhale。&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;
    以前老教程里写的是：
&lt;/p&gt;
&lt;pre&gt;npm install -g deepseek-tui
deepseek&lt;/pre&gt;
&lt;p&gt;
    现在应该改成：
&lt;/p&gt;
&lt;pre&gt;npm install -g codewhale
codewhale&lt;/pre&gt;
&lt;p&gt;
    旧的 &lt;code inline=&quot;&quot;&gt;deepseek-tui&lt;/code&gt; 包已经不适合作为新安装方式了。
&lt;/p&gt;
&lt;p&gt;
    所以这次的坑，不是 Node 没装好，不是 npm 镜像问题，也不是 Windows 环境变量一定没配好，而是教程过期了。
&lt;/p&gt;
&lt;p&gt;
    一句话：
&lt;/p&gt;
&lt;p&gt;
    &lt;strong&gt;老名字叫 deepseek-tui，新名字叫 codewhale。以后别再用 deepseek 启动，直接用 codewhale。&lt;/strong&gt;
&lt;/p&gt;
&lt;h2&gt;
    四、正确解决方式
&lt;/h2&gt;
&lt;p&gt;
    直接执行：
&lt;/p&gt;
&lt;pre&gt;npm uninstall -g deepseek-tui
npm install -g codewhale --registry=https://registry.npmmirror.com&lt;/pre&gt;
&lt;p&gt;
    安装完成后检查：
&lt;/p&gt;
&lt;pre&gt;Get-Command codewhale
Get-Command codewhale-tui&lt;/pre&gt;
&lt;p&gt;
    再查看版本：
&lt;/p&gt;
&lt;pre&gt;codewhale --version&lt;/pre&gt;
&lt;p&gt;
    最后启动：
&lt;/p&gt;
&lt;pre&gt;codewhale&lt;/pre&gt;
&lt;p&gt;
    如果你想看 npm 到底把命令装到哪里，可以执行：
&lt;/p&gt;
&lt;pre&gt;npm config get prefix&lt;/pre&gt;
&lt;p&gt;
    正常情况下会返回类似：
&lt;/p&gt;
&lt;pre&gt;C:\Users\Administrator\AppData\Roaming\npm&lt;/pre&gt;
&lt;p&gt;
    然后看这个目录里有没有生成命令：
&lt;/p&gt;
&lt;pre&gt;dir &amp;quot;C:\Users\Administrator\AppData\Roaming\npm&amp;quot; | findstr /i &amp;quot;codewhale deepseek&amp;quot;&lt;/pre&gt;
&lt;p&gt;
    如果这里能看到 &lt;code inline=&quot;&quot;&gt;codewhale.cmd&lt;/code&gt;，说明命令入口已经生成。
&lt;/p&gt;
&lt;h2&gt;
    五、Windows 桌面执行脚本的小坑
&lt;/h2&gt;
&lt;p&gt;
    如果脚本放在桌面，推荐这样进入桌面目录：
&lt;/p&gt;
&lt;pre&gt;cd ([Environment]::GetFolderPath(&amp;quot;Desktop&amp;quot;))&lt;/pre&gt;
&lt;p&gt;
    这个比手写路径更稳。
&lt;/p&gt;
&lt;p&gt;
    因为有些电脑的桌面可能不是：
&lt;/p&gt;
&lt;pre&gt;C:\Users\Administrator\Desktop&lt;/pre&gt;
&lt;p&gt;
    而是 OneDrive 或其他目录。
&lt;/p&gt;
&lt;p&gt;
    进入桌面后执行：
&lt;/p&gt;
&lt;pre&gt;Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\fix-npm-path.ps1&lt;/pre&gt;
&lt;p&gt;
    这里的：
&lt;/p&gt;
&lt;pre&gt;Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass&lt;/pre&gt;
&lt;p&gt;
    只是让当前 PowerShell 窗口临时允许执行脚本，不是永久关闭安全策略。
&lt;/p&gt;
&lt;h2&gt;
    六、最终判断逻辑
&lt;/h2&gt;
&lt;p&gt;
    遇到这种“安装成功，但命令找不到”的问题，不要一上来就反复重装。
&lt;/p&gt;
&lt;p&gt;
    按这个顺序排查：
&lt;/p&gt;
&lt;h3&gt;
    1. 先看命令是否存在
&lt;/h3&gt;
&lt;pre&gt;Get-Command deepseek
Get-Command deepseek-tui
Get-Command codewhale&lt;/pre&gt;
&lt;h3&gt;
    2. 再看 npm 全局目录
&lt;/h3&gt;
&lt;pre&gt;npm config get prefix&lt;/pre&gt;
&lt;h3&gt;
    3. 再看目录里有没有命令文件
&lt;/h3&gt;
&lt;pre&gt;dir &amp;quot;C:\Users\Administrator\AppData\Roaming\npm&amp;quot; | findstr /i &amp;quot;codewhale deepseek&amp;quot;&lt;/pre&gt;
&lt;h3&gt;
    4. 如果 PATH 没问题，但命令不存在
&lt;/h3&gt;
&lt;p&gt;
    那就不是环境变量问题，而是包没有生成这个命令。
&lt;/p&gt;
&lt;p&gt;
    这次就是这个情况。
&lt;/p&gt;
&lt;h2&gt;
    七、最终命令合集
&lt;/h2&gt;
&lt;p&gt;
    最推荐直接用这一套：
&lt;/p&gt;
&lt;pre&gt;npm uninstall -g deepseek-tui
npm install -g codewhale --registry=https://registry.npmmirror.com
Get-Command codewhale
codewhale --version
codewhale&lt;/pre&gt;
&lt;p&gt;
    如果仍然找不到，再检查：
&lt;/p&gt;
&lt;pre&gt;npm config get prefix
dir &amp;quot;C:\Users\Administrator\AppData\Roaming\npm&amp;quot; | findstr /i &amp;quot;codewhale deepseek&amp;quot;&lt;/pre&gt;
&lt;h2&gt;
    八、这次踩坑的核心结论
&lt;/h2&gt;
&lt;p&gt;
    这次问题不是：
&lt;/p&gt;
&lt;pre&gt;npm 没安装成功&lt;/pre&gt;
&lt;p&gt;
    也不完全是：
&lt;/p&gt;
&lt;pre&gt;Windows PATH 没配好&lt;/pre&gt;
&lt;p&gt;
    而是：
&lt;/p&gt;
&lt;pre&gt;DeepSeek-TUI 改名为 CodeWhale，旧命令 deepseek 已经过时&lt;/pre&gt;
&lt;p&gt;
    所以以后安装不要再按老教程写：
&lt;/p&gt;
&lt;pre&gt;npm install -g deepseek-tui
deepseek&lt;/pre&gt;
&lt;p&gt;
    而是直接写：
&lt;/p&gt;
&lt;pre&gt;npm install -g codewhale
codewhale&lt;/pre&gt;
&lt;p&gt;
    技术工具更新太快，很多时候最坑人的不是报错本身，而是教程已经过期了。
&lt;/p&gt;
&lt;p&gt;
    这次就是典型案例：&lt;br/&gt;&lt;strong&gt;命令没错，环境也快配好了，结果项目名字变了。&lt;/strong&gt;
&lt;/p&gt;</description><pubDate>Thu, 04 Jun 2026 14:44:00 +0800</pubDate></item><item><title>什么是FDE工程师？</title><link>https://www.jiangtian.top/?id=16354</link><description>&lt;h2&gt;FDE 工程师是什么？&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;FDE = Forward Deployed Engineer&lt;/strong&gt;，直译是：&lt;strong&gt;前线部署工程师 / 客户现场工程师&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;更通俗地说：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;不是坐在办公室只写产品代码的工程师，而是冲到客户真实业务现场，把 AI / 软件系统真正落地跑起来的人。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;OpenAI 对 FDE 的描述是：负责和战略客户一起完成复杂的端到端模型部署，覆盖需求发现、技术范围定义、系统设计、构建和生产上线。(&lt;a href=&quot;https://openai.com/careers/forward-deployed-engineer-%28fde%29-sf-san-francisco/?utm_source=chatgpt.com&quot; title=&quot;Forward Deployed Engineer (FDE) - SF&quot;&gt;OpenAI&lt;/a&gt;)&lt;/p&gt;&lt;h2&gt;它和普通工程师的区别&lt;/h2&gt;&lt;p&gt;普通软件工程师更像是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我负责把产品功能写好。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;FDE 更像是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;我负责让客户的问题真的被解决，必要时我自己写代码、接系统、改流程、做上线、培训团队。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;所以 FDE 其实是一个混合岗位：&lt;/p&gt;&lt;table&gt;&lt;thead&gt;&lt;tr class=&quot;firstRow&quot;&gt;&lt;th&gt;角色&lt;/th&gt;&lt;th&gt;FDE 身上对应的能力&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;软件工程师&lt;/td&gt;&lt;td&gt;写代码、做集成、搭系统&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;解决方案架构师&lt;/td&gt;&lt;td&gt;设计技术方案&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;顾问&lt;/td&gt;&lt;td&gt;理解客户业务和痛点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;项目经理&lt;/td&gt;&lt;td&gt;推动上线交付&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;产品经理&lt;/td&gt;&lt;td&gt;把客户反馈带回产品团队&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h2&gt;为什么 AI 时代突然火了？&lt;/h2&gt;&lt;p&gt;因为大模型不是装上就能用。&lt;/p&gt;&lt;p&gt;企业真正落地 AI，会遇到很多现实问题：&lt;/p&gt;&lt;p&gt;比如数据在哪、权限怎么控、怎么接 ERP/CRM/OA、怎么保证安全、怎么评估效果、怎么让业务团队真的用起来。&lt;/p&gt;&lt;p&gt;这时候光卖 API 不够，光讲方案也不够，需要一个既懂技术、又懂业务、还敢下场交付的人。&lt;/p&gt;&lt;p&gt;这就是 FDE。&lt;/p&gt;&lt;p&gt;Reuters 报道也把 FDE 称为当前 AI 领域非常热门的岗位之一，原因是它能把 AI 模型嵌入真实企业系统，解决“从试点到生产”的落地难题。(&lt;a href=&quot;https://www.reuters.com/technology/artificial-intelligence/artificial-intelligencer-hottest-job-ai-right-now-2026-02-26/?utm_source=chatgpt.com&quot; title=&quot;What&amp;#39;s the hottest job in AI right now?&quot;&gt;Reuters&lt;/a&gt;)&lt;/p&gt;&lt;h2&gt;一句话理解&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;FDE 工程师 = 企业 AI 落地的“特种兵”。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;不是纯程序员。&lt;/p&gt;&lt;p&gt;不是纯售前。&lt;/p&gt;&lt;p&gt;不是纯顾问。&lt;/p&gt;&lt;p&gt;而是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;带着技术武器，进入客户战场，直接把业务问题打穿的人。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;适合什么人？&lt;/h2&gt;&lt;p&gt;适合这类人：&lt;/p&gt;&lt;p&gt;懂代码，但不想只关在 IDE 里。&lt;/p&gt;&lt;p&gt;能和老板、业务负责人、技术团队都聊得明白。&lt;/p&gt;&lt;p&gt;喜欢解决真实复杂问题。&lt;/p&gt;&lt;p&gt;能接受不确定性、出差、客户现场、快速交付。&lt;/p&gt;&lt;p&gt;对 AI 应用、企业数字化、业务流程优化有兴趣。&lt;/p&gt;&lt;h2&gt;对你最有价值的理解&lt;/h2&gt;&lt;p&gt;你以后做企业 AI 智能体、AI 操作系统、AI 落地方案，其实最需要培养的就是 &lt;strong&gt;FDE 思维&lt;/strong&gt;：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;不只是“我会调模型”，而是“我能把模型接进企业业务流程，并让它产生可衡量的业务结果”。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这也是未来 AI 课程、AI 咨询、AI 交付里非常值钱的一类能力。&lt;/p&gt;</description><pubDate>Sun, 31 May 2026 19:50:42 +0800</pubDate></item><item><title>macOS 上 v2rayN 的 TUN 模式没生效，怎么排查和修复？</title><link>https://www.jiangtian.top/?id=16353</link><description>&lt;p&gt;
    很多人以为：
&lt;/p&gt;
&lt;p&gt;
    只要 v2rayN 节点能连上，TUN 模式就一定生效。
&lt;/p&gt;
&lt;p&gt;
    其实不是。
&lt;/p&gt;
&lt;p&gt;
    节点能用，只代表代理核心本身通了。
&lt;/p&gt;
&lt;p&gt;
    TUN 能用，代表系统流量真的被接管了。
&lt;/p&gt;
&lt;p&gt;
    这两个不是一回事。
&lt;/p&gt;
&lt;p&gt;
    这次遇到的问题就是典型案例：
&lt;/p&gt;
&lt;p&gt;
    手动走 SOCKS5 代理可以访问 Google。
&lt;/p&gt;
&lt;p&gt;
    但是浏览器、终端、系统直连还是访问不了。
&lt;/p&gt;
&lt;p&gt;
    这说明什么？
&lt;/p&gt;
&lt;p&gt;
    说明代理链路本身是好的，但系统流量没有真正走进 TUN。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    一、先看现象
&lt;/h1&gt;
&lt;p&gt;
    诊断结果里有三个关键现象。
&lt;/p&gt;
&lt;p&gt;
    第一个：
&lt;/p&gt;
&lt;pre&gt;127.0.0.1:10808 正在监听&lt;/pre&gt;
&lt;p&gt;
    说明本机 SOCKS5 代理端口已经起来了。
&lt;/p&gt;
&lt;p&gt;
    第二个：
&lt;/p&gt;
&lt;pre&gt;curl --socks5-hostname 127.0.0.1:10808 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    可以返回：
&lt;/p&gt;
&lt;pre&gt;HTTP/2 200&lt;/pre&gt;
&lt;p&gt;
    说明节点、代理核心、远程链路都是正常的。
&lt;/p&gt;
&lt;p&gt;
    第三个：
&lt;/p&gt;
&lt;pre&gt;curl -I https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    直接访问超时。
&lt;/p&gt;
&lt;p&gt;
    同时：
&lt;/p&gt;
&lt;pre&gt;127.0.0.1:10809 Connection refused&lt;/pre&gt;
&lt;p&gt;
    说明 HTTP 代理端口没有起来。
&lt;/p&gt;
&lt;p&gt;
    这几个现象合起来，结论就很清楚：
&lt;/p&gt;
&lt;p&gt;
    代理核心没坏。
&lt;/p&gt;
&lt;p&gt;
    节点没坏。
&lt;/p&gt;
&lt;p&gt;
    真正的问题是：
&lt;/p&gt;
&lt;p&gt;
    系统流量没有被 TUN 接管。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    二、什么是 TUN 模式？
&lt;/h1&gt;
&lt;p&gt;
    普通代理模式，一般是这样的：
&lt;/p&gt;
&lt;p&gt;
    软件自己设置代理。
&lt;/p&gt;
&lt;p&gt;
    浏览器自己设置代理。
&lt;/p&gt;
&lt;p&gt;
    终端自己设置代理。
&lt;/p&gt;
&lt;p&gt;
    哪个软件设置了，哪个软件才走代理。
&lt;/p&gt;
&lt;p&gt;
    而 TUN 模式不一样。
&lt;/p&gt;
&lt;p&gt;
    TUN 模式会在系统里创建一个虚拟网卡，然后把系统流量导进这个虚拟网卡，再交给代理核心处理。
&lt;/p&gt;
&lt;p&gt;
    简单说：
&lt;/p&gt;
&lt;p&gt;
    普通代理是“软件自己走代理”。
&lt;/p&gt;
&lt;p&gt;
    TUN 模式是“系统帮所有流量走代理”。
&lt;/p&gt;
&lt;p&gt;
    所以 TUN 模式生效后，理论上你不用单独给浏览器、终端、npm、OpenClaw 配代理，它们也应该能自动走代理。
&lt;/p&gt;
&lt;p&gt;
    但前提是：
&lt;/p&gt;
&lt;p&gt;
    TUN 真的启动成功了。
&lt;/p&gt;
&lt;p&gt;
    路由真的被改了。
&lt;/p&gt;
&lt;p&gt;
    DNS 真的被接管了。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    三、TUN 没生效的典型表现
&lt;/h1&gt;
&lt;p&gt;
    如果你遇到下面这些情况，基本就可以怀疑 TUN 没生效。
&lt;/p&gt;
&lt;h2&gt;
    1. SOCKS5 手动访问成功
&lt;/h2&gt;
&lt;p&gt;
    比如：
&lt;/p&gt;
&lt;pre&gt;curl -I --socks5-hostname 127.0.0.1:10808 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    能成功。
&lt;/p&gt;
&lt;p&gt;
    这说明节点本身没问题。
&lt;/p&gt;
&lt;h2&gt;
    2. 直接访问失败
&lt;/h2&gt;
&lt;p&gt;
    比如：
&lt;/p&gt;
&lt;pre&gt;curl -I https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    失败、超时、无法连接。
&lt;/p&gt;
&lt;p&gt;
    这说明系统流量没被接管。
&lt;/p&gt;
&lt;h2&gt;
    3. 浏览器不设置代理就访问不了
&lt;/h2&gt;
&lt;p&gt;
    如果浏览器只有在手动设置 SOCKS5 代理后才能访问，关掉代理就不行，也说明 TUN 没真正接管系统流量。
&lt;/p&gt;
&lt;h2&gt;
    4. 没有新的 utun 网卡
&lt;/h2&gt;
&lt;p&gt;
    macOS 上 TUN 通常会创建 &lt;code inline=&quot;&quot;&gt;utun&lt;/code&gt; 相关虚拟网卡。
&lt;/p&gt;
&lt;p&gt;
    可以用这个命令看：
&lt;/p&gt;
&lt;pre&gt;ifconfig | grep -Ei &amp;quot;utun|sing|tun&amp;quot; -A 8&lt;/pre&gt;
&lt;p&gt;
    如果开了 TUN 之后完全看不到相关变化，大概率 TUN 根本没起来。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    四、这次问题的根因判断
&lt;/h1&gt;
&lt;p&gt;
    这次日志里，最关键的是：
&lt;/p&gt;
&lt;pre&gt;SOCKS5 10808 成功
HTTP 10809 refused
直连 Google timeout&lt;/pre&gt;
&lt;p&gt;
    这说明：
&lt;/p&gt;
&lt;p&gt;
    v2rayN / xray / sing-box 代理核心至少有一部分是正常的。
&lt;/p&gt;
&lt;p&gt;
    因为 SOCKS5 已经能访问外网。
&lt;/p&gt;
&lt;p&gt;
    但系统流量没有自动走代理。
&lt;/p&gt;
&lt;p&gt;
    也就是说：
&lt;/p&gt;
&lt;p&gt;
    TUN 模式没有真正生效。
&lt;/p&gt;
&lt;p&gt;
    进一步看，还有一个问题：
&lt;/p&gt;
&lt;p&gt;
    HTTP 代理端口 10809 没有监听。
&lt;/p&gt;
&lt;p&gt;
    所以如果某些软件配置的是：
&lt;/p&gt;
&lt;pre&gt;http://127.0.0.1:10809&lt;/pre&gt;
&lt;p&gt;
    也会直接失败。
&lt;/p&gt;
&lt;p&gt;
    这类失败不是节点失败，而是本机端口没开。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    五、第一步：确认核心类型，优先用 sing-box
&lt;/h1&gt;
&lt;p&gt;
    macOS 上使用 TUN，建议优先使用 sing-box core。
&lt;/p&gt;
&lt;p&gt;
    因为 TUN、路由接管、DNS 劫持这些能力，sing-box 更适合。
&lt;/p&gt;
&lt;p&gt;
    在 v2rayN 里检查：
&lt;/p&gt;
&lt;pre&gt;设置
核心类型
切换为 sing-box&lt;/pre&gt;
&lt;p&gt;
    切换之后，重新选择节点。
&lt;/p&gt;
&lt;p&gt;
    然后重新开启 TUN。
&lt;/p&gt;
&lt;p&gt;
    不要一边用 xray，一边指望 TUN 一定能稳定接管所有系统流量。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    六、第二步：彻底重启 v2rayN 和代理核心
&lt;/h1&gt;
&lt;p&gt;
    有时候 TUN 开关点了，但核心没有正确重载。
&lt;/p&gt;
&lt;p&gt;
    这时候不要只点界面按钮。
&lt;/p&gt;
&lt;p&gt;
    直接把相关进程杀掉：
&lt;/p&gt;
&lt;pre&gt;pkill -f v2rayN
pkill -f sing-box
pkill -f xray&lt;/pre&gt;
&lt;p&gt;
    然后重新打开 v2rayN。
&lt;/p&gt;
&lt;p&gt;
    重新选择节点。
&lt;/p&gt;
&lt;p&gt;
    重新开启 TUN。
&lt;/p&gt;
&lt;p&gt;
    如果 macOS 弹出管理员密码授权，一定要允许。
&lt;/p&gt;
&lt;p&gt;
    因为 TUN 需要创建虚拟网卡、修改系统路由，这不是普通权限能完成的。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    七、第三步：确认 TUN 网卡是否创建成功
&lt;/h1&gt;
&lt;p&gt;
    开启 TUN 后，执行：
&lt;/p&gt;
&lt;pre&gt;ifconfig | grep -Ei &amp;quot;utun|sing|tun&amp;quot; -A 8&lt;/pre&gt;
&lt;p&gt;
    如果看到新的 &lt;code inline=&quot;&quot;&gt;utun&lt;/code&gt; 相关接口，说明 TUN 至少创建了虚拟网卡。
&lt;/p&gt;
&lt;p&gt;
    如果完全没有变化，说明 TUN 没创建成功。
&lt;/p&gt;
&lt;p&gt;
    这时候重点查：
&lt;/p&gt;
&lt;p&gt;
    v2rayN 是否有管理员权限。
&lt;/p&gt;
&lt;p&gt;
    sing-box 是否正常启动。
&lt;/p&gt;
&lt;p&gt;
    TUN 配置是否开启。
&lt;/p&gt;
&lt;p&gt;
    macOS 是否阻止了相关网络权限。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    八、第四步：确认系统路由是否被接管
&lt;/h1&gt;
&lt;p&gt;
    TUN 模式不是创建虚拟网卡就结束了。
&lt;/p&gt;
&lt;p&gt;
    还要把流量导过去。
&lt;/p&gt;
&lt;p&gt;
    继续执行：
&lt;/p&gt;
&lt;pre&gt;netstat -rn -f inet | head -n 40&lt;/pre&gt;
&lt;p&gt;
    如果 TUN 生效，路由表里通常会出现和 &lt;code inline=&quot;&quot;&gt;utun&lt;/code&gt; 有关的路由。
&lt;/p&gt;
&lt;p&gt;
    如果没有任何路由变化，说明：
&lt;/p&gt;
&lt;p&gt;
    TUN 网卡可能起来了，但系统流量没走进去。
&lt;/p&gt;
&lt;p&gt;
    这时候重点查 v2rayN 的 TUN 配置。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    九、第五步：检查 TUN 的关键配置
&lt;/h1&gt;
&lt;p&gt;
    在 v2rayN 的 TUN 设置里，重点看这些选项：
&lt;/p&gt;
&lt;pre&gt;Enable TUN：开启
Stack：mixed / system / gvisor，优先 mixed
Auto Route：开启
Strict Route：开启
Sniff：开启
DNS Hijack：开启&lt;/pre&gt;
&lt;p&gt;
    其中最关键的是：
&lt;/p&gt;
&lt;pre&gt;Auto Route
DNS Hijack&lt;/pre&gt;
&lt;p&gt;
    Auto Route 负责让系统流量走 TUN。
&lt;/p&gt;
&lt;p&gt;
    DNS Hijack 负责把 DNS 查询也接管过来。
&lt;/p&gt;
&lt;p&gt;
    如果只开了 TUN，但没开 Auto Route，可能出现：
&lt;/p&gt;
&lt;p&gt;
    TUN 网卡存在，但流量没进去。
&lt;/p&gt;
&lt;p&gt;
    如果 DNS 没接管，可能出现：
&lt;/p&gt;
&lt;p&gt;
    IP 流量走了代理，但域名解析还是污染或失败。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十、第六步：检查 10808 和 10809 端口
&lt;/h1&gt;
&lt;p&gt;
    执行：
&lt;/p&gt;
&lt;pre&gt;lsof -nP -iTCP:10808 -sTCP:LISTEN || true
lsof -nP -iTCP:10809 -sTCP:LISTEN || true&lt;/pre&gt;
&lt;p&gt;
    正常情况下：
&lt;/p&gt;
&lt;pre&gt;10808：SOCKS5 代理
10809：HTTP 代理&lt;/pre&gt;
&lt;p&gt;
    但不同客户端配置不一样。
&lt;/p&gt;
&lt;p&gt;
    如果只看到 10808，没有 10809，说明 HTTP 代理没有开。
&lt;/p&gt;
&lt;p&gt;
    这时候如果某些软件使用的是：
&lt;/p&gt;
&lt;pre&gt;http://127.0.0.1:10809&lt;/pre&gt;
&lt;p&gt;
    就会失败。
&lt;/p&gt;
&lt;p&gt;
    可以临时改成 SOCKS5：
&lt;/p&gt;
&lt;pre&gt;socks5h://127.0.0.1:10808&lt;/pre&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十一、第七步：用三条命令判断问题在哪
&lt;/h1&gt;
&lt;h2&gt;
    1. 测试直连
&lt;/h2&gt;
&lt;pre&gt;curl -I --connect-timeout 8 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    如果失败，说明当前系统默认链路不通。
&lt;/p&gt;
&lt;h2&gt;
    2. 测试 SOCKS5
&lt;/h2&gt;
&lt;pre&gt;curl -I --socks5-hostname 127.0.0.1:10808 --connect-timeout 8 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    如果成功，说明节点和 SOCKS5 代理正常。
&lt;/p&gt;
&lt;h2&gt;
    3. 测试 HTTP 代理
&lt;/h2&gt;
&lt;pre&gt;curl -I -x http://127.0.0.1:10809 --connect-timeout 8 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    如果失败并提示：
&lt;/p&gt;
&lt;pre&gt;Connection refused&lt;/pre&gt;
&lt;p&gt;
    说明 10809 端口没有监听。
&lt;/p&gt;
&lt;p&gt;
    这时候不要怀疑节点。
&lt;/p&gt;
&lt;p&gt;
    应该去检查 HTTP 入站配置。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十二、完整排查脚本
&lt;/h1&gt;
&lt;p&gt;
    可以直接复制执行：
&lt;/p&gt;
&lt;pre&gt;echo &amp;quot;=== core process ===&amp;quot;
ps aux | grep -Ei &amp;quot;v2ray|v2rayn|xray|sing-box&amp;quot; | grep -v grep

echo &amp;quot;=== ports ===&amp;quot;
lsof -nP -iTCP:10808 -sTCP:LISTEN || true
lsof -nP -iTCP:10809 -sTCP:LISTEN || true

echo &amp;quot;=== tun interfaces ===&amp;quot;
ifconfig | grep -Ei &amp;quot;utun|sing|tun&amp;quot; -A 8

echo &amp;quot;=== route ===&amp;quot;
netstat -rn -f inet | head -n 40

echo &amp;quot;=== direct test ===&amp;quot;
curl -I --connect-timeout 8 https://www.google.com

echo &amp;quot;=== socks test ===&amp;quot;
curl -I --socks5-hostname 127.0.0.1:10808 --connect-timeout 8 https://www.google.com

echo &amp;quot;=== http proxy test ===&amp;quot;
curl -I -x http://127.0.0.1:10809 --connect-timeout 8 https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    判断方法：
&lt;/p&gt;
&lt;pre&gt;SOCKS 成功，直连失败，没有 utun：
TUN 没创建成功。

SOCKS 成功，直连失败，有 utun：
TUN 创建了，但路由或 DNS 没接管。

SOCKS 成功，HTTP 代理失败：
10809 没开，软件不要配置 http://127.0.0.1:10809。

SOCKS 失败：
节点、订阅、核心、远程链路有问题。

直连成功：
系统网络本身可以访问，不一定说明 TUN 生效。&lt;/pre&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十三、临时解决方案：先用 SOCKS5 接管系统代理
&lt;/h1&gt;
&lt;p&gt;
    如果 TUN 一时修不好，可以先用系统代理顶上。
&lt;/p&gt;
&lt;p&gt;
    先确认网络服务名：
&lt;/p&gt;
&lt;pre&gt;networksetup -listallnetworkservices&lt;/pre&gt;
&lt;p&gt;
    一般是：
&lt;/p&gt;
&lt;pre&gt;Wi-Fi&lt;/pre&gt;
&lt;p&gt;
    然后执行：
&lt;/p&gt;
&lt;pre&gt;networksetup -setwebproxystate &amp;quot;Wi-Fi&amp;quot; off
networksetup -setsecurewebproxystate &amp;quot;Wi-Fi&amp;quot; off
networksetup -setsocksfirewallproxy &amp;quot;Wi-Fi&amp;quot; 127.0.0.1 10808
networksetup -setsocksfirewallproxystate &amp;quot;Wi-Fi&amp;quot; on&lt;/pre&gt;
&lt;p&gt;
    然后测试：
&lt;/p&gt;
&lt;pre&gt;curl -I https://www.google.com&lt;/pre&gt;
&lt;p&gt;
    如果能通，说明系统代理已经把流量导到 SOCKS5 了。
&lt;/p&gt;
&lt;p&gt;
    这不是 TUN，但可以先解决访问问题。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十四、恢复系统代理
&lt;/h1&gt;
&lt;p&gt;
    如果后面不想走系统代理了，执行：
&lt;/p&gt;
&lt;pre&gt;networksetup -setsocksfirewallproxystate &amp;quot;Wi-Fi&amp;quot; off
networksetup -setwebproxystate &amp;quot;Wi-Fi&amp;quot; off
networksetup -setsecurewebproxystate &amp;quot;Wi-Fi&amp;quot; off&lt;/pre&gt;
&lt;p&gt;
    然后再测试网络。
&lt;/p&gt;
&lt;hr/&gt;
&lt;h1&gt;
    十五、最终修复路线
&lt;/h1&gt;
&lt;p&gt;
    这类问题不要乱猜。
&lt;/p&gt;
&lt;p&gt;
    按这个顺序来：
&lt;/p&gt;
&lt;pre&gt;1. 确认 SOCKS5 10808 是否可用。
2. 如果 SOCKS5 可用，说明节点没坏。
3. 检查 TUN 是否创建 utun 网卡。
4. 检查路由是否走 utun。
5. 检查 Auto Route 是否开启。
6. 检查 DNS Hijack 是否开启。
7. 检查 10809 HTTP 代理是否真的监听。
8. TUN 修不好时，先用 SOCKS5 系统代理临时解决。&lt;/pre&gt;
&lt;p&gt;
    这次问题最核心的一句话是：
&lt;/p&gt;
&lt;p&gt;
    节点没坏，v2rayN 也不是完全不能用。
&lt;/p&gt;
&lt;p&gt;
    真正的问题是：
&lt;/p&gt;
&lt;p&gt;
    TUN 没有把系统流量接管过来。
&lt;/p&gt;
&lt;p&gt;
    所以，不要一上来就重装。
&lt;/p&gt;
&lt;p&gt;
    先看端口。
&lt;/p&gt;
&lt;p&gt;
    再看 utun。
&lt;/p&gt;
&lt;p&gt;
    再看路由。
&lt;/p&gt;
&lt;p&gt;
    再看 DNS。
&lt;/p&gt;
&lt;p&gt;
    这样排查，才不会越修越乱。
&lt;/p&gt;</description><pubDate>Sat, 30 May 2026 21:08:32 +0800</pubDate></item><item><title>用 chatgpt 的 session 代充值</title><link>https://www.jiangtian.top/?id=16352</link><description>&lt;p&gt;问：&lt;/p&gt;&lt;p&gt;plus 充值，请先浏览器登录 https://chatgpt.com，之后打开 &lt;a href=&quot;https://chatgpt.com/api/auth/session，点进去，把里面的内容全选复制给我即可这个的充值原理是啥？&quot; _src=&quot;https://chatgpt.com/api/auth/session，点进去，把里面的内容全选复制给我即可这个的充值原理是啥？&quot;&gt;https://chatgpt.com/api/auth/session，点进去，把里面的内容全选复制给我即可这个的充值原理是啥？&lt;/a&gt; &lt;/p&gt;&lt;p&gt;答：&lt;/p&gt;&lt;p data-path-to-node=&quot;0&quot;&gt;这种充值方式的原理，简单来说就是提取你账号的临时「通行证」（Access Token），让代充商家可以在不获取你账号密码的情况下，直接以你的身份进入系统完成支付。&lt;/p&gt;&lt;h3 data-path-to-node=&quot;1&quot;&gt;核心原理拆解&lt;/h3&gt;&lt;ul data-path-to-node=&quot;2&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;2,0,0&quot;&gt;获取会话凭证：你打开的那个链接，返回的是你当前登录状态的底层数据。在这堆数据中，最关键的一项叫做「accessToken」。这相当于你账号当前的一把临时钥匙。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;2,1,0&quot;&gt;规避风控拦截：ChatGPT 对支付环境的风控极其严格。如果商家直接用你的账号密码在他们的电脑（异地 IP）上登录，极大概率会触发 OpenAI 的安全警报，导致无法调出支付页面甚至直接封号。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;2,2,0&quot;&gt;伪造本人操作：商家拿到你复制的「accessToken」后，可以通过修改浏览器请求头等技术手段，把这把钥匙挂在他们自己的网络请求上。服务器一看到这把合法的钥匙，就会误以为是你在自己的设备上进行操作。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;2,3,0&quot;&gt;完成绑卡扣款：绕过风控后，商家就能顺利进入账单系统，绑定他们自己的虚拟信用卡完成扣款，从而让你的账号升级为 Plus。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 data-path-to-node=&quot;3&quot;&gt;潜在的安全与隐私风险&lt;/h3&gt;&lt;ul data-path-to-node=&quot;4&quot; class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,0,0&quot;&gt;隐私完全暴露：这把临时钥匙的权限非常高。在它失效之前，商家不仅能帮你充值，理论上也可以读取、浏览甚至导出你的所有历史对话记录。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,1,0&quot;&gt;信用卡连带封号：第三方代充通常使用的是批量生成的虚拟信用卡。如果这张卡存在风险（比如拒付、灰产来源），OpenAI 的支付网关在后续清算时一旦发现异常，会直接封禁使用了该卡的账号。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;5&quot;&gt;如果你已经通过这种方式完成了充值，建议立刻前往 ChatGPT 的设置（Settings）中，找到安全相关选项并点击「Log out of all devices」（登出所有设备）。这个操作可以强制让你之前提取的「accessToken」立即失效，从而阻断商家继续访问你账号的可能。&lt;/p&gt;</description><pubDate>Sat, 30 May 2026 19:38:45 +0800</pubDate></item><item><title>Mac M1 上 v2rayN 提示“中国大陆证书问题访问不了”：一次真实排障复盘</title><link>https://www.jiangtian.top/?id=16351</link><description>&lt;h2&gt;一、问题背景&lt;/h2&gt;&lt;p&gt;在 Mac M1 / Apple Silicon 设备上安装 v2rayN 后，用户反馈访问部分网站失败，v2rayN 提示类似：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;中国大陆有证书问题，访问不了。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;很多人第一反应会认为是“证书坏了”“系统证书不对”“网站证书异常”。但从实际排查过程看，这类问题不一定真是证书问题。&lt;/p&gt;&lt;p&gt;这次案例的真实原因是：&lt;/p&gt;&lt;pre&gt;v2rayN&amp;nbsp;主程序启动了
xray&amp;nbsp;/&amp;nbsp;sing-box&amp;nbsp;核心也启动了
但&amp;nbsp;macOS&amp;nbsp;系统代理没有启用
而且实际代理端口不是默认的&amp;nbsp;10809&amp;nbsp;/&amp;nbsp;10808
同时&amp;nbsp;DNS&amp;nbsp;解析也存在异常&lt;/pre&gt;&lt;p&gt;所以最终表现出来像是 TLS / SSL / 证书问题，但底层真正的问题是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;系统流量没有进入 v2rayN 代理，而是在直连；直连时 DNS 又解析异常，导致 TLS 握手超时。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2&gt;二、先看现象：为什么看起来像“证书问题”？&lt;/h2&gt;&lt;p&gt;执行检测脚本时，直连访问 &lt;code inline=&quot;&quot;&gt;https://www.google.com&lt;/code&gt; 出现了：&lt;/p&gt;&lt;pre&gt;SSL&amp;nbsp;connection&amp;nbsp;timeout
curl:&amp;nbsp;(28)&amp;nbsp;SSL&amp;nbsp;connection&amp;nbsp;timeout&lt;/pre&gt;&lt;p&gt;这句话非常关键。&lt;/p&gt;&lt;p&gt;它不是：&lt;/p&gt;&lt;pre&gt;SSL&amp;nbsp;certificate&amp;nbsp;problem
certificate&amp;nbsp;verify&amp;nbsp;failed
unable&amp;nbsp;to&amp;nbsp;get&amp;nbsp;local&amp;nbsp;issuer&amp;nbsp;certificate&lt;/pre&gt;&lt;p&gt;如果是上面这类报错，才更像是系统证书链、根证书、目标证书或者中间人证书问题。&lt;/p&gt;&lt;p&gt;但本次是：&lt;/p&gt;&lt;pre&gt;SSL&amp;nbsp;connection&amp;nbsp;timeout&lt;/pre&gt;&lt;p&gt;这说明连接已经到了 TLS 握手阶段，但握手一直没有完成，最后超时。&lt;/p&gt;&lt;p&gt;通俗说就是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;不是证书校验失败，而是连到一个不正常的目标之后，TLS 握手卡住了。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2&gt;三、第一处异常：DNS 解析不正常&lt;/h2&gt;&lt;p&gt;检测结果里，&lt;code inline=&quot;&quot;&gt;www.google.com&lt;/code&gt; 被解析成了：&lt;/p&gt;&lt;pre&gt;157.240.7.20&lt;/pre&gt;&lt;p&gt;这个结果明显不正常。&lt;/p&gt;&lt;p&gt;&lt;code inline=&quot;&quot;&gt;157.240.x.x&lt;/code&gt; 更像 Meta / Facebook 相关 IP 段，不应该是 Google 常规解析结果。&lt;/p&gt;&lt;p&gt;同时 curl 里还出现了：&lt;/p&gt;&lt;pre&gt;IPv6:&amp;nbsp;2001::1
IPv4:&amp;nbsp;157.240.7.20&lt;/pre&gt;&lt;p&gt;这说明当前网络环境下 DNS 解析有较大概率存在以下情况之一：&lt;/p&gt;&lt;pre&gt;1.&amp;nbsp;DNS&amp;nbsp;污染
2.&amp;nbsp;路由器&amp;nbsp;/&amp;nbsp;热点&amp;nbsp;DNS&amp;nbsp;异常
3.&amp;nbsp;上游&amp;nbsp;DNS&amp;nbsp;不可靠
4.&amp;nbsp;网络链路对特定域名做了错误解析&lt;/pre&gt;&lt;p&gt;DNS 解析错了之后，浏览器或者 curl 就会连到错误的 IP。连到错误 IP 后，TLS 握手自然容易失败、卡住、超时。&lt;/p&gt;&lt;p&gt;所以这里的第一条判断是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;直连链路不可靠，DNS 已经异常。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2&gt;四、第二处异常：v2rayN 默认代理端口没有监听&lt;/h2&gt;&lt;p&gt;检测脚本默认测试了两个常见端口：&lt;/p&gt;&lt;pre&gt;HTTP&amp;nbsp;代理端口：10809
SOCKS5&amp;nbsp;代理端口：10808&lt;/pre&gt;&lt;p&gt;但是检测结果显示：&lt;/p&gt;&lt;pre&gt;127.0.0.1:10809&amp;nbsp;没有监听
127.0.0.1:10808&amp;nbsp;没有监听&lt;/pre&gt;&lt;p&gt;随后通过 curl 测试代理访问：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;-x&amp;nbsp;http://127.0.0.1:10809&amp;nbsp;https://www.google.com&lt;/pre&gt;&lt;p&gt;返回：&lt;/p&gt;&lt;pre&gt;Connection&amp;nbsp;refused
Failed&amp;nbsp;to&amp;nbsp;connect&amp;nbsp;to&amp;nbsp;127.0.0.1&amp;nbsp;port&amp;nbsp;10809&lt;/pre&gt;&lt;p&gt;再测 SOCKS5：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;--socks5-hostname&amp;nbsp;127.0.0.1:10808&amp;nbsp;https://www.google.com&lt;/pre&gt;&lt;p&gt;同样返回：&lt;/p&gt;&lt;pre&gt;Connection&amp;nbsp;refused
Failed&amp;nbsp;to&amp;nbsp;connect&amp;nbsp;to&amp;nbsp;127.0.0.1&amp;nbsp;port&amp;nbsp;10808&lt;/pre&gt;&lt;p&gt;&lt;code inline=&quot;&quot;&gt;Connection refused&lt;/code&gt; 的含义非常直接：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这个端口上没有程序在接收连接。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;也就是说，不是节点不通，也不是远端服务器拒绝，而是本机 &lt;code inline=&quot;&quot;&gt;127.0.0.1:10809&lt;/code&gt; / &lt;code inline=&quot;&quot;&gt;127.0.0.1:10808&lt;/code&gt; 这两个代理端口根本没开。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;五、第三处异常：v2rayN 核心其实启动了，但监听的是另一个端口&lt;/h2&gt;&lt;p&gt;继续检查进程：&lt;/p&gt;&lt;pre&gt;ps&amp;nbsp;aux&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-Ei&amp;nbsp;&amp;quot;v2ray|v2rayn|xray|sing-box|mihomo|clash&amp;quot;&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-v&amp;nbsp;grep&lt;/pre&gt;&lt;p&gt;可以看到：&lt;/p&gt;&lt;pre&gt;v2rayN&amp;nbsp;主程序已启动
sing-box&amp;nbsp;已启动
xray&amp;nbsp;已启动&lt;/pre&gt;&lt;p&gt;这说明 v2rayN 不是完全没运行。&lt;/p&gt;&lt;p&gt;再看监听端口：&lt;/p&gt;&lt;pre&gt;lsof&amp;nbsp;-nP&amp;nbsp;-iTCP&amp;nbsp;-sTCP:LISTEN&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-Ei&amp;nbsp;&amp;quot;108|789|2017|2080|9090|v2ray|xray|sing|clash|mihomo&amp;quot;&lt;/pre&gt;&lt;p&gt;发现实际监听的是：&lt;/p&gt;&lt;pre&gt;xray&amp;nbsp;TCP&amp;nbsp;127.0.0.1:59022&amp;nbsp;(LISTEN)&lt;/pre&gt;&lt;p&gt;这一步直接把问题定位清楚了：&lt;/p&gt;&lt;pre&gt;你以为代理端口是&amp;nbsp;10809&amp;nbsp;/&amp;nbsp;10808
但真实监听端口是&amp;nbsp;59022&lt;/pre&gt;&lt;p&gt;所以之前拿 &lt;code inline=&quot;&quot;&gt;10809 / 10808&lt;/code&gt; 测代理，当然会失败。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;六、第四处异常：macOS 系统代理没有启用&lt;/h2&gt;&lt;p&gt;继续检查 macOS 系统代理：&lt;/p&gt;&lt;pre&gt;networksetup&amp;nbsp;-getwebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsecurewebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsocksfirewallproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;结果显示：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;No
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;15236

Enabled:&amp;nbsp;No
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;15236

Enabled:&amp;nbsp;No
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;15235&lt;/pre&gt;&lt;p&gt;这里有两个关键信息。&lt;/p&gt;&lt;p&gt;第一，&lt;code inline=&quot;&quot;&gt;Enabled: No&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;系统代理没有启用&lt;/pre&gt;&lt;p&gt;第二，系统记录的旧端口是：&lt;/p&gt;&lt;pre&gt;15235&amp;nbsp;/&amp;nbsp;15236&lt;/pre&gt;&lt;p&gt;但实际 xray 监听的是：&lt;/p&gt;&lt;pre&gt;59022&lt;/pre&gt;&lt;p&gt;这说明系统代理配置和 v2rayN 当前实际监听端口完全错位。&lt;/p&gt;&lt;p&gt;最终状态就是：&lt;/p&gt;&lt;pre&gt;v2rayN&amp;nbsp;核心在跑
但&amp;nbsp;macOS&amp;nbsp;系统流量没有走它
浏览器&amp;nbsp;/&amp;nbsp;curl&amp;nbsp;/&amp;nbsp;系统请求仍然在直连
直连&amp;nbsp;DNS&amp;nbsp;又异常
于是表现为&amp;nbsp;SSL&amp;nbsp;/&amp;nbsp;TLS&amp;nbsp;/&amp;nbsp;证书类错误&lt;/pre&gt;&lt;p&gt;一句话概括：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;代理核心在旁边跑着，但系统流量没有进代理通道。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr/&gt;&lt;h2&gt;七、正确排查命令清单&lt;/h2&gt;&lt;h3&gt;1. 查看 v2rayN / xray / sing-box 是否运行&lt;/h3&gt;&lt;pre&gt;ps&amp;nbsp;aux&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-Ei&amp;nbsp;&amp;quot;v2ray|v2rayn|xray|sing-box|mihomo|clash&amp;quot;&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-v&amp;nbsp;grep&lt;/pre&gt;&lt;p&gt;如果没有任何输出，说明 v2rayN 或核心没起来。&lt;/p&gt;&lt;p&gt;如果能看到：&lt;/p&gt;&lt;pre&gt;v2rayN
xray
sing-box&lt;/pre&gt;&lt;p&gt;说明主程序和核心至少已经启动。&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;2. 查看本机实际监听端口&lt;/h3&gt;&lt;pre&gt;lsof&amp;nbsp;-nP&amp;nbsp;-iTCP&amp;nbsp;-sTCP:LISTEN&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-Ei&amp;nbsp;&amp;quot;108|789|2017|2080|9090|v2ray|xray|sing|clash|mihomo&amp;quot;&lt;/pre&gt;&lt;p&gt;重点看类似：&lt;/p&gt;&lt;pre&gt;127.0.0.1:59022&amp;nbsp;(LISTEN)&lt;/pre&gt;&lt;p&gt;这个端口才是当前真实代理端口。&lt;/p&gt;&lt;p&gt;不要死盯 &lt;code inline=&quot;&quot;&gt;10809 / 10808&lt;/code&gt;，因为 v2rayN 可能会动态生成或使用其他端口。&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;3. 测试这个端口是 HTTP 代理还是 SOCKS5 代理&lt;/h3&gt;&lt;p&gt;假设实际监听端口是 &lt;code inline=&quot;&quot;&gt;59022&lt;/code&gt;，先测 HTTP：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;-x&amp;nbsp;http://127.0.0.1:59022&amp;nbsp;--connect-timeout&amp;nbsp;10&amp;nbsp;--max-time&amp;nbsp;20&amp;nbsp;https://www.google.com&amp;nbsp;-o&amp;nbsp;/dev/null&lt;/pre&gt;&lt;p&gt;再测 SOCKS5：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;--socks5-hostname&amp;nbsp;127.0.0.1:59022&amp;nbsp;--connect-timeout&amp;nbsp;10&amp;nbsp;--max-time&amp;nbsp;20&amp;nbsp;https://www.google.com&amp;nbsp;-o&amp;nbsp;/dev/null&lt;/pre&gt;&lt;p&gt;判断规则：&lt;/p&gt;&lt;pre&gt;HTTP&amp;nbsp;那条成功：59022&amp;nbsp;是&amp;nbsp;HTTP&amp;nbsp;代理端口
SOCKS5&amp;nbsp;那条成功：59022&amp;nbsp;是&amp;nbsp;SOCKS5&amp;nbsp;代理端口
两条都失败：核心配置、节点、端口类型或入站配置可能有问题&lt;/pre&gt;&lt;hr/&gt;&lt;h3&gt;4. 查看 macOS 系统代理状态&lt;/h3&gt;&lt;pre&gt;networksetup&amp;nbsp;-getwebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsecurewebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsocksfirewallproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;如果你不是用 Wi-Fi，可以先查看网络服务名称：&lt;/p&gt;&lt;pre&gt;networksetup&amp;nbsp;-listallnetworkservices&lt;/pre&gt;&lt;p&gt;常见结果：&lt;/p&gt;&lt;pre&gt;USB&amp;nbsp;10/100/1000&amp;nbsp;LAN
Wi-Fi
iPhone&amp;nbsp;USB
Thunderbolt&amp;nbsp;Bridge&lt;/pre&gt;&lt;p&gt;如果当前网络是 Wi-Fi，就继续用 &lt;code inline=&quot;&quot;&gt;Wi-Fi&lt;/code&gt;。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;八、修复方法一：在 v2rayN 里开启系统代理&lt;/h2&gt;&lt;p&gt;最推荐先用图形界面操作。&lt;/p&gt;&lt;p&gt;打开 v2rayN，找到类似选项：&lt;/p&gt;&lt;pre&gt;设置系统代理
自动配置系统代理
开启系统代理
Set&amp;nbsp;system&amp;nbsp;proxy
System&amp;nbsp;Proxy&lt;/pre&gt;&lt;p&gt;开启后，再执行：&lt;/p&gt;&lt;pre&gt;networksetup&amp;nbsp;-getwebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsecurewebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsocksfirewallproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;看到：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;Yes&lt;/pre&gt;&lt;p&gt;才说明系统代理真的打开了。&lt;/p&gt;&lt;p&gt;如果仍然是：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;No&lt;/pre&gt;&lt;p&gt;那说明 v2rayN 没有成功写入 macOS 系统代理配置。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;九、修复方法二：手动设置 macOS 系统代理&lt;/h2&gt;&lt;p&gt;如果确认 &lt;code inline=&quot;&quot;&gt;59022&lt;/code&gt; 是 SOCKS5 代理端口，可以执行：&lt;/p&gt;&lt;pre&gt;sudo&amp;nbsp;networksetup&amp;nbsp;-setsocksfirewallproxy&amp;nbsp;Wi-Fi&amp;nbsp;127.0.0.1&amp;nbsp;59022
sudo&amp;nbsp;networksetup&amp;nbsp;-setsocksfirewallproxystate&amp;nbsp;Wi-Fi&amp;nbsp;on&lt;/pre&gt;&lt;p&gt;验证：&lt;/p&gt;&lt;pre&gt;networksetup&amp;nbsp;-getsocksfirewallproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;正常应该显示：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;Yes
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;59022&lt;/pre&gt;&lt;p&gt;如果确认 &lt;code inline=&quot;&quot;&gt;59022&lt;/code&gt; 是 HTTP 代理端口，可以执行：&lt;/p&gt;&lt;pre&gt;sudo&amp;nbsp;networksetup&amp;nbsp;-setwebproxy&amp;nbsp;Wi-Fi&amp;nbsp;127.0.0.1&amp;nbsp;59022
sudo&amp;nbsp;networksetup&amp;nbsp;-setsecurewebproxy&amp;nbsp;Wi-Fi&amp;nbsp;127.0.0.1&amp;nbsp;59022

sudo&amp;nbsp;networksetup&amp;nbsp;-setwebproxystate&amp;nbsp;Wi-Fi&amp;nbsp;on
sudo&amp;nbsp;networksetup&amp;nbsp;-setsecurewebproxystate&amp;nbsp;Wi-Fi&amp;nbsp;on&lt;/pre&gt;&lt;p&gt;验证：&lt;/p&gt;&lt;pre&gt;networksetup&amp;nbsp;-getwebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsecurewebproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;正常应该显示：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;Yes
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;59022&lt;/pre&gt;&lt;hr/&gt;&lt;h2&gt;十、修复方法三：处理 DNS 异常&lt;/h2&gt;&lt;p&gt;本次日志里 DNS 也明显不正常，所以建议把 DNS 改成更稳定的公共 DNS。&lt;/p&gt;&lt;p&gt;执行：&lt;/p&gt;&lt;pre&gt;sudo&amp;nbsp;networksetup&amp;nbsp;-setdnsservers&amp;nbsp;Wi-Fi&amp;nbsp;1.1.1.1&amp;nbsp;8.8.8.8&lt;/pre&gt;&lt;p&gt;然后清理 DNS 缓存：&lt;/p&gt;&lt;pre&gt;sudo&amp;nbsp;dscacheutil&amp;nbsp;-flushcache
sudo&amp;nbsp;killall&amp;nbsp;-HUP&amp;nbsp;mDNSResponder&lt;/pre&gt;&lt;p&gt;重新测试：&lt;/p&gt;&lt;pre&gt;dig&amp;nbsp;www.google.com&lt;/pre&gt;&lt;p&gt;如果 DNS 结果仍然异常，说明当前网络链路本身可能存在 DNS 污染或拦截。这时候更要先保证系统代理真正生效。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;十一、如何判断已经修好？&lt;/h2&gt;&lt;h3&gt;1. 系统代理启用&lt;/h3&gt;&lt;pre&gt;networksetup&amp;nbsp;-getwebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsecurewebproxy&amp;nbsp;Wi-Fi
networksetup&amp;nbsp;-getsocksfirewallproxy&amp;nbsp;Wi-Fi&lt;/pre&gt;&lt;p&gt;至少对应协议要看到：&lt;/p&gt;&lt;pre&gt;Enabled:&amp;nbsp;Yes
Server:&amp;nbsp;127.0.0.1
Port:&amp;nbsp;正确端口&lt;/pre&gt;&lt;hr/&gt;&lt;h3&gt;2. 代理端口有监听&lt;/h3&gt;&lt;pre&gt;lsof&amp;nbsp;-nP&amp;nbsp;-iTCP&amp;nbsp;-sTCP:LISTEN&amp;nbsp;|&amp;nbsp;grep&amp;nbsp;-Ei&amp;nbsp;&amp;quot;v2ray|xray|sing|59022|108|789&amp;quot;&lt;/pre&gt;&lt;p&gt;要看到类似：&lt;/p&gt;&lt;pre&gt;127.0.0.1:59022&amp;nbsp;(LISTEN)&lt;/pre&gt;&lt;hr/&gt;&lt;h3&gt;3. 通过代理访问成功&lt;/h3&gt;&lt;p&gt;HTTP 代理测试：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;-x&amp;nbsp;http://127.0.0.1:59022&amp;nbsp;https://www.google.com&amp;nbsp;-o&amp;nbsp;/dev/null&lt;/pre&gt;&lt;p&gt;或者 SOCKS5 测试：&lt;/p&gt;&lt;pre&gt;curl&amp;nbsp;-Iv&amp;nbsp;--socks5-hostname&amp;nbsp;127.0.0.1:59022&amp;nbsp;https://www.google.com&amp;nbsp;-o&amp;nbsp;/dev/null&lt;/pre&gt;&lt;p&gt;如果看到：&lt;/p&gt;&lt;pre&gt;HTTP/2&amp;nbsp;200
HTTP/1.1&amp;nbsp;200&lt;/pre&gt;&lt;p&gt;或者没有 TLS 超时报错，说明代理链路基本正常。&lt;/p&gt;&lt;hr/&gt;&lt;h2&gt;十二、这类问题的排查模型&lt;/h2&gt;&lt;p&gt;以后遇到 v2rayN、Clash、sing-box、xray 类似问题，不要一上来就重装。&lt;/p&gt;&lt;p&gt;按这个顺序排：&lt;/p&gt;&lt;pre&gt;第一步：程序是否启动
第二步：核心是否启动
第三步：本地端口是否监听
第四步：系统代理是否开启
第五步：系统代理端口是否和实际监听端口一致
第六步：DNS&amp;nbsp;是否异常
第七步：通过代理&amp;nbsp;curl&amp;nbsp;是否成功
第八步：再考虑证书问题&lt;/pre&gt;&lt;p&gt;很多所谓“证书问题”，本质并不是证书坏了，而是：&lt;/p&gt;&lt;pre&gt;DNS&amp;nbsp;解析错了
系统代理没开
端口配错了
代理核心没监听
系统流量没走代理&lt;/pre&gt;&lt;hr/&gt;&lt;h2&gt;十三、本次案例最终结论&lt;/h2&gt;&lt;p&gt;本次问题不是典型证书问题。&lt;/p&gt;&lt;p&gt;真实原因是：&lt;/p&gt;&lt;pre&gt;1.&amp;nbsp;v2rayN&amp;nbsp;主程序已启动；
2.&amp;nbsp;xray&amp;nbsp;/&amp;nbsp;sing-box&amp;nbsp;核心也已启动；
3.&amp;nbsp;但默认检测端口&amp;nbsp;10809&amp;nbsp;/&amp;nbsp;10808&amp;nbsp;没有监听；
4.&amp;nbsp;实际监听端口是&amp;nbsp;127.0.0.1:59022；
5.&amp;nbsp;macOS&amp;nbsp;系统代理处于&amp;nbsp;Disabled；
6.&amp;nbsp;系统代理记录的端口还是旧的&amp;nbsp;15235&amp;nbsp;/&amp;nbsp;15236；
7.&amp;nbsp;当前&amp;nbsp;DNS&amp;nbsp;解析也异常；
8.&amp;nbsp;所以浏览器和&amp;nbsp;curl&amp;nbsp;仍然直连，导致&amp;nbsp;TLS&amp;nbsp;握手超时；
9.&amp;nbsp;最终表现成类似“证书问题”。&lt;/pre&gt;&lt;p&gt;一句话总结：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;v2rayN 不是没运行，而是系统流量没有接入它；修复重点不是重装证书，而是确认本地监听端口、开启系统代理，并修正 DNS。&lt;/p&gt;&lt;/blockquote&gt;</description><pubDate>Sat, 30 May 2026 19:27:23 +0800</pubDate></item><item><title>OpenClaw 报错：Managed Codex app-server binary was not found 的根因与修复方法</title><link>https://www.jiangtian.top/?id=16350</link><description>&lt;h1 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;OpenClaw 报错：Managed Codex app-server binary was not found 的根因与修复方法&lt;/h1&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;一、问题现象&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;在 OpenClaw 使用 Codex 嵌入式代理时，出现如下报错：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Embedded&amp;nbsp;agent&amp;nbsp;failed&amp;nbsp;before&amp;nbsp;reply:
Managed&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary&amp;nbsp;was&amp;nbsp;not&amp;nbsp;found&amp;nbsp;for&amp;nbsp;@openai/codex.
Reinstall&amp;nbsp;or&amp;nbsp;update&amp;nbsp;OpenClaw,&amp;nbsp;or&amp;nbsp;run&amp;nbsp;pnpm&amp;nbsp;install&amp;nbsp;in&amp;nbsp;a&amp;nbsp;source&amp;nbsp;checkout.
Set&amp;nbsp;plugins.entries.codex.config.appServer.command&amp;nbsp;or&amp;nbsp;OPENCLAW_CODEX_APP_SERVER_BIN&amp;nbsp;to&amp;nbsp;use&amp;nbsp;a&amp;nbsp;custom&amp;nbsp;Codex&amp;nbsp;binary.&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这个错误的直观表现是：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;OpenClaw 已经收到了任务，也已经尝试启动 Codex 嵌入式代理，但在真正回复之前，Codex app-server 启动失败了。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;换句话说，这不是模型回答质量问题，也不是 prompt 问题，而是 OpenClaw 在调用 Codex 本地服务时，找不到应该启动的 Codex app-server 可执行文件。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;二、第一层理解：这个错误到底是什么意思？&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这句话的核心是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Managed&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary&amp;nbsp;was&amp;nbsp;not&amp;nbsp;found&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;意思是：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;OpenClaw 的 Codex 插件默认会尝试使用它自己管理的 Codex app-server binary，也就是所谓的 managed binary。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;但当前环境里，这个 managed binary 没找到。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;所以 OpenClaw 给了三个方向：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Reinstall&amp;nbsp;or&amp;nbsp;update&amp;nbsp;OpenClaw&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;重新安装或更新 OpenClaw。&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;run&amp;nbsp;pnpm&amp;nbsp;install&amp;nbsp;in&amp;nbsp;a&amp;nbsp;source&amp;nbsp;checkout&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果你是源码安装 OpenClaw，那么进入源码目录执行&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;pnpm install&lt;/code&gt;。&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Set&amp;nbsp;plugins.entries.codex.config.appServer.command&amp;nbsp;or&amp;nbsp;OPENCLAW_CODEX_APP_SERVER_BIN&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;手动指定 Codex app-server 的启动文件。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;前两个办法适合普通安装缺失、源码依赖缺失的问题；但如果你反复重装仍然不行，就说明真正的问题不在 OpenClaw 主程序本身，而在 OpenClaw 找 Codex app-server 的路径逻辑上。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;三、为什么重装 OpenClaw 也没用？&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这类问题最容易误判成“OpenClaw 没装好”。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;但实际排查下来，重装很多次仍然报错，通常说明：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;OpenClaw 主程序是好的，Codex 也可能已经安装成功了。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;真正坏的是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;OpenClaw&amp;nbsp;没有找到正确的&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;尤其在 Windows 环境里，经常会出现这种情况：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;你已经通过 npm 安装了&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;@openai/codex&lt;/code&gt;，系统里也有&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;codex.cmd&lt;/code&gt;，但是 OpenClaw 的 Codex 插件默认并不会直接使用你 PATH 里的&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;codex.cmd&lt;/code&gt;。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;它可能仍然去找自己插件目录里的 managed binary。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;一旦这个 managed binary 没生成、没下载、没缓存成功、路径解析失败，或者 OpenClaw 当前版本在 Windows 上没有正确找到它，就会一直报：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Managed&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary&amp;nbsp;was&amp;nbsp;not&amp;nbsp;found&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;所以继续重装 OpenClaw 很可能只是重复覆盖主程序，而没有解决 Codex app-server binary 的真实路径问题。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;四、错误排查过程&lt;/h2&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;1. 第一反应：更新 OpenClaw&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;最先尝试的是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;update
openclaw&amp;nbsp;doctor&amp;nbsp;--non-interactive&amp;nbsp;--fix
openclaw&amp;nbsp;gateway&amp;nbsp;restart&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这个方向是合理的，因为如果只是 OpenClaw 安装不完整，更新和 doctor 修复可能解决。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;但如果重装、更新、doctor 都无效，就要换思路。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;2. 第二步：检查旧配置是否覆盖&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;然后排查&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;openclaw.json&lt;/code&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;里是否有旧的 Codex 配置，尤其是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;&amp;quot;appServer&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;quot;command&amp;quot;:&amp;nbsp;&amp;quot;...&amp;quot;
}&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果这里写错路径，OpenClaw 会按错误路径启动 Codex。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;所以之前尝试过删除：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;plugins.entries.codex.config.appServer&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;并清理环境变量：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;$null,&amp;nbsp;&amp;quot;User&amp;quot;)
[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_ARGS&amp;quot;,&amp;nbsp;$null,&amp;nbsp;&amp;quot;User&amp;quot;)
[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_MODE&amp;quot;,&amp;nbsp;$null,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这个方向也没错，因为旧配置确实可能覆盖默认行为。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;但你的场景里，这一步仍然没有解决。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这就说明问题不是“旧配置乱指”，而是 OpenClaw 默认 managed binary 本身找不到。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;3. 第三步：确认 Codex CLI 是否独立可用&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;最终切入点是：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;不要再让 OpenClaw 自己猜 Codex 在哪里，而是先确认系统里的 Codex CLI 是否能独立运行。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;执行：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;codex.cmd&amp;nbsp;app-server&amp;nbsp;--help&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果这条命令可以正常输出帮助信息，说明&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;@openai/codex&lt;/code&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;本身没坏。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;也就是说：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Codex&amp;nbsp;能跑，OpenClaw&amp;nbsp;找不到它。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这个判断非常关键。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;它把问题从“Codex 没装好”改成了“OpenClaw 没有找到正确的 Codex 启动文件”。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;4. 第四步：找到 codex.cmd 的真实路径&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;执行：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;(Get-Command&amp;nbsp;codex.cmd).Source&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;一般会输出类似：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;C:\Users\你的用户名\AppData\Roaming\npm\codex.cmd&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这个路径才是 Windows 下 npm 全局安装生成的 Codex 命令入口。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;只要&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;codex.cmd app-server --help&lt;/code&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;能跑，这个路径就可以作为 OpenClaw 的 Codex app-server 启动入口。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;五、最终解决办法：显式指定 OPENCLAW_CODEX_APP_SERVER_BIN&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;最终解决命令是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;(Get-Command&amp;nbsp;codex.cmd).Source,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这条命令的意思是：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;把系统里的&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;codex.cmd&lt;/code&gt;&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;路径写入用户级环境变量：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;OPENCLAW_CODEX_APP_SERVER_BIN&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;OpenClaw 启动 Codex app-server 时，不再只依赖 managed binary，而是直接使用这个明确指定的 Codex 可执行文件。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;然后确认一下：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::GetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果能看到类似：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;C:\Users\你的用户名\AppData\Roaming\npm\codex.cmd&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;说明环境变量写入成功。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;接着关闭当前 PowerShell，重新打开一个新的 PowerShell。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;注意：这一步非常重要。&lt;br/&gt;环境变量写入之后，旧 PowerShell 窗口不一定立刻生效。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;然后重启 OpenClaw gateway：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;stop&lt;/pre&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;start&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;或者：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;restart&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;最后检查状态：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;status&lt;/pre&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;六、完整修复命令汇总&lt;/h2&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;1. 安装或重装 Codex CLI&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;npm&amp;nbsp;install&amp;nbsp;-g&amp;nbsp;@openai/codex&lt;/pre&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;2. 测试 Codex app-server 是否能独立运行&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;codex.cmd&amp;nbsp;app-server&amp;nbsp;--help&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果能输出帮助信息，说明 Codex 本身可用。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;3. 查看 codex.cmd 路径&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;(Get-Command&amp;nbsp;codex.cmd).Source&lt;/pre&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;4. 写入 OpenClaw Codex app-server 路径&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;(Get-Command&amp;nbsp;codex.cmd).Source,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;5. 检查环境变量是否写入成功&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::GetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;6. 关闭 PowerShell，重新打开&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这一步不要省略。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;7. 重启 OpenClaw gateway&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;stop&lt;/pre&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;start&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;或者：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;restart&lt;/pre&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;8. 查看状态&lt;/h3&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw&amp;nbsp;gateway&amp;nbsp;status&lt;/pre&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;七、为什么这个办法有效？&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;因为这个错误的本质不是 OpenClaw 没装好，而是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;OpenClaw&amp;nbsp;的&amp;nbsp;Codex&amp;nbsp;插件默认要找&amp;nbsp;managed&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary，但它没有找到。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;而我们通过：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;OPENCLAW_CODEX_APP_SERVER_BIN&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;直接告诉 OpenClaw：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;不要猜了，就用这个&amp;nbsp;codex.cmd&amp;nbsp;来启动&amp;nbsp;app-server。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这相当于绕过了 OpenClaw 默认的 managed binary 查找逻辑，直接指定了一个已经验证可用的 Codex 启动入口。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这也是为什么前面重装 OpenClaw、清理配置、删除 appServer 配置都不一定有效，而最后这一步能解决。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;八、这类问题的标准判断流程&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;以后再遇到类似报错，可以按这个顺序判断：&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;第一层：看错误关键词&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果看到：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Managed&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary&amp;nbsp;was&amp;nbsp;not&amp;nbsp;found&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;先判断为：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;OpenClaw&amp;nbsp;找不到&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;binary。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;不要先怀疑 prompt，也不要先怀疑模型。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;第二层：确认 Codex 是否独立可用&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;执行：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;codex.cmd&amp;nbsp;app-server&amp;nbsp;--help&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果这条命令失败，说明 Codex CLI 本身没装好。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果这条命令成功，说明 Codex 没坏，是 OpenClaw 找不到它。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;第三层：显式指定 Codex 路径&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;执行：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;(Get-Command&amp;nbsp;codex.cmd).Source,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;然后重启 PowerShell 和 gateway。&lt;/p&gt;&lt;h3 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;第四层：检查 OpenClaw 配置&lt;/h3&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果仍然失败，再检查：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;openclaw.json&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;重点看有没有：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;&amp;quot;appServer&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;quot;command&amp;quot;:&amp;nbsp;&amp;quot;...&amp;quot;
}&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果有错误路径，删除或者修正。&lt;/p&gt;&lt;h2 style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;九、经验总结&lt;/h2&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;这次问题最大的坑在于：&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;错误提示里第一句就说：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;Reinstall&amp;nbsp;or&amp;nbsp;update&amp;nbsp;OpenClaw&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;所以人很容易陷入“反复重装”的循环。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;但实际上，重装只能解决 OpenClaw 主程序或托管 binary 安装不完整的问题。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;如果问题是 Windows 下 Codex binary 路径解析失败，或者 OpenClaw 没有使用 npm 全局安装的&lt;span class=&quot;Apple-converted-space&quot;&gt; &lt;/span&gt;&lt;code inline=&quot;&quot;&gt;codex.cmd&lt;/code&gt;，那么重装多少遍都可能没用。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;真正有效的判断是：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;codex.cmd&amp;nbsp;app-server&amp;nbsp;--help&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;只要这条命令能跑，就说明 Codex 在系统里是好的。&lt;/p&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;接下来要做的不是重装，而是把它的路径明确告诉 OpenClaw：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;[Environment]::SetEnvironmentVariable(&amp;quot;OPENCLAW_CODEX_APP_SERVER_BIN&amp;quot;,&amp;nbsp;(Get-Command&amp;nbsp;codex.cmd).Source,&amp;nbsp;&amp;quot;User&amp;quot;)&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;最终一句话：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;这个问题不是&amp;nbsp;Codex&amp;nbsp;不存在，而是&amp;nbsp;OpenClaw&amp;nbsp;没找到正确的&amp;nbsp;Codex&amp;nbsp;app-server&amp;nbsp;启动入口。&lt;/pre&gt;&lt;p style=&quot;caret-color: rgb(0, 0, 0); white-space: normal;&quot;&gt;解决方法也很简单：&lt;/p&gt;&lt;pre style=&quot;caret-color: rgb(0, 0, 0);&quot;&gt;先确认&amp;nbsp;codex.cmd&amp;nbsp;能跑，再用&amp;nbsp;OPENCLAW_CODEX_APP_SERVER_BIN&amp;nbsp;显式指定它。&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Thu, 28 May 2026 20:56:57 +0800</pubDate></item></channel></rss>