<?xml version="1.0" encoding="UTF-8"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
      <title>matto的小屋</title>
      <link href="/atom.xml" rel="self"/>
      <link href="/feed" rel="self"/>
      <link href="https://www.matto.top"/>
      <updated>2026-05-14T08:32:23.948Z</updated>
      <id>https://www.matto.top</id>
      <author>
        <name>matto</name>
      </author>
      <generator>Mix Space CMS</generator>
      <lastBuildDate>2026-05-14T08:32:23.948Z</lastBuildDate>
      <language>zh-CN</language>
      <image>
          <url>https://api.matto.top/api/v2/objects/avatar/r8okryi0lawm57502v.jpg</url>
          <title>matto的小屋</title>
          <link>https://www.matto.top</link>
      </image>
        <entry>
            <title>web音频处理学习</title>
            <link href='https://www.matto.top/posts/frontend/web%E9%9F%B3%E9%A2%91%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0'/>
            <id>https://www.matto.top/posts/frontend/web%E9%9F%B3%E9%A2%91%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0</id>
            <published>2026-03-05T14:40:00.000Z</published>
            <updated>2026-03-05T14:40:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/frontend/web%E9%9F%B3%E9%A2%91%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0'>https://www.matto.top/posts/frontend/web%E9%9F%B3%E9%A2%91%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0</a></blockquote>
<div><div><blockquote><p>本文由 Codex（GPT-5.3-Codex）辅助编写，内容已由作者审核整理。</p></blockquote><p>线上录音这类需求，第一眼都觉得不难：开麦、录音、上传、结束。</p><p>结果真上线以后，最容易翻车的往往不是按钮逻辑，而是格式、兼容和转码链路。
这篇就把这块常见坑整理成一份通用笔记，尽量不绑具体业务，主要讲可复用的结论。</p><h2 id="格式基础">格式基础</h2><h3 id="先把几个词对齐">先把几个词对齐</h3><p>很多排障卡半天，其实是词没对齐。</p><ul><li>容器：文件怎么装数据，比如 MP4、WebM、MOV、Ogg。</li><li>编码：音视频怎么压缩，比如 AAC、Opus、MP3、H.264。</li><li>MIME：传输时怎么声明类型，比如 <code>audio/mpeg</code>、<code>audio/mp4</code>。</li></ul><p>说人话版本：容器是壳子，编码是压缩算法，MIME 是贴在外面的标签。</p><h3 id="常见音频类型">常见音频类型</h3><p><strong>MP3</strong></p><ul><li>归类：音频编码（工程里也常直接当格式名用）。</li><li>常见 MIME：<code>audio/mpeg</code>。</li><li>一句话：兼容很强，但同码率下压缩效率一般。</li></ul><p><strong>AAC（常见文件后缀是 M4A）</strong></p><ul><li>归类：AAC 是编码；M4A 通常是容器文件形态。</li><li>常见 MIME：<code>audio/mp4</code>、<code>audio/aac</code>。</li><li>一句话：同码率下通常比 MP3 更省体积。</li></ul><p><strong>Opus（常与 WebM/Ogg 搭配）</strong></p><ul><li>归类：音频编码。</li><li>常见 MIME：<code>audio/webm;codecs=opus</code>、<code>audio/ogg;codecs=opus</code>。</li><li>一句话：低码率语音效果和时延表现都不错。</li></ul><p><strong>FLAC</strong></p><ul><li>归类：无损编码。</li><li>常见 MIME：<code>audio/flac</code>。</li><li>一句话：音质保真，但体积还是偏大。</li></ul><h3 id="常见视频容器">常见视频容器</h3><p><strong>MP4</strong></p><ul><li>常见组合：H.264 + AAC。</li><li>一句话：Web 分发里最省心，兼容面广。</li></ul><p><strong>WebM</strong></p><ul><li>常见组合：VP8/VP9/AV1 + Opus。</li><li>一句话：现代浏览器支持不错，开源生态友好。</li></ul><p><strong>MOV</strong></p><ul><li>一句话：更偏生产链路和剪辑中间态。</li></ul><h2 id="录音能力与兼容性">录音能力与兼容性</h2><h3 id="web-录音涉及的核心-api">Web 录音涉及的核心 API</h3><p><strong>getUserMedia</strong></p><p>拿麦克风流的入口。</p><ul><li>接口：<code>navigator.mediaDevices.getUserMedia({ audio: true })</code></li><li>前提：HTTPS 或 localhost</li><li>常见坑：权限拒绝、设备占用、前后台切换</li></ul><p><strong>MediaRecorder</strong></p><p>把 <code>MediaStream</code> 编成 <code>Blob</code>。</p><ul><li>输入：<code>MediaStream</code></li><li>输出：<code>Blob</code></li><li>关键点：不要拍脑袋写死格式，先做 <code>isTypeSupported()</code> 探测</li></ul><p><strong>audio / 播放器库</strong></p><p>回放层。</p><ul><li>简单场景：<code>&lt;audio&gt;</code> 基本够用</li><li>要波形、拖拽、分段可视化：再上播放器库</li></ul><h3 id="istypesupported-的兼容性数据">isTypeSupported 的兼容性数据</h3><p>截至 2026-03-05（Can I Use，StatCounter 2026-02）：</p><ul><li><code>MediaRecorder.isTypeSupported()</code>：<strong>95.66%</strong></li><li><code>MediaRecorder</code>：<strong>95.16%</strong></li></ul><p>主流版本区间：</p><table><thead><tr><th>设备/浏览器</th><th>支持起始版本</th><th>当前状态</th></tr></thead><tbody><tr><td>Chrome 桌面</td><td>47+</td><td>支持</td></tr><tr><td>Edge</td><td>79+</td><td>支持</td></tr><tr><td>Firefox 桌面</td><td>25+</td><td>支持</td></tr><tr><td>Safari 桌面</td><td>14.1+</td><td>支持</td></tr><tr><td>iOS Safari</td><td>14+</td><td>支持</td></tr><tr><td>Chrome Android</td><td>支持（当前主线）</td><td>支持</td></tr><tr><td>Samsung Internet</td><td>5+</td><td>支持</td></tr><tr><td>Firefox Android</td><td>147（当前数据为支持）</td><td>支持</td></tr><tr><td>Opera Mini</td><td>unknown</td><td>不可作为前提</td></tr></tbody></table><p>这两年还有两个值得记的变化：</p><ol start="1"><li>Safari 18.4（WebKit）</li><li>MediaRecorder 增加 WebM 录制（Opus + VP8/VP9）</li><li>增加 fragmented MP4（ISOBMFF）</li><li><p>iOS 18.4 / iPadOS 18.4 / macOS 15.4 增加 Ogg 支持</p></li><li>Chrome 136（Chromium）</li><li>MediaRecorder 扩展 H26x 相关能力（<code>hvc1.*</code>、<code>hev1.*</code>、<code>avc3.*</code>）</li><li>MP4 / Matroska 组合扩展</li><li>HEVC 是否可用仍看设备和系统能力</li></ol><p>所以线上判断要分两层看：</p><ol start="1"><li>API 在不在</li><li>目标 MIME 能不能稳定录</li></ol><p>大多数坑都在第二层。</p><h2 id="mp3-转码原理与实现">MP3 转码原理与实现</h2><h3 id="转码链路总览">转码链路总览</h3><p>常见链路是：浏览器先录出 <code>webm/opus</code> 或 <code>mp4/aac</code>，再统一转 MP3。</p><table><thead><tr><th>步骤</th><th>输入</th><th>输出</th><th>变化点</th></tr></thead><tbody><tr><td>1. 录制</td><td>麦克风流</td><td><code>Blob(容器+编码)</code></td><td>浏览器按可用编码输出</td></tr><tr><td>2. 解码</td><td><code>Blob</code></td><td><code>AudioBuffer(PCM float)</code></td><td>去掉原容器与原编码</td></tr><tr><td>3. 归一化</td><td><code>AudioBuffer</code></td><td>统一 PCM</td><td>统一采样率/声道/振幅</td></tr><tr><td>4. MP3 编码</td><td>统一 PCM</td><td>MP3 帧流</td><td>分帧/量化/熵编码</td></tr><tr><td>5. 封装输出</td><td>MP3 帧流</td><td><code>.mp3</code> 文件</td><td>输出 <code>audio/mpeg</code></td></tr></tbody></table><h3 id="1.-录制：得到“容器-+-编码”">1. 录制：得到“容器 + 编码”</h3><p>这一步拿到的是容器包，不是裸 PCM。</p><pre><code class="lang-ts">const mimeCandidates = [
  &#x27;audio/webm;codecs=opus&#x27;,
  &#x27;audio/webm&#x27;,
  &#x27;audio/mp4;codecs=mp4a.40.2&#x27;,
  &#x27;audio/mp4&#x27;,
  &#x27;audio/aac&#x27;,
];

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mimeType = mimeCandidates.find((t) =&gt; MediaRecorder.isTypeSupported(t));
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
const chunks: BlobPart[] = [];

recorder.ondataavailable = (e) =&gt; e.data.size &amp;&amp; chunks.push(e.data);
recorder.start();
recorder.stop();

const recordedBlob = await new Promise&lt;Blob&gt;((resolve) =&gt; {
  recorder.onstop = () =&gt; resolve(new Blob(chunks, { type: recorder.mimeType || &#x27;audio/webm&#x27; }));
});</code></pre><h3 id="2.-解码：先回到-pcm">2. 解码：先回到 PCM</h3><p>先把容器/编码剥掉，回到统一采样表示。</p><pre><code class="lang-ts">const AudioContextCtor = window.AudioContext || (window as any).webkitAudioContext;
const ctx = new AudioContextCtor();

const arrayBuffer = await recordedBlob.arrayBuffer();
const audioBuffer = await ctx.decodeAudioData(arrayBuffer.slice(0));

// audioBuffer.getChannelData(ch) =&gt; Float32Array
// audioBuffer.sampleRate / audioBuffer.numberOfChannels

await ctx.close();</code></pre><h3 id="3.-归一化">3. 归一化</h3><p>这一步是稳定性的关键：</p><ul><li>采样率统一（例如 16k / 22.05k / 44.1k）</li><li>声道统一（语音常用单声道）</li><li>振幅裁剪或归一化（避免爆音或过小）</li></ul><pre><code class="lang-ts">function toMonoInt16(buffer: AudioBuffer): Int16Array {
  const channels = buffer.numberOfChannels;
  const length = buffer.length;
  const out = new Int16Array(length);
  const channelData = Array.from({ length: channels }, (_, i) =&gt; buffer.getChannelData(i));

  for (let i = 0; i &lt; length; i += 1) {
    let sample = 0;
    for (let ch = 0; ch &lt; channels; ch += 1) sample += channelData[ch][i] || 0;
    sample /= channels;
    sample = Math.max(-1, Math.min(1, sample));
    out[i] = sample &lt; 0 ? sample * 0x8000 : sample * 0x7fff;
  }

  return out;
}</code></pre><h3 id="4.-mp3-编码">4. MP3 编码</h3><p>浏览器原生没有直接的 PCM -&gt; MP3 编码器，常见是 <code>lamejs</code> 或 <code>ffmpeg.wasm</code>。</p><p>MP3 这步不是改后缀，而是完整编码流程（分帧、频域处理、量化、哈夫曼编码）。</p><pre><code class="lang-ts">import lamejs from &#x27;lamejs&#x27;;

function encodeMp3FromPcmInt16(pcmMono: Int16Array, sampleRate: number, kbps = 64) {
  const encoder = new lamejs.Mp3Encoder(1, sampleRate, kbps);
  const blockSize = 1152;
  const chunks: Uint8Array[] = [];

  for (let i = 0; i &lt; pcmMono.length; i += blockSize) {
    const block = pcmMono.subarray(i, i + blockSize);
    const mp3buf = encoder.encodeBuffer(block);
    if (mp3buf.length &gt; 0) chunks.push(new Uint8Array(mp3buf));
  }

  const end = encoder.flush();
  if (end.length &gt; 0) chunks.push(new Uint8Array(end));

  return new Blob(chunks, { type: &#x27;audio/mpeg&#x27; });
}</code></pre><h3 id="5.-文件层封装与-mime">5. 文件层封装与 MIME</h3><p>最后这步就是把产物以标准文件形态对外输出。</p><pre><code class="lang-ts">const mp3Blob = encodeMp3FromPcmInt16(pcmInt16, targetSampleRate, 64);
const file = new File([mp3Blob], &#x27;record.mp3&#x27;, { type: &#x27;audio/mpeg&#x27; });

const previewUrl = URL.createObjectURL(mp3Blob);
audioEl.src = previewUrl;

// await upload(file)
// URL.revokeObjectURL(previewUrl)</code></pre><h3 id="容器和编码在这条链路里的关系">容器和编码在这条链路里的关系</h3><p>一条典型转换链：</p><p><code>WebM(容器)+Opus(编码)</code> 或 <code>MP4(容器)+AAC(编码)</code>
-&gt; 解码
-&gt; <code>PCM</code>
-&gt; MP3 编码
-&gt; <code>MP3 文件(audio/mpeg)</code></p><p>所以“转 MP3”实质上是两次变化：</p><ol start="1"><li>原容器/原编码被解开，回到 PCM</li><li>PCM 再编码并输出为 MP3 文件结构</li></ol><h3 id="mime-探测顺序的实践结论">MIME 探测顺序的实践结论</h3><p>线上更稳的方式是“按设备探测”，不是固定写死一个格式。</p><pre><code class="lang-ts">[
  &#x27;audio/webm;codecs=opus&#x27;,
  &#x27;audio/webm&#x27;,
  &#x27;audio/mp4;codecs=mp4a.40.2&#x27;,
  &#x27;audio/mp4&#x27;,
  &#x27;audio/aac&#x27;,
  &#x27;audio/ogg;codecs=opus&#x27;,
]</code></pre><p>录制前逐项 <code>isTypeSupported()</code>，命中第一个就用哪个。</p><p>线上定位问题时，通常会额外记三项：</p><ol start="1"><li>机型 + 系统 + 浏览器版本</li><li>候选 MIME 的探测结果</li><li>实际录制输出的 <code>mimeType</code></li></ol><p>这三项基本能把格式问题定位个七七八八。</p><h3 id="浏览器兼容性（补充）">浏览器兼容性（补充）</h3><p>截至 2026-03-05：</p><ul><li><code>getUserMedia</code>：96.31%</li><li><code>MediaRecorder</code>：95.16%</li><li><code>&lt;audio&gt;</code>：96.24%</li></ul><h2 id="总结">总结</h2><ol start="1"><li>先把容器、编码、MIME 分清，后面排障会快很多。</li><li><code>MediaRecorder</code> 可用，不代表目标 MIME 一定可用。</li><li>转码不是改后缀，而是“解码到 PCM -&gt; 重新编码 -&gt; 封装输出”。</li><li>线上要记录探测结果和实际输出类型，不然很难追兼容问题。</li></ol><h2 id="参考">参考</h2><ul><li>MDN - <code>MediaDevices.getUserMedia()</code>：<a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia">https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia</a></li><li>MDN - <code>MediaRecorder</code>：<a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder">https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder</a></li><li>MDN - <code>MediaRecorder.isTypeSupported()</code>：<a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported_static">https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported_static</a></li><li>MDN - 音频编码指南：<a href="https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Audio_codecs">https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Audio_codecs</a></li><li>Can I Use - <code>MediaRecorder.isTypeSupported()</code>：<a href="https://caniuse.com/mdn-api_mediarecorder_istypesupported_static">https://caniuse.com/mdn-api_mediarecorder_istypesupported_static</a></li><li>Can I Use - MediaRecorder：<a href="https://caniuse.com/mediarecorder">https://caniuse.com/mediarecorder</a></li><li>Can I Use - getUserMedia：<a href="https://caniuse.com/stream">https://caniuse.com/stream</a></li><li>Can I Use - Audio element：<a href="https://caniuse.com/audio">https://caniuse.com/audio</a></li><li>WebKit - Safari 18.4 特性（MediaRecorder/WebM/Ogg）：<a href="https://webkit.org/blog/16574/webkit-features-in-safari-18-4/">https://webkit.org/blog/16574/webkit-features-in-safari-18-4/</a></li><li>Chrome 136 发布说明（MediaRecorder H26x/MP4/Matroska）：<a href="https://developer.chrome.com/release-notes/136">https://developer.chrome.com/release-notes/136</a></li></ul></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/frontend/web%E9%9F%B3%E9%A2%91%E5%A4%84%E7%90%86%E5%AD%A6%E4%B9%A0#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>certbot 续期实录：从续不上到自动续</title>
            <link href='https://www.matto.top/posts/backend/certbot%E7%BB%AD%E6%9C%9F%E5%AE%9E%E5%BD%95'/>
            <id>https://www.matto.top/posts/backend/certbot%E7%BB%AD%E6%9C%9F%E5%AE%9E%E5%BD%95</id>
            <published>2026-03-05T14:07:00.000Z</published>
            <updated>2026-03-05T14:07:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/backend/certbot%E7%BB%AD%E6%9C%9F%E5%AE%9E%E5%BD%95'>https://www.matto.top/posts/backend/certbot%E7%BB%AD%E6%9C%9F%E5%AE%9E%E5%BD%95</a></blockquote>
<div><div><p>今天帮线上机器 <code>ycy</code> 续 SSL，原本以为就是一句 <code>certbot renew</code>。
结果一查发现：<code>certbot</code> 根本没托管当前在线证书，定时任务在跑但等于白跑。</p><p>这篇就把完整过程记一下，重点是两件事：
1) 怎么把“已经过期但不在 certbot 管理里”的证书救回来；
2) 怎么补一层自动同步，避免下次又到期。</p><h2 id="先看现状，不要上来就-renew">先看现状，不要上来就 renew</h2><p>先确认远端可连、<code>certbot</code> 可用、以及当前证书到底谁在管。
这一步解决的是“定位问题归属”；坑是如果跳过，很容易在错误路径里反复重试。</p><pre><code class="lang-bash">ssh ycy &#x27;hostname &amp;&amp; whoami &amp;&amp; certbot --version&#x27;
ssh ycy &#x27;certbot certificates&#x27;</code></pre><p>我当时看到的是 <code>No certificates found.</code>。
但线上站点明明有 HTTPS，这就说明证书来源不是 <code>/etc/letsencrypt/live/...</code> 这套。</p><p>接着查 Nginx 配置里实际读的证书路径。
这一步解决的是“找到真实生效文件”；坑是很多机器迁移过后，路径会和工具默认路径分家。</p><pre><code class="lang-bash">ssh ycy &quot;grep -R --line-number --no-messages \
&#x27;ssl_certificate\|ssl_certificate_key\|fullchain.pem\|privkey.pem&#x27; \
/etc/nginx /etc/apache2 2&gt;/dev/null&quot;</code></pre><p>最后定位到：
- <code>/etc/nginx/ssl/api.matto.top_P256/fullchain.cer</code>
- <code>/etc/nginx/ssl/qing.matto.top_P256/fullchain.cer</code></p><p>再看有效期，<code>api.matto.top</code> 已在 <strong>2026-02-21</strong> 过期。</p><h2 id="为什么自动续签没生效">为什么自动续签没生效</h2><p><code>certbot</code> 的定时器是好的，但它没有可续的证书对象，所以系统看起来“有自动化”，实际上没有任何续签动作。</p><p>我也检查过 <code>acme.sh</code>，机器里并没有安装。
换句话说，这台机器就是“证书文件在用，签发链路丢了”。</p><h2 id="这次怎么续上：standalone-一把过">这次怎么续上：standalone 一把过</h2><p>原本打算用 <code>--nginx</code> 插件自动验证，但机器没装 <code>python3-certbot-nginx</code>，而且刚好碰到 <code>apt</code> 锁被系统任务占用。
为了不在安装插件上耗时间，直接走了 <code>standalone</code>：短暂停 <code>nginx</code>，签完立即拉起。</p><p>这段命令解决的是“先把证书签出来并上线”；坑是 <code>standalone</code> 需要占用 80 端口，执行期间会有几十秒中断。</p><pre><code class="lang-bash">ssh ycy &#x27;
set -e
systemctl stop nginx
certbot certonly --standalone \
  --non-interactive --agree-tos --register-unsafely-without-email \
  --key-type ecdsa \
  --cert-name matto.top-shared \
  -d api.matto.top -d qing.matto.top
systemctl start nginx
&#x27;</code></pre><p>签发成功后，证书到期时间是 <strong>2026-06-03 13:07:20 UTC</strong>。</p><p>然后把新证书同步到 Nginx 当前在用路径。
这一步解决的是“让线上立刻吃到新证书”；坑是只签发不复制，线上依然可能读旧文件。</p><pre><code class="lang-bash">ssh ycy &#x27;
install -m 600 /etc/letsencrypt/live/matto.top-shared/privkey.pem /etc/nginx/ssl/api.matto.top_P256/private.key
install -m 644 /etc/letsencrypt/live/matto.top-shared/fullchain.pem /etc/nginx/ssl/api.matto.top_P256/fullchain.cer
install -m 600 /etc/letsencrypt/live/matto.top-shared/privkey.pem /etc/nginx/ssl/qing.matto.top_P256/private.key
install -m 644 /etc/letsencrypt/live/matto.top-shared/fullchain.pem /etc/nginx/ssl/qing.matto.top_P256/fullchain.cer
nginx -t &amp;&amp; systemctl reload nginx
&#x27;</code></pre><h2 id="把自动续费补完整：续签后自动同步并重载">把自动续费补完整：续签后自动同步并重载</h2><p>如果只做到上一步，下次 <code>certbot</code> 自动续期时，<code>/etc/letsencrypt/live/...</code> 会更新，但 <code>/etc/nginx/ssl/...</code> 不会自动更新。
所以要加 <code>deploy hook</code>，在每次续签成功后自动复制并 reload。</p><p>这段脚本解决的是“续签和线上生效打通”；坑是没有 <code>RENEWED_LINEAGE</code> 判断的话，可能影响其他证书。</p><pre><code class="lang-bash"># /etc/letsencrypt/renewal-hooks/deploy/99-sync-nginx-matto.sh
#!/usr/bin/env bash
set -euo pipefail

if [ &quot;${RENEWED_LINEAGE:-}&quot; = &quot;/etc/letsencrypt/live/matto.top-shared&quot; ]; then
  install -m 600 &quot;$RENEWED_LINEAGE/privkey.pem&quot; /etc/nginx/ssl/api.matto.top_P256/private.key
  install -m 644 &quot;$RENEWED_LINEAGE/fullchain.pem&quot; /etc/nginx/ssl/api.matto.top_P256/fullchain.cer
  install -m 600 &quot;$RENEWED_LINEAGE/privkey.pem&quot; /etc/nginx/ssl/qing.matto.top_P256/private.key
  install -m 644 &quot;$RENEWED_LINEAGE/fullchain.pem&quot; /etc/nginx/ssl/qing.matto.top_P256/fullchain.cer
  nginx -t
  systemctl reload nginx
fi</code></pre><p>接着验证三件事：</p><ol start="1"><li><code>certbot</code> 里已经有证书对象</li><li>线上域名拿到的是新证书</li><li><code>certbot.timer</code> 是 <code>enabled + active</code></li></ol><p>最后打开matto.top，发现通了
大功告成~</p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/backend/certbot%E7%BB%AD%E6%9C%9F%E5%AE%9E%E5%BD%95#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>写在博客第四次复活之时</title>
            <link href='https://www.matto.top/notes/10'/>
            <id>https://www.matto.top/notes/10</id>
            <published>2026-02-11T08:22:12.658Z</published>
            <updated>null</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/notes/10'>https://www.matto.top/notes/10</a></blockquote>
<div><div><p>不知不觉玩博客也已经四年了
最开始用的时候还是hexo主题，当时看到了一个很是漂亮的二次元主题，以及一个看着很完善的bilibili教学视频（<a href="https://www.bilibili.com/video/BV1cp4y1i7C7/?spm_id_from=333.999.0.0）">https://www.bilibili.com/video/BV1cp4y1i7C7/?spm_id_from=333.999.0.0）</a>
又恰好手上有着一台1核2G的阿里云服务器空闲着，于是有了最初的blog
而现如今，早就从hexo迁到了vuepress又到了现在的mx-space
手上的服务器从阿里云到腾讯云再到现在的野草云
大一就购买的87r/年的阿里云服务器，在用了四年后也在去年的一堆续费邮件中永远关闭了</p><p>最开始喜欢博客什么呢，也许是鲜艳抓眼的样式布局，也许是技术大牛都有这么个东西的跟风，也许是可以自诩数字游民的骄傲
这一次折腾，想了想最大的兴趣是留个清闲的地方，记录自己的一些想法</p><p>最近一年在字节遇到了很多厉害的人，结识了一些自带气场的大能人
自己的心境与能力也跟着发生了很多变化
“那么多有能力，在各个行业都能大放光彩的人，此时却和你一起做产品基础的事情，只为了把产品打磨好”
希望自己也能在成长与思考这件事做好，输出更优质的博客内容。</p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/notes/10#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>AI Coding的过去、现在和未来</title>
            <link href='https://www.matto.top/posts/AI/AI%20Coding%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5'/>
            <id>https://www.matto.top/posts/AI/AI%20Coding%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5</id>
            <published>2026-02-04T03:03:00.000Z</published>
            <updated>2026-02-11T07:17:42.150Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/AI/AI%20Coding%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5'>https://www.matto.top/posts/AI/AI%20Coding%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5</a></blockquote>
<div><div><p>是在组内技术分享的演讲稿，也是本人四年AI Coding的一些感受与思考
最近一年的技术专项都围绕着AI Coding展开，也是边感慨边写下了下面的文字。</p><p><strong>背景</strong></p><p>还记得是2022年的夏天，还是大一的我在苦学《C语言程序设计》，舍友突然冒过来说Github出了个贼牛逼的插件叫Copilot，学生认证之后，只需要输入中文描述我们要实现的功能，然后一路Tab，AI就能像变魔法一样帮我们把代码都写了，别老是惦记钻研C语言里的指针指向啥了！</p><p><img src="https://s3.bmp.ovh/2026/02/10/4WeZ7Wmu.gif"/></p><p>Copilot自动补全代码</p><p>当时GPT-3才刚刚发布，AI的编程能力其实并不强，只能做一些很简单的补全操作，但即便如此，“魔法一样的Copilot”仍然在23年中被超过 100 万开发者激活。</p><p>而四年后的今天，AI Coding早就不仅仅时候ide中的代码补全：通过对话式编辑、终端启动应用输指令、Web异步任务派发，前俩天翛然打通Lovelace的内网穿透，在手机上也可以进行编程。任务的大小也从最开始的<strong>code片段</strong>到<strong>文件编写</strong>再到现在的<strong>系统开发</strong>，AI代码生成已经逐渐成为软件工程领域的一个新的方向，影响着工业界、学术界甚至是完全不懂代码的爱好者们。</p><p>微软于2020年2月发布的BERTCode被认为是第一个基于Transformer架构的代码生成模型，而在2021年8月发布的基于GPT3微调的CodeX，成为AI代码生成领域的另一个里程碑，该模型于同年八月被应用于Github Copilot产品。2022年末，随着GPT3.5发布，基于通用大语言模型+Agent的代码生成产品开始与针对代码生成任务的专用模型并行发展，各家的AI Coding项目也如雨后春笋般涌现。</p><table><tbody><tr><td><p>产品</p></td><td><p>文档</p></td><td><p>公司</p></td><td><p>发布日期</p></td><td><p>备注</p></td></tr><tr><td><p><img src="https://s3.bmp.ovh/2026/02/10/1AkINWt8.png"/></p></td><td><p><a href="https://github.com/features/copilot">https://github.com/features/copilot</a></p></td><td><p>Github</p></td><td><p>2021.10.27</p></td><td><p>IDE插件，主要聚焦于AI代码补全</p></td></tr><tr><td><p><img src="https://s3.bmp.ovh/2026/02/10/7A2GdTH2.png"/></p></td><td><p><a href="https://cursor.com/cn/changelog/0-0-37">https://cursor.com/cn/changelog/0-0-37</a></p></td><td><p>Anysphere</p></td><td><p>2023.3.14</p></td><td><p>早期可用版本，IDE工具，支持AI补全、编辑，但不支持对话操作</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191628_115.png"/></p></td><td><p><a href="https://developer.android.com/studio/gemini/overview?hl=zh-cn">https://developer.android.com/studio/gemini/overview?hl=zh-cn</a></p></td><td><p>Google</p></td><td><p>2023.05.10</p></td><td><p>Android Studio 内置对话式编码助手：回答问题/生成代码/修错，强绑定 Android 开发栈。</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191628_300.png"/></p></td><td><p><a href="https://www.jetbrains.com/ai/#plans-and-pricing">https://www.jetbrains.com/ai/#plans-and-pricing</a></p></td><td><p>JetBrains</p></td><td><p>2023.6.26</p></td><td rowspan="3"><p>各家厂商的翻版Copilot，提供代码补全、单元自测编写等功能</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191628_407.png"/></p></td><td><p><a href="https://comate.baidu.com/zh">https://comate.baidu.com/zh</a></p></td><td><p>百度</p></td><td><p>2023.10.24</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191628_738.png"/></p></td><td><p><a href="https://tongyi.aliyun.com/lingma">https://tongyi.aliyun.com/lingma</a></p></td><td><p>阿里巴巴</p></td><td><p>2023.10.31</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191629_653.png"/></p></td><td><p><a href="https://windsurf.com/blog/codeium-live?utm_source=chatgpt.com">https://windsurf.com/blog/codeium-live?utm_source=chatgpt.com</a></p></td><td><p>GitLab</p></td><td><p>2023.11.15</p></td><td><p><p>在浏览器中通过“聊天 + 外部知识更新”的使用AI进行编程。</p></p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191629_625.png"/></p></td><td><p><a href="https://www.cognition.ai/introducing-devin">https://www.cognition.ai/introducing-devin</a></p></td><td><p>Cognition</p></td><td><p>2024.3.12</p></td><td><p>端到端AI代码生成能力，覆盖软件生命周全流程</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_397.png"/></p></td><td><p><a href="https://github.blog/2024-04-29-github-copilot-workspace/">https://github.blog/2024-04-29-github-copilot-workspace/</a></p></td><td><p>Github</p></td><td><p>2024.4.29</p></td><td><p>深度集成Github，具备代码生成与Debug能力</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_724.png"/></p></td><td><p><a href="https://www.marscode.cn/home">https://www.marscode.cn/home</a></p></td><td><p>字节跳动</p></td><td><p>2024.6.27</p></td><td><p>偏重于IDE插件，提供Cloud IDE环境，尚不具备端到端代码生成能力</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_671.png"/></p></td><td><p><a href="https://cline.bot/">https://cline.bot/</a></p></td><td><p>Saoud Rizwan</p></td><td><p>2024.10.9</p></td><td><p>在2.0版本由Claude Dev改名而来，支持读写文件、创建项目、执行终端命令的对话式插件。</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_353.png"/></p></td><td><p><a href="https://zed.dev/">https://zed.dev/</a></p></td><td><p>Zed Industries</p></td><td><p>2024.8.20</p></td><td><p>在 Zed 编辑器里集成 AI，支持AI对话和编辑</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_387.png"/></p></td><td><p><a href="https://bolt.new/">https://bolt.new/</a></p></td><td><p>StackBlitz</p></td><td><p>2024.10.22</p></td><td><p>在网页中提供AI 编程环境，根据用户idea快速生成可运行全栈应用</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_889.png"/></p></td><td><p><a href="https://windsurf.com/">https://windsurf.com/</a></p></td><td><p>GitLab</p></td><td><p>2024.11.13</p></td><td><p><strong>独立 Agentic IDE</strong><p>：把对话/任务执行（Cascade）和编辑器深度融合</p></p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_566.png"/></p></td><td><p><a href="https://aipa.bytedance.net/">https://aipa.bytedance.net/</a></p></td><td><p>字节跳动</p></td><td><p>2024.11.20</p></td><td><p>类似于bolt， AI 驱动的轻应用生成平台，在网页中迅速实现Idea</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_383.png"/></p></td><td><p><a href="https://cursor.com/cn/changelog/0-44-x?spm=a2c6h.13046898.publish-article.26.78d16ffael4TBS&amp;amp;utm_source=chatgpt.com">https://cursor.com/cn/changelog/0-44-x?spm=a2c6h.13046898.publish-article.26.78d16ffael4TBS&amp;amp;utm_source=chatgpt.com</a></p></td><td><p>Anysphere</p></td><td><p>2024.12.17</p></td><td><p>引入 <strong>Yolo Mode</strong>（允许 agent 自动运行终端命令），支持Agent 能看终端内容、在后台跑命令</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_808.png"/></p></td><td><p><a href="https://lingma.aliyun.com/">https://lingma.aliyun.com/</a></p></td><td><p>阿里巴巴</p></td><td><p>2024.12</p></td><td><p><p>由补全/问答升级到 <strong>多文件修改 + 工具使用的端到端任务</strong>（AI 程序员）</p></p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_959.png"/></p></td><td><p><a href="https://www.trae.ai">https://www.trae.ai</a></p></td><td><p>字节跳动</p></td><td><p>2025.1</p></td><td><p>Claude 的 <strong>终端/IDE 里的 Agentic coding</strong>（可运行命令、改文件、做任务）</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_300.png"/></p></td><td><p><a href="https://aime.bytedance.net/">https://aime.bytedance.net/</a></p></td><td><p>字节跳动</p></td><td><p>2025.1.20</p></td><td><p>异步办公助手，后续迭代升级后，拓展AI Coidng能力，拥有搭建环境、编辑代码、预览网页的功能</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_670.png"/></p></td><td><p><a href="https://blog.jetbrains.com/junie/">https://blog.jetbrains.com/junie/</a></p></td><td><p>JetBrains</p></td><td><p>2025.1.23</p></td><td><p>JetBrains内置的coding agent：可委派任务、执行命令、产出可审查的代码变更。</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_322.png"/></p></td><td><p><a href="https://manus.im">https://manus.im</a></p></td><td><p>Monica</p></td><td><p>2025.3.6</p></td><td><p>能够自主完成复杂任务的AI助手，与Aime类似</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191630_81.png"/></p></td><td><p><a href="https://code.claude.com/">https://code.claude.com/</a></p></td><td><p>Anthropic</p></td><td><p>2025.5.22</p></td><td><p>伴随claude 4一起被公布，支持在终端里当“coding agent”</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191631_879.png"/></p></td><td><p><a href="https://lingma.aliyun.com/">https://lingma.aliyun.com/</a></p></td><td><p>阿里巴巴</p></td><td><p>2025.5.30</p></td><td><p>2.0版本升级为AI 原生 IDE：集成智能体、行间建议、工程感知、工具调用等。</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191631_573.png"/></p></td><td><p><a href="https://cursor.com/cn/changelog/page/4">https://cursor.com/cn/changelog/page/4</a></p></td><td><p>Anysphere</p></td><td><p>2025.6.4</p></td><td><p>引入 <strong>Bugbot</strong>，支持<strong>Background Agent</strong>（远程/后台 agent）走向更大范围可用影响力外溢到github社区，<strong>优化 MCP 一键安装等</strong></p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191631_430.png"/></p></td><td><p><a href="https://www.trae.ai/solo">https://www.trae.ai/solo</a></p></td><td><p>字节跳动</p></td><td><p>2025.7.18</p></td><td><p>把“AI 助手”升级成一个<strong>以任务为中心的 Coding Agent 工作台</strong>：打通终端、编辑器、文档、浏览器、Figma等上下文，由plan到edit完成目标。</p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191631_599.png"/></p></td><td><p><a href="https://www.codebuddy.ai/">https://www.codebuddy.ai/</a></p></td><td><p>腾讯</p></td><td><p>2025.9.9</p></td><td><p><p>腾讯版独立 AI IDE，集成了插件、IDE、CLI三端，类似于Trae + coco</p></p></td></tr><tr><td><p><img src="https://lf0-fast-deliver-inner.bytedance.net/obj/eden-internal/lmgoeh7bfupevhpe/lark-analysis/image_1770632191631_652.png"/></p></td><td><p><a href="https://bytedance.larkoffice.com/wiki/Er28whaTUiRgMekHLvDcISWmnXc">TRAE CLI (Coco CLI) 使用文档</a></p></td><td><p>字节跳动</p></td><td><p>2025.9.9</p></td><td><p>对标 Claude Code / OpenAI Codex / Gemini CLI，在终端中完成任务</p></td></tr></tbody></table><p>随着基础模型能力持续增强，AI Coding 的业界焦点正在从“写代码”走向“做软件”。早期以代码补全为代表的能力，主要解决的是<strong>编码阶段的局部效率问题</strong>；而当模型在自然语言理解、长上下文、工具调用与规划能力上显著提升后，AI 生成的产物不再局限于代码本身，而是逐渐覆盖需求澄清、方案设计、代码注释、单元测试、缺陷定位与修复、代码评审建议、变更说明乃至 PR/Issue 的闭环交付等多类软件工程产物，业界产物的覆盖点也从 LLM4Code扩展为了 <a href="https://conf.researchr.org/home/staf-2025/llm4se-2025"><strong>LLM4SE</strong></a><strong>（Large Language Model for Software Engineering）</strong>。</p><p>从产品形态看，最早流行的是 IDE 插件式助手：如 GitHub Copilot、JetBrains AI Assistant 等，它们以“预测下一个 token/下一段代码”为核心，能力主要覆盖编码阶段。随后，随着对话式交互与代码理解能力增强，这类工具逐步叠加了代码解释、仓库问答、重构建议、单测生成、缺陷修复与局部优化等能力，并开始尝试把工作流向“评审—测试—提交”扩展。进一步地，以 Cursor 这类 <strong>Agentic IDE</strong> 为代表的产品开始把“实现—提交—PR”做成一条更连续的链路：它的 Cloud Agent 能直接从 GitHub 克隆仓库、在独立分支上修改并推送代码，方便交接与评审；结合其 GitHub 集成，还可以围绕 issue/PR 触发 agent 工作流，自动把修改结果沉淀为可审阅的 PR。</p><p>在2022到2024的两年中，这些能力更多以“散点插件能力”的形式存在：它们可以分别完成补全、解释、生成测试、修复 bug，却缺少把需求理解、实现路径、验证方式等串成闭环的自主规划与决策能力。于是近一年（2025）开始出现两类更“端到端”的路线：</p><ul><li><strong>“虚拟程序员 / 工作室”式端到端交付</strong>：以 agent 智能体为核心，把“理解需求—分解任务—检索项目与知识—生成/修改代码—运行测试”组织成完整链路，目标是让用户用一句话描述意图后，系统像真实程序员一样分析项目、执行计划并交付可用软件。如Bolt、AIPA，融合网页编辑器快速搭建开发环境，交付代码给用户。同时也有Aime、Manus这类全能AI智能体，将编程作为AI能力的延伸，面向非开发者完成代码开发。</li></ul><p><img src="https://s3.bmp.ovh/2026/02/10/pAlvmwOo.png"/></p><p>在Manus上用一段idea生成炫酷的前端界面</p><ul><li><strong>Agentic IDE / Cli一体化</strong>：把补全、对话、检索、重构、运行/调试、生成测试与多文件修改统一在同一编辑器内，由“代理”持续维护任务目标与上下文，尽量减少开发者在工具间切换。典型如 Cursor 与 TRAE，强调在编辑器内由 agent 持续推进实现、修复与迭代，也有Claude Code、coco，利用终端的能力完成编码任务，在编辑器中显现代码diff便于采纳。</li></ul><p>与此同时，生态层也在不断变化：端到端 AI Coding 的可组合化正沿着一条“先工具、再上下文、再流程、最后编辑器”的路径落地，而且每一层背后都有明确的推动者。Anthropic 提出<strong>MCP</strong>并开源，用统一接口把部署、日志、工单、数据库等外部系统能力暴露给 agent，解决“agent 能做什么”的问题；OpenAI 在 Codex 体系中提出<strong>skills</strong> 概念，把修 bug、补测试、发布等任务固化为可复用的“流程 + 资源 + 脚本”包，方便团队共享并支持多代理分工；Zed 提出 <strong>ACP</strong>开放协议，类似于LSP协议，旨在标准化 code editors 与 coding agents 的通信，支持越来越多的入口调用code editors的功能，让程序员可以无时无刻地指挥AI干活。</p><p><strong>过去：将 AI 塞进开发者的“输入法”</strong></p><p>在 Copilot 进入主流视野之前，AI Coding其实已经有了一种古朴的使用方法：chatgpt.com网页上输入自己的问题，通过对话式解决遇到的报错、语法细节、工程配置等问题。这种用法其实是在把AI当作一种新的<strong>知识检索与解释引擎</strong>，就像过去去搜Stack Overflow/Reddit/CSDN 一样，把问题描述成自然语言，拿到一段“看起来合理”的回答，再据此完成自己的代码编写。</p><p>而Copilot的出现，让开发者不必离开当前文件，不必把需求改写成一段长问题，也不必复制粘贴示例代码——只要继续写，AI 就会在你敲击键盘的间隙给出下一段建议。</p><p>这种产品形态的核心，是把 AI 从“讲解者”升级成“续写者”。在交互上，它不要求你先提出一个完整问题，而是默认你正在进行某个任务，并用一种极轻量的方式参与其中：<strong>幽灵文本（ghost text）+ 一键接受（Tab）/一键拒绝（Esc）</strong>。从体验上看，Copilot 更像一个“会读上下文的超级自动补全”，让开发者进入一种连续的“Tab-flow”：写注释、写函数签名、写几行骨架，然后不断用接受/拒绝把它拉到正确轨道上。</p><p><strong>怎么做到“像读心”一样补全的？</strong></p><p>从原理上看，早期代码补全插件并不需要“理解整个仓库”，它更像一种工程化的 prompt 组装器，省去你拷贝上下文到网页的成本，快速帮你生成目标代码</p><ul><li><strong>输入源</strong>：当前文件、光标附近的代码片段、你刚写的注释/变量名/函数签名，以及少量工程侧信息（语言、依赖、常见框架习惯等）。</li><li><strong>处理方式</strong>：把这些“局部线索”整理成一段 prompt，交给代码模型去做“下一段最可能出现什么”的预测（本质是 next token / next chunk 生成）。</li><li><strong>输出形态</strong>：生成候选代码，以不破坏你编辑节奏的方式呈现（内联 ghost text 或补全面板），由你决定是否采纳。</li></ul><p>而整个处理流程会涉及代理服务器和模型的交互，整个流程如下图所示</p><p><img src="https://s3.bmp.ovh/2026/02/10/wzaURsIr.jpg"/></p><p>在最关键的Propmt构建中，Copilot采用了<strong>FIM</strong>（Fill-in-the-Middle）模式，传统的模型只会“续写”，而 FIM 模式可以提供<strong>光标前（Prefix）</strong>和<strong>光标后（Suffix）</strong>的代码，同时也会去收集相似代码片段和打开的其它文件信息，一个典型FIM结构的prompt的结构如下：</p><table><tbody><tr><td><p>JSON<br/>{<br/>&quot;prefix&quot;: &quot;# Path: codeviz\app.py\n# Compare this snippet from codeviz\predictions.py:\n# import json\n# ... (省略了另一个文件中的大量代码)\n\nimport json\nfrom flask import Flask, render<em>template, request, send<em>from_directory\n# ... (省略了当前文件光标前的代码)\n\n@app.route(&#x27;/api/predict_module_name&#x27;, methods=[&#x27;POST&#x27;])\ndef suggest_module_name():\n module_id = request.json[&#x27;module_id&#x27;]\n module_name = predict_module_name(module_id)\n&quot;, // 包含了当前文件的路径、从其他相似文件中提取的片段，以及光标前的所有代码。<br/>&quot;suffix&quot;: &quot;if __name</em></em> == &#x27;<strong>main</strong>&#x27;:\r\n app.run(debug=True)&quot;, // 包含了光标后的代码<br/>&quot;isFimEnabled&quot;: true,<br/>&quot;promptElementRanges&quot;: [ //元数据数组，标注了 prefix 中每一段内容来自哪里（路径、相似文件、还是光标前代码），用于调试和分析<br/>{ &quot;kind&quot;: &quot;PathMarker&quot;, &quot;start&quot;: 0, &quot;end&quot;: 23 },<br/>{ &quot;kind&quot;: &quot;SimilarFile&quot;, &quot;start&quot;: 23, &quot;end&quot;: 2219 },<br/>{ &quot;kind&quot;: &quot;BeforeCursor&quot;, &quot;start&quot;: 2219, &quot;end&quot;: 3142 }<br/>]<br/>}</p></td></tr></tbody></table><p>在 Prompt构建过程中，Copilot 设计了“许愿池”机制（Prompt Wishlist）。它会首先列出一个包含各种可能上下文元素的“愿望清单”，然后根据<strong>优先级</strong>和<strong>剩余的 Token 预算</strong>，贪心地从清单中挑选元素，拼装成最终的 Prompt。Copilot 会像集邮一样，搜集所有可用的上下文信息，形成一个初始的 Wishlist，然后根据预设的<strong>优先级</strong>和<strong>插入顺序</strong>进行排序这是一个动态的过程，受到大量 A/B 实验开关的控制。例如：</p><ul><li>suffixPercent：大约 <strong>15%</strong> 的 Token 预算会专门留给 suffix，这体现了对 FIM 模式的重视。</li><li>fimSuffixLengthThreshold: 如果 suffix 太短，可能就不会启用 FIM 模式。</li><li>SuffixStartMode: 决定 suffix 从哪里开始截取，有时会从当前函数的下一个兄弟函数开始，以提供更完整的上下文。</li><li>NeighboringTabsOption: 控制从其他文件中提取代码片段的“激进”程度。</li></ul><p>最后，Copilot 会像整理行囊一样，按照排序后的列表，逐一将元素塞进 Prompt，直到 Token 预算耗尽。</p><p><img src="https://s3.bmp.ovh/2026/02/10/paGfsn5H.jpg"/></p><p><strong>怎么就迅速流行了？</strong></p><table><tbody><tr><td><p>Copilot 的破圈不在于它一开始就能写对复杂系统，而在于它让开发者第一次体感到：<strong>把“机械输入”外包给模型以后，人开始从键盘打字员变成了代码编辑者与审稿人。</strong></p></td></tr></tbody></table><p>除去Github本身的宣发优势，在社区中能得到广泛认可是因为它解决了一个非常朴素的问题：<strong>人类在写代码时，大量时间花在“把已知结构打出来”</strong>。早期 Copilot 最强的价值点，集中在以下几类“局部效率”：</p><ul><li><strong>样板代码与惯用法的加速</strong>：CRUD、参数校验、错误处理骨架、重复性重构、常见算法套路……这些东西“创造性”很低，但手打很累。</li><li><strong>从注释/函数签名到第一版实现</strong>：你把输入输出、边界条件写清楚，Copilot 往往能给出一个“可运行的雏形”，把你从空白页推进到可迭代状态。</li><li><strong>在“卡壳点”给一个方向</strong>：哪怕它不完全正确，它也能提供一条可修改的路径，让开发者从“想不出来怎么写”变成“我知道该改哪里”。</li></ul><p>而Copilot将这几个能力，把“AI”塞进了开发者原本就要按 Tab 的地方，<strong>让开发者在原来的工作流里多一个候选</strong>：IDE 里出现 ghost text，按 Tab 接受、按 Esc 忽略——几乎零学习成本。它不像一个需要你“切过去说话”的工具，更像升级版 autocomplete，所以更容易形成习惯与依赖。</p><p>除次优秀的使用体验外，它的流行还来自于一种被社区反复提到的“<strong>学习增益</strong>”——它把很多原本需要查文档的片段，变成了一个<strong>可编辑的草稿</strong>，让学习者用“对照—运行—改错”的方式，更快跨过陌生语法与生态的启动门槛。这个现象在 <a href="https://survey.stackoverflow.co/2023/?utm_source=chatgpt.com#ai">Stack Overflow</a> 和Copilot的调查中也能看到：<strong>正在学习编程的人群，比职业开发者更倾向于使用/计划使用 AI 工具</strong>。</p><p><img src="https://s3.bmp.ovh/2026/02/10/3ILIDEHd.png"/></p><p>github于2023统计的代码经验/AI工具接受度图</p><p>在 <strong>Stack Overflow 2023 开发者调查</strong>中，GitHub Copilot 在 “AI developer tools / AI tools” 相关统计里占有了 <strong>55%</strong> 的使用比例，显著高于第二名，出现了“超过 100 万开发者激活”的标志性数字。</p><p><strong>代码补全的天花板：只会“写”而不能“做”</strong></p><p>早期 Copilot 代表的补全工具，本质上是把 AI 用作“编码阶段的局部加速器”：它让代码在输入法意义上更容易生成，却难以替代软件工程真正难的部分——<strong>对齐约束、覆盖边界、验证闭环与协作交付</strong>。</p><ul><li><strong>上下文短、视野窄</strong>：它看见的往往是局部片段，只能看见当前文件、附近函数、少量相邻代码，以及少数被检索到的参考片段，而系统正确性恰恰来自“全局约束”的一致对齐，导致在越复杂的系统中采纳率越低。</li><li><strong>不会替你验证闭环</strong>：补全插件解决的是“输入”问题，而软件工程的硬骨头是“验证”与“协作闭环”。补全工具通常不具备持续推进任务的能力，它不会自动跑测试，也不会根据失败反复迭代修复，更不会完成协作链路。当项目越来越大、规则越多的时候，一个连code lint都无法运行的工具，当然写不出可靠的代码。</li><li><strong>缺少工具链整合：</strong>成熟项目里，一个需求往往牵涉：多文件改动、文档补充、PR 描述、review 反馈、版本发布。而代码补全只能用于“当前文件、当前函数”的生成，无法把任务拆解为一组跨文件、可验证、可合并的变更集合。</li></ul><p>随着AI模型能力越来越强，行业对AI的期待也越来越高，IDE中的局部补全已经没法再被用户当成“AI的魔法”，关注点也从<strong>“写得快”转向“交付对</strong>”，AI Coding 的下一步也就不可避免地从补全走向对话，让模型不仅续写代码，还要能理解上下文、维护目标、调用工具并推动一次真正可审阅的变更落地。</p><p><strong>现在：用AI落地自己的IDEA</strong></p><p>2024年6月，Anthropic发布了他们的<strong>Claude 3.5 Sonnet</strong> 模型，相比于8k上下文的gpt4，提供了<strong>200K</strong>上下文还有超强的问题解决能力，模型开始可以把“多个关键文件 + 报错信息 + 测试用例 + 接口约定”放进同一次推理里，不必频繁切片，开发者一边感慨着新模型的强大，一边不再满足于让 AI 只当一个“超级补全”，开始围绕着AI Coding 的“现在”提出了一个更宏大的命题展开：<strong>如何让 AI Coding从“简单续写”走向“想法落地”？</strong></p><p><img src="https://s3.bmp.ovh/2026/02/10/kRTCNIYP.png"/></p><p>claude 3.5 sonet benchmark score</p><p><strong>AI IDE：从“超级补全”到“可行动的协作者”</strong></p><p>AI IDE不颠覆开发者熟悉的编辑器形态，而是将 Agentic Workflow（代理工作流）深度融合在 IDE 的每一个角落，让 AI 从一个“对话框里的助手”变成无处不在的“合作伙伴”。同时由于有了更全的代码仓库信息，在辅助编程上也有了更强的能力。</p><p><strong>Codebase Indexing：把整仓库变成可检索的上下文</strong></p><p>Cursor 的一个基础设定是：<strong>先把代码库做索引/embedding</strong>，再让 Agent 基于索引召回相关文件与片段。它在官方特性页里直接把这件事称为“codebase understanding / codebase embedding model”，从而为大型代码库提供快速、精准的语义搜索能力，将上下文拓展至整个仓库而不仅仅是光标前后。</p><p><img src="https://s3.bmp.ovh/2026/02/10/rEjHurjX.jpg"/></p><ul><li><strong>本地数据构建：</strong>在<strong>客户端本地</strong>，系统首次会扫描整个工作区，为代码库构建一棵基于内容哈希的 <strong>Merkle tree</strong>。在后续的周期性检查中，系统只需对比 Merkle tree 的哈希值，就能快速定位发生变更的文件，形成一个<strong>增量变更集</strong>。</li><li><strong>服务器存储：</strong>变更集上传至<strong>服务端</strong>后，文件被切分成小的代码片段（chunks），并为每个片段计算 <strong>embedding</strong>。这些向量最终被写入如 Turbopuffer 等专门的向量数据库中。值得注意的是，服务端持久化的并非代码原文，而是向量本身，以及一组用于定位的元数据，包括混淆处理过的文件引用（<strong>path_ref</strong>）、代码在文件中的具体行号范围（<strong>range</strong>）和用于去重的片段哈希（chunk_hash）。这一策略主要用于保证用户数据的隐私性。</li></ul><p><img src="https://s3.bmp.ovh/2026/02/10/o4Vh4JQs.png"/></p><p>使用Merkle tree快速检测代码变更</p><ul><li><strong>检索优化：</strong>当用户发起<strong>查询</strong>时，查询语句同样会被转换为 query embedding。系统利用此 embedding 在向量库中进行高效的近邻检索，找出最相关的 Top-K 个代码片段“指针”（即 <strong>path_ref</strong> 和 <strong>range</strong>）。这些“指针”被返回给客户端，客户端根据它们在本地工作区中精确提取出对应的代码原文。最后，这些代码片段作为上下文被回传给大语言模型，辅助其完成代码生成或问答任务。</li></ul><p>而上下文拓展到整个仓库后，相比于Copilot，带来的优势有</p><ul><li>AI 能按“语义相关性”找到真正应该改的文件，而不是只依赖你当前打开的几页代码</li><li>跨文件改动时，更容易保持一致性（同类逻辑复用、命名风格、错误处理规范）</li></ul><p><strong>Multi-file Edits / Apply Workflow：交付“可应用的变更集”</strong></p><p>在扩大了AI Coding的能力范围后，一个重要的问题也摆在面前：“用户怎么去响应AI生成的代码？”</p><p>AI ide给出的答案是把一次对话的输出固化为<strong>可审阅、可分块应用、可回滚</strong>的改动集合。它将输出从“生成文本”升级为“可审阅、可局部应用、可回滚”的改动集合，应用在ide中的结果，是更贴合真实开发中的 <strong>PR</strong> ，将accpet和reject的能力交还给开发者们。</p><p>客户端基于 diff 中的“指针/上下文”读取本地文件并执行 patch 应用；在 Agent 模式下，系统还会通过工具循环（读写文件、运行命令、基于报错再迭代）把变更推进到可验证状态。</p><p>而针对“改几百行要调用很多次、容易循环卡住、速度慢”的问题，像Cursor 还专门训练过一个用于“全文件快速应用编辑”的模型 <strong>Llama-3-70b-ft-spec</strong>，相比同期的GPT 4o模型在大量代码修改时，拥有更强的ide编辑能力和响应速度，可以一次完成整个文件的编写，不必靠多轮模型调用拼出来。</p><p><img src="https://s3.bmp.ovh/2026/02/10/AMbaSr8X.png"/></p><p>Llama-3在推测性编辑时的响应速度</p><p><strong>工作流：IDE 内的“一条龙”服务</strong></p><p>除了代码编辑场景外，AI IDE还就开发链路尽可能地补全AI接入，让每个环节都可以低成本地使用AI提效。</p><ol start="1"><li><strong>Lint 自动 fix：编码错误的“自动收尾”</strong></li></ol><p>在真实开发里，很多时间并不是花在核心逻辑上，而是花在“让它能过规则”：unused、import 顺序、格式化、某些类型/风格约束。而现有的AI IDE把这一类问题从“你写完之后再手动清理”变成“变更集落地后的自动收敛步骤”。实现上先获取 lint/formatter 的诊断（来自工具或编辑器问题面板），再把这些诊断转化为可应用的 patch，最后按文件回填并 Apply，直到告警消失或收敛到可接受的状态。</p><p>这一点在跨文件改动里尤其重要。Multi-file Apply 往往会带来大量小而分散的改动点：某处新增了导入，另一处出现了未使用变量，第三处触发了风格规则。让 AI 在最后统一“收尾”，利用ide原有的能力提高编码质量和可靠性。</p><ol start="1"><li><strong>集成终端：编辑器能力的延伸</strong></li></ol><p>AI IDE把终端变成 Agent 可用的工具之一：让AI的能力从“编辑器内的文本生成”扩展为“对工程工具链的可执行控制”，让 Agent 能直接做“配置落地”的动作，而不是停留在“建议你这样设置”。让开发者不需要频繁离开对话去手动跑命令、贴输出、再回到 IDE 解释一遍。</p><ol start="1"><li><strong>git代码流集成</strong></li></ol><p>git管理先前就是IDE插件中最“卷”的赛道之一，而AI IDE融入其中后，可以基于你已经 staged 的 diff 来生成 commit message，在commit的时候自动触发review hooks，对变更内容进行review，省去功能概括耗费时间的同时提高代码质量。</p><p>这一整能力被Aime/Manus/AIPA广泛实现，在补充了网页预览能力和vercel等部署功能，吸引大量非研发者从0到1完成想法落地，而这一段时间新的AI工具也大量出现，以“端到端”编程为卖点接连上线。</p><p>把这三块能力连起来看——Codebase Indexing 解决“找对上下文”，Multi-file Apply 解决“把改动交付成可审阅的变更集”，再叠加 IDE 内的一条龙工作流（lint 自动收尾、终端可执行、git 流与 review/commit 绑定、预览与部署反馈回流）——AI IDE 实际上完成了一次范式迁移：它不再只是在光标附近帮我写几行代码，而是把 AI 变成能推进工程状态的“实习生”。</p><p>cursor和trae也因此在商业上取得了巨大的成功，Cursor在2025年年化收入超过<strong>10亿美元</strong>，并服务 <strong>数百万开发者</strong>，同时也在去年年底被估值为<strong>293亿美元；</strong>Trae在上线五个月用户数就达到了100万，相比于copilot快了整整一年。</p><p><strong>Claude Code：终端里的“可行动协作者”</strong></p><p>如果说 Cursor 是把 AI 无缝嵌入图形化界面的典范，那么 Claude Code 则选择了另一条更“复古”也更极客的道路：让 AI 成为终端（CLI）里的原生公民。它不试图重新发明一个编辑器，而是作为一个强大的命令行工具存在，可以与任何 IDE、任何工作流组合。</p><p>它的设计哲学更接近 Unix 世界的“可组合性”：把自己变成一个能接入现有工具链的“行动体”，与 shell、git、测试框架、CI 脚本天然咬合，而不是把开发者锁进某个专用 GUI 的工作方式里。</p><p><strong>Claude Code 为什么“能做完事”</strong></p><p>如果把 Claude Code 的系统拆开看，它其实非常像一个经典的“本地执行 + 远端推理”的 agent 架构：</p><ol start="1"><li><strong>本地执行层（Claude Code 客户端）</strong></li></ol><p>它运行在你的机器上，掌管三件事：</p><ul><li><strong>工具调用</strong>：读文件、改文件、搜索、跑命令、跑测试、做 git 操作；</li><li><strong>状态与回传</strong>：把命令输出、diff、关键文件片段回传给模型；</li><li><p><strong>循环控制</strong>：把任务推进到“可交付”的终点，而不是停在“建议”。</p></li><li><p><strong>远端智能层（Claude 模型：规划与决策的“大脑”）</strong></p></li></ul><p>智能层的架构可以概括为“一个大循环”，由最简单的 大模型-工具 调用循环构成，AI去分析用户输入，如果问题足够简单，主循环只需通过迭代调用工具即可处理；针对复杂任务会调用Task工具，主智能体创建自身的克隆，将问题分解为子问题，同时保证最终的预期结果。</p><p><img src="https://s3.bmp.ovh/2026/02/10/QMZiPh5g.gif"/></p><p>而Claude原生集成了若干工具，包括bash、文件读写、规划模式等等，最重要的Task工具是一个muti agent系统，架构如下：</p><p><img src="https://s3.bmp.ovh/2026/02/10/lhEg6l3P.png"/></p><p>出自<a href="http://anthropic.com/engineering/built-multi-agent-research-system">http://anthropic.com/engineering/built-multi-agent-research-system</a></p><ul><li><strong>LeadResearcher</strong>先判断问题复杂度，再把任务拆分成多个子任务，分派给不同 subagents 并行去搜集信息；当信息足够时退出 research loop。</li><li><strong>Subagents（执行者）</strong>：各自带着更明确的目标、输出格式和工具使用边界去检索与归纳；在每轮工具调用后会做一次反思/校验，决定下一步怎么搜。</li><li><strong>CitationAgent（引用代理）</strong>：在 research loop 结束后接管，把“研究报告 + 已收集文档”对齐，找出每个关键断言对应的<strong>具体引用位置</strong>，保证所有结论都能被来源支撑。</li></ul><p>而在具体落地的过程中，多智能体相对单智能体<strong>协调复杂度也显著增长，</strong>早期失败案例包括“为了一个简单问题生成 50 个 subagents”、“subagents 相互干扰、不断更新，反而分散注意力”，最后围绕提示词工程（prompt engineering）进行改进和优化。</p><ol start="1"><li><strong>扩展与平台层</strong></li></ol><p>当项目进入真实企业流程，交付往往离不开外部系统（工单、文档、知识库、发布系统、监控等）。Claude Code 强调通过 <strong>MCP（Model Context Protocol）</strong> 接入外部工具，把“编码”扩展为“端到端交付”，让 Claude Code 不止能改 repo，还能理解“repo 之外”的上下文与流程约束。</p><p><strong>未来：让AI真正像“人”一样完成需求</strong></p><p>从2022 年 6 月，GitHub Copilot正式 GA，预览期约 <strong>120 万</strong>人报名试用，到 2025 年 7 月 30 日，Microsoft在财报电话会披露：<strong>GitHub Copilot 累计用户达到 2000 万</strong>，AI Coding越来越融入了Coding的一部分。</p><p>同一时期，GitHub在 2025 年 Octoverse 指出：新加入 GitHub 的开发者中，<strong>接近 80% 在第一周内会使用 Copilot</strong>，在国内高校里，越多越多计算机专业的同学也习惯了用AI完成老师布置的编码作业和实验室任务。</p><p>虽然AI IDE在开发链路有着完整融合，Agent Cli凭着轻巧好用的特性在各个平台得以接入，仍有“三朵乌云”笼罩在AI Coding的上方：</p><ul><li><strong>上下文与历史逻辑断层：</strong>复杂软件交付的核心风险从“写代码”转移到“理解系统”：AI 在跨模块、跨版本演进的代码库中，虽然有了Agents.md等补充说明，但仍难以可靠恢复“写在人的脑子里”的决策背景（历史权衡、隐式约束、组织约定），导致生成结果在局部正确、全局不一致，难以支撑大型项目的持续演进。</li><li><strong>风格与规范众口难调</strong>：AI 输出质量上限受提示词与团队规范治理约束，在此之上开发者对注释密度、命名习惯、错误处理、抽象层级、输出语言、冗余解释等偏好也各不相同，AI 生成结果难以在跨人协作中保持一致的工程美学与可维护性。</li></ul><p><img src="https://s3.bmp.ovh/2026/02/10/wf0DRx1O.png"/></p><ul><li><strong>闭环验证不足：</strong>AI Coding 的最后一公里仍是“可执行的验证闭环”：当前的 Browser/Computer Use 虽可自动点击、输入、浏览，但在复杂 UI、动态页面、权限/登录、长任务与异常恢复上仍不稳定；同时多模态理解与执行之间存在误差，使得“像人一样体验产品并验证”仍主要停留在受控环境与基准测试阶段。OpenAI API 文档也把 <strong>Computer use</strong> 标注为 beta/preview，并提醒不要在高风险、强认证环境中盲目信任。</li></ul><p><img src="https://s3.bmp.ovh/2026/02/10/0qA98LLy.png"/></p><p>OpenAI CUA的任务完成率
（<a href="https://openai.com/zh-Hans-CN/index/computer-using-agent/?utm\_source=chatgpt.com）">https://openai.com/zh-Hans-CN/index/computer-using-agent/?utm\_source=chatgpt.com）</a></p><p><strong>组织级记忆</strong></p><p>如果把一个成熟团队的交付能力拆开，代码只是最终沉淀；真正让系统长期可演进的，是隐藏在过程里的“因果链”：<strong>为什么采用这个架构、为什么拒绝另一个方案、哪些地方踩过坑、上线后出现过哪些事故、哪些约束是被血泪教训写进来的</strong>。而这部分知识通常散落在PR 讨论、Issue、告警、复盘文档、灰度/回滚记录里，甚至只存在于核心成员的口述经验中。</p><p>而未来，随着skills的兴起，相信会有更好的记录系统，支持跨系统的“因果链检索”（代码变更 → PR → Issue → 代码草稿 → 事故/告警），从 PR/讨论自动抽取决策要点生成草稿，自动识别“过期决策”（依赖变更、架构迁移）并提醒修订。</p><p><strong>AI本地记忆</strong></p><p>moltBook的大火，一大原因是它将任务运行在计算机本地。正如插件补全到AI IDE的跨越，AI上下文上限的增长在里面起了很大作用。也许在未来AI能记忆的上下文会进一步增长，而相应地产生一套“编程习惯记忆助手”，将<strong>记忆做成</strong>可审计、可检索、可更新的外部系统，存储记录开发者日常工具链（文件、Git、CI、监控）的使用习惯。
当正式开始执行任务时，可以去尽可能载入开发者自己的开发习惯，真正地成为一名能辅助、能教育的“助手实习生”。</p><p><strong>为AI而生的交互规范</strong></p><p>真实产品验证的最大难点不是“点按钮”，而是<strong>环境的不确定性</strong>：登录与权限差异、页面状态分支、弹窗与异常、网络抖动、数据不一致、第三方依赖波动……人类能凭经验绕过，AI 要做到稳定，需要的是“执行体系”而非“视觉能力”本身。</p><p>但真实验收的关键是<strong>判定标准</strong>。人类 QA 在做的事情，本质上是把需求转化为断言：看到什么、没看到什么、某个指标是否变化、某条日志是否出现、某个请求是否符合契约。</p><p>在未来，或许会有一套规范，将软件工程教程范式定义为<strong>可机读的断言集合</strong>，把 PRD/issue/验收标准自动转成断言（UI 状态、API 响应、日志、指标、数据一致性、权限边界）。</p><p>在此之上，人为交互的规范都将抽象化，相信会有越来越多像<a href="https://www.moltbook.com/">moltBook</a>这样的社区，针对AI提供一套更方便的交互形式，让AI可以原生地完成所有功能。</p><p>最后，随着 AI Agent 形态持续成熟，AI Coding 很可能在未来两年再次改写我们的直觉：它不再只是“更聪明的补全”或“更顺手的对话框”，而是逐步具备一名工程师完成交付所需的完整能力栈。</p><p>但对AI Coding 的最佳期待并不是“某一天它完全取代人”，而是它把软件工程中最耗时、最重复、最容易出错的部分系统性外包给机器，让大家把注意力重新集中在真正稀缺的东西上——<strong>对问题的洞察、对系统的判断</strong>，让<strong>“码农”、“搬砖”</strong>这两个词彻底成为过去式。</p><p><strong>代码会消失，但创造的欲望不会</strong></p><p><img src="https://s3.bmp.ovh/2026/02/10/N9fBs42R.png"/></p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/AI/AI%20Coding%E7%9A%84%E8%BF%87%E5%8E%BB%E3%80%81%E7%8E%B0%E5%9C%A8%E5%92%8C%E6%9C%AA%E6%9D%A5#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>告别国内服务器</title>
            <link href='https://www.matto.top/posts/backend/%E5%91%8A%E5%88%AB%E5%9B%BD%E5%86%85%E6%9C%8D%E5%8A%A1%E5%99%A8'/>
            <id>https://www.matto.top/posts/backend/%E5%91%8A%E5%88%AB%E5%9B%BD%E5%86%85%E6%9C%8D%E5%8A%A1%E5%99%A8</id>
            <published>2025-10-22T18:24:00.000Z</published>
            <updated>2025-10-22T18:24:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/backend/%E5%91%8A%E5%88%AB%E5%9B%BD%E5%86%85%E6%9C%8D%E5%8A%A1%E5%99%A8'>https://www.matto.top/posts/backend/%E5%91%8A%E5%88%AB%E5%9B%BD%E5%86%85%E6%9C%8D%E5%8A%A1%E5%99%A8</a></blockquote>
<div><div><p>服务器到期了
但是实在续不起了，该说拜拜了</p><p>目前只有一台主力服务器野草云</p><h2 id="redis处理">redis处理</h2><p>腾讯云挂了然后onedrive网盘炸了
本来以为是因为服务跑在腾讯云的服务器上
结果发现早就迁到野草云了，草
重新启动了发现还有问题，清一色500，鉴定为onedrive的问题
onedrive这边排了半天，发现是redis的问题</p><pre><code class="lang-redis"> redis-cli ROLE
1) &quot;slave&quot;
2) &quot;101.43.35.74&quot;
3) (integer) 6379
4) &quot;connecting&quot;
5) (integer) -1</code></pre><p>吧redis的角色切换到master
<code>redis
redis-cli SLAVEOF NO ONE</code></p><p>修了之后发现token还是存不进去
还是得走onedrive-vercel那套鉴权
走到第二步，然后调接口获取token</p><h3 id="bug">bug</h3><p>kv.set发现跑不动
Redis set failed (REDIS_URL not set): MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.</p><p>用sudo du -xhd1 / | sort -h指令，然后一路梳理下来发现是mongo占太多了
写一个docker的配置文件限制一下</p><pre><code class="lang-bash"> sudo tee /etc/docker/daemon.json &gt;/dev/null &lt;&lt;&#x27;JSON&#x27;
{
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;50m&quot;,
    &quot;max-file&quot;: &quot;3&quot;
  }
}
JSON</code></pre><p>文件发现一值获取不到download信息
因为之前获取的接口写的很随意，实际上要通过:content才能拿到@microsoft.graph.downloadUrl
因此这里加了个/content获取信息的兜底来修复，具体的可以看ad8c836这个commit</p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/backend/%E5%91%8A%E5%88%AB%E5%9B%BD%E5%86%85%E6%9C%8D%E5%8A%A1%E5%99%A8#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>ios侧适配的坑</title>
            <link href='https://www.matto.top/posts/frontend/ios%E5%BC%80%E5%8F%91%E7%9A%84%E5%9D%91'/>
            <id>https://www.matto.top/posts/frontend/ios%E5%BC%80%E5%8F%91%E7%9A%84%E5%9D%91</id>
            <published>2025-03-12T03:32:00.000Z</published>
            <updated>2025-03-12T03:32:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/frontend/ios%E5%BC%80%E5%8F%91%E7%9A%84%E5%9D%91'>https://www.matto.top/posts/frontend/ios%E5%BC%80%E5%8F%91%E7%9A%84%E5%9D%91</a></blockquote>
<div><div><h2 id="前言">前言</h2><p>最近在修ios端页面的一些bug
只能说safari是现代ie名不虚传</p><h2 id="坑">坑</h2><h3 id="drop-shadow配上滚动元素不渲染">drop-shadow配上滚动元素不渲染</h3><p>当一个父盒子里的子元素超宽，并且设置成是scroll模式下
有一部分子元素一开始是隐藏着的，只有滚动了才会显示</p><p>这时候如果父元素设置了drop-shadow，那么这部分子元素会有一个渲染延迟甚至压根不渲染</p><pre><code class="lang-css">.parent {
  /* ... */
  overflow-y: scroll;
}

.child {
  /* ... */
  filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.5));
}</code></pre><h4 id="解决方案">解决方案</h4><h5 id="1.-使用box-shadow代替drop-shadow">1. 使用box-shadow代替drop-shadow</h5><pre><code class="lang-css">.child {
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}</code></pre><h5 id="2.-使用transform:-translatez(0)">2. 使用transform: translateZ(0)</h5><pre><code class="lang-css">.child {
  transform: translateZ(0);
}</code></pre><h3 id="safari单标签模式渲染问题">safari单标签模式渲染问题</h3><p>safari当前有两种模式
多标签模式和单标签模式
前者导航栏在底部，可以迅速切换
后者导航栏在顶部，底部是一些操作按钮</p><p>多标签模式下比较正常
单标签模式下会存在一些问题</p><h4 id="问题">问题</h4><ol start="1"><li>100vh会算上导航栏的高度</li><li>会给浏览器加上橡皮条的操作</li><li>下拉快了会把底部操作栏隐藏掉</li></ol><h4 id="解决方案">解决方案</h4><ol start="1"><li>使用-webkit-fill-available，它可以正常获取到真正的vh</li></ol><pre><code class="lang-css">@supports (-webkit-touch-callout: none) {
  body {
    min-height: -webkit-fill-available;
  }
}</code></pre><ol start="2"><li>禁用ios的touchStart事件</li></ol><p>加passive是因为ios的touchStart事件会默认阻止默认行为，导致无法触发touchmove事件</p><pre><code class="lang-js">document.body.addEventListener(&#x27;touchstart&#x27;, (e) =&gt; {
  e.preventDefault();
}, { passive: false });</code></pre><p>3的禁止和2一致，上面这个也会阻止底部操作栏被隐藏的操作</p><h3 id="video元素">video元素</h3><p>这个也是大众很清楚的坑了
我接触到的主要是</p><ol start="1"><li>视频播放时，ios会默认全屏播放</li><li>ios视频不自动播放</li><li>ios视频会显示操作按钮</li></ol><h4 id="解决方案">解决方案</h4><p>我是使用muted来解决自动播放的问题</p><p>以及利用playinline来视频蜜汁放大的问题
playinline真的是个很重要的元素，它可以防止视频全屏播放，保持页面上下文，让用户可以不离开当前页面观看视频</p><pre><code class="lang-html">    &lt;video
      autoplay
      loop
      muted
      webkit-playsinline
      playsinline
      src=&quot;https://example.com/video.mp4&quot;
    &gt;</code></pre></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/frontend/ios%E5%BC%80%E5%8F%91%E7%9A%84%E5%9D%91#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>热更新的ws连接不适用</title>
            <link href='https://www.matto.top/posts/backend/%E7%83%AD%E6%9B%B4%E6%96%B0%E7%9A%84ws%E8%BF%9E%E6%8E%A5%E4%B8%8D%E9%80%82%E7%94%A8'/>
            <id>https://www.matto.top/posts/backend/%E7%83%AD%E6%9B%B4%E6%96%B0%E7%9A%84ws%E8%BF%9E%E6%8E%A5%E4%B8%8D%E9%80%82%E7%94%A8</id>
            <published>2025-01-21T07:24:00.000Z</published>
            <updated>2025-01-21T07:24:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/backend/%E7%83%AD%E6%9B%B4%E6%96%B0%E7%9A%84ws%E8%BF%9E%E6%8E%A5%E4%B8%8D%E9%80%82%E7%94%A8'>https://www.matto.top/posts/backend/%E7%83%AD%E6%9B%B4%E6%96%B0%E7%9A%84ws%E8%BF%9E%E6%8E%A5%E4%B8%8D%E9%80%82%E7%94%A8</a></blockquote>
<div><div><p>开发时候遇到了如标题一样的问题
按理来说这种问题应该放在归类里一起整理
但是实在没有博客可以水了，就这么整了</p><h2 id="问题">问题</h2><p>热更新的 ws 连接不适用
开发者工具里显示</p><pre><code class="lang-bash">WebSocket connection to &#x27;wss://localhost:9000/&#x27; failed:
client:511 [vite] failed to connect to websocket.
your current setup:
  (browser) www.xingtu.cn/ &lt;--[HTTP]--&gt; localhost:9000/ (server)
  (browser) www.xingtu.cn:/ &lt;--[WebSocket (failing)]--&gt; localhost:9000/ (server)
Check out your Vite / network configuration and</code></pre><p>打开网络连接，发现尝试连接了，但是显示不适用的状态
那确实是 ws 连接出问题了，从热更新服务启动和连接各看看吧</p><h2 id="排查">排查</h2><h3 id="1.-检查vite的配置">1. 检查vite的配置</h3><p>首先是检查vite的配置，看看有没有启动</p><pre><code class="lang-ts">// vite.config.ts
server: {
  hmr: {
    port: 9000,
  },
},</code></pre><p>没啥问题，正常启动
使用debug模式启动下vite服务看看有没有启动hmr服务</p><pre><code class="lang-bash">DEBUG=vite:* pnpm dev</code></pre><p>打开后可以看到大量的vite:hmr信息，说明hmr服务启动的没有问题，那就是链接的问题了</p><h3 id="2.-检查连接">2. 检查连接</h3><p>用抓包器看看ws连接的请求
请求结果是</p><pre><code class="lang-bash">WSS/1.1 400</code></pre><p>emmm非常简朴的相应结果，排排请求信息吧
请求信息很正常
但是多了一个rule
原来是中间件规则给wss请求加了个请求头</p><pre><code class="lang-bash">Xt-env: dev</code></pre><p>这个请求头是用来区分开发环境和生产环境的，结果他就挂了
解决方法就是在匹配规则里排除掉wss</p><pre><code class="lang-bash">www.matto.top https://localhost:9000/

# 排除掉wss
https://www.matto.top https://localhost:9000/</code></pre></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/backend/%E7%83%AD%E6%9B%B4%E6%96%B0%E7%9A%84ws%E8%BF%9E%E6%8E%A5%E4%B8%8D%E9%80%82%E7%94%A8#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>mac服务器配置</title>
            <link href='https://www.matto.top/posts/blog/mac%E5%88%9D%E5%A7%8B%E8%AE%BE%E7%BD%AE'/>
            <id>https://www.matto.top/posts/blog/mac%E5%88%9D%E5%A7%8B%E8%AE%BE%E7%BD%AE</id>
            <published>2024-12-16T07:24:00.000Z</published>
            <updated>2024-12-16T07:24:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/blog/mac%E5%88%9D%E5%A7%8B%E8%AE%BE%E7%BD%AE'>https://www.matto.top/posts/blog/mac%E5%88%9D%E5%A7%8B%E8%AE%BE%E7%BD%AE</a></blockquote>
<div><div><p>入职啦，村里又发 mac 了
记一下配置过程</p><p>本设置最后一次更新时2025.6.25</p><h2 id="终端">终端</h2><h3 id="iterm2">iTerm2</h3><p>可以装个 <a href="https://iterm2.com/downloads.html">iTerm2</a> 替代初始的 terminal
虽然俺更喜欢 vsc 的终端</p><h3 id="zsh">zsh</h3><p>可以把 zsh 设置成默认的</p><pre><code class="lang-bash">chsh -s /bin/zsh</code></pre><p>然后开始装插件</p><pre><code class="lang-bash"># 美观的ohmyzsh
sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;
# 自动补全
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
# 语法高亮检查
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting</code></pre><p>最后改一下~/.zshrc 的 plugin 部分</p><pre><code class="lang-bash">plugins=(git
    zsh-autosuggestions
    zsh-syntax-highlighting
    z)</code></pre><h2 id="homebrew">homebrew</h2><p>安装</p><pre><code class="lang-bash"># 可以尝试使用国内镜像
/bin/zsh -c &quot;$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)&quot;</code></pre><p>咱们用的是 zsh，得在 zshrc 里配置一下</p><pre><code class="lang-bash">source /Users/bytedance/.profile</code></pre><h2 id="git">git</h2><p>先生成 key
这里用的是 ed25519，比起 rsa 更安全、更快</p><pre><code class="lang-bash">ssh-keygen -t ed25519 -C &quot;matto2024@163.com&quot; # 把邮箱改成自己的</code></pre><p>git 一般是自带的
生成好主要是需要配置一下 ssh config 和 git config
前者用途是为不同的 Git 服务器(如 GitHub、GitLab、公司内部 Git)设置不同的 SSH 密钥
后者是配置 git 用户名和邮箱</p><p>~/.ssh/config 文件</p><pre><code class="lang-bash">Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519</code></pre><p>git config 配置</p><pre><code class="lang-bash">git config --global user.name &quot;matto&quot;
git config --global user.email &quot;matto2024@163.com&quot;</code></pre><p>最后是需要在对应的 git 服务器上添加 ssh 的 pub key</p><h3 id="多个-git-用户信息的配置">多个 git 用户信息的配置</h3><p>然而有的时候我们针对公司内的 git 和 自己的 github 需要配置不同的用户信息
我们可以在 gitconfig 里面来进行匹配，完成配置</p><pre><code class="lang-bash"># 这一串设置通过匹配当前仓库的remote.origin.url 来决定使用哪个gitconfig
# 这里匹配的是github和gitlab的git和https前缀
[includeIf &quot;hasconfig:remote.*.url:git@github.com:*/**&quot;]
    path = ~/.gitconfig-personal
[includeIf &quot;hasconfig:remote.*.url:https://github.com/**&quot;]
    path = ~/.gitconfig-personal

[includeIf &quot;hasconfig:remote.*.url:git@gitlab.company.com:*/**&quot;]
    path = ~/.gitconfig-company
[includeIf &quot;hasconfig:remote.*.url:https://gitlab.company.com/**&quot;]
    path = ~/.gitconfig-company</code></pre><p>然后创建对应的 gitconfig-personal 文件</p><pre><code class="lang-bash">touch ~/.gitconfig-personal</code></pre><p>再在里面填入</p><pre><code class="lang-bash">[user]
    name = matto
    email = matto2023@163.com</code></pre><p>打开一个使用 github 的仓库，然后执行</p><pre><code class="lang-bash">git config user.name</code></pre><p>来看看有没有成功，正常来说就成功了。</p><h2 id="前端环境">前端环境</h2><h3 id="node">node</h3><pre><code class="lang-bash">brew install fnm</code></pre><p>然后装 node</p><pre><code class="lang-bash">fnm install --lts</code></pre><p>会发现安装的 node 指令用不了
需要在.zshrc 里配置一下</p><pre><code class="lang-bash"># 基础配置 - 必需
eval &quot;$(fnm env --use-on-cd)&quot;

# 可选的额外配置
export PATH=&quot;/opt/homebrew/bin:$PATH&quot;  # 确保 brew 安装的 fnm 在 PATH 中

# 自动完成配置（可选）
# 添加 fnm 的自动完成支持
eval &quot;$(fnm completions --shell zsh)&quot;

# 自动切换 node 版本（可选）
# 如果目录下有 .node-version 或 .nvmrc 文件，会自动切换到对应版本
eval &quot;$(fnm env --use-on-cd)&quot;</code></pre></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/blog/mac%E5%88%9D%E5%A7%8B%E8%AE%BE%E7%BD%AE#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>qqbot开发</title>
            <link href='https://www.matto.top/posts/project/qqbot%E5%BC%80%E5%8F%91'/>
            <id>https://www.matto.top/posts/project/qqbot%E5%BC%80%E5%8F%91</id>
            <published>2024-12-09T07:24:00.000Z</published>
            <updated>2024-12-09T07:24:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/project/qqbot%E5%BC%80%E5%8F%91'>https://www.matto.top/posts/project/qqbot%E5%BC%80%E5%8F%91</a></blockquote>
<div><div><p>这里使用的koishi
它的界面和文档都让俺很满意
而且整体架构也好优雅，拓展性很强！
以及它的社区也很活跃，有啥问题基本都能找到解决方案</p><p>koishi的下载和安装这里就不赘述了</p><h2 id="基础知识">基础知识</h2><h3 id="配置本地同步化">配置本地同步化</h3><p>koishi的配置文件是存在本地的，所有的配置信息都在koishi.yml里面，它在这个基础上提供了一个非常详尽的可视化配置界面</p><p>配置文件存在本地有两点我们需要注意：</p><ol start="1"><li>每次编辑完之后要进行保存，与本地文件想同步</li><li>当我们需要进行迁移的时候，将配置文件一起迁移即可。</li></ol><h3 id="服务器本地开发">服务器本地开发</h3><p>当我们在云服务器跑这个服务的时候
会把它跑在127.0.0.1:5140端口上
然而在本地就可以通过访问相同域名来实现开发，编辑远程服务器的内容
采用的应该是ssh隧道，将本地的5140端口映射到远程服务器的5140端口上</p><h2 id="qq接入">qq接入</h2><p>一开始想使用adapter-qq，由于需要使用qq开发者平台提供的机器人的一套系统
其实还可以，而且功能提示相当友好，但是机器人上线需要进行审核，所以放弃了
于是使用了adapter-onebot</p><h3 id="napcat">napcat</h3><p>koishi-adapter-onebot 现在只提供了koishi的适配，它并没有提供qq的登录态和信息价接受
我们这里通过napcat来登录qq，获取到聊天记录信息，并把消息转发到相应的ws端口上，供koishi-adapter-onebot使用</p><h4 id="安装">安装</h4><p>参照<a href="https://napneko.github.io/guide/boot/Shell">文档</a>
linux直接执行脚本就好了，下面这个脚本会自动帮我们安装linuxQQ、napcat、xvfb</p><pre><code class="lang-bash">curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh &amp;&amp; sudo bash napcat.sh</code></pre><p>安装好了之后，我们再利用指令来启动napcat</p><pre><code class="lang-bash"> xvfb-run -a qq --no-sandbox -q [自己的qq号]</code></pre><h4 id="配置">配置</h4><p>值的一提的事，napcat是有可配置网站的，做的很好看，超喜欢
通过localhost:6099来访问
访问的时候需要token，使用 NapCat.Installer - Linux 一键脚本安装时, 该文件位于 /opt/QQ/resources/app/app_launcher/napcat/config/webui.json</p><p>在使用koishi的one-bot的时候，我们不需要额外的配置
在使用astrbot的时候，需要新建一个方向代理客户端
参照<a href="https://astrbot.app/deploy/platform/aiocqhttp/napcat.html#%E5%9C%A8-napcatqq-%E4%B8%AD%E6%B7%BB%E5%8A%A0-websocket-%E5%AE%A2%E6%88%B7%E7%AB%AF">文档</a></p><h2 id="ai接入">ai接入</h2><p>这里使用的事<a href="https://chatluna.chat/guide/getting-started.html">chatluna</a></p><p>ChatLuna 是一款强大的语言模型聊天服务插件，基于 LangChain 开发，目前作为 Koishi 上的插件存在。ChatLuna 支持与多种主流大语言模型进行交互，如 OpenAI、Google Gemini 和 Claude 等。</p><p>ChatLuna 不仅为用户提供了模型聊天功能，还为其他 Koishi 插件开发者提供了便捷的 LangChain Model 接口，方便他们与大语言模型进行交互和扩展开发。</p><h3 id="接入">接入</h3><p>在插件广场先搜索下载好chatluna插件
下载好之后，koishi有一个非常反直觉的点就是，下载安装好后插件配置里找不到刚刚下载的插件
这里需要点击全局配置，然后在右上方点击 “添加插件” 按钮来添加</p><p>在chatluna插件的基础上我们还需要配置几个基础插件。</p><h4 id="数据库服务">数据库服务</h4><p>这里采用最简单的sqlite，koishi事自带的
启用即可</p><h4 id="模型介接入">模型介接入</h4><p>chatluna插件支持多种模型接入，这里使用的是ds
我们前往插件市场，搜索 chatluna-deepseek-adapter
但是我这边搜索不到，是因为官方插件源没有正常更新。 前往 market 插件设置为其他源即可：
我使用的事<a href="https://koishi-registry.yumetsuki.moe/index.json">https://koishi-registry.yumetsuki.moe/index.json</a>
<img alt="img" src="https://chatluna.chat/assets/markethuanyuan.BdsMIK-i.png"/></p><p>使用deepseek的模型需要申请key
到<a href="https://platform.deepseek.com">deepseek的开放平台</a>去注册一个账号，再充点钱
在apikeys页面创建apikey，将得到的apikey填入chatluna-deepseek-adapter插件的配置信息中
配置好了重载设置，chatluna-deepseek-adapter插件就会尝试开始加载模型
然后就可以在chatluna插件的配置信息中看到可以选择deepseek的模型了</p><h2 id="astrbot">astrbot</h2><p>后来因为社区维护问题，转战了astrbot，这边有着更详细的文档和更活跃的社区。
因为为文档写的非常全，参照<a href="https://astrbot.app/config/astrbot-config.html">文档</a>就可以启动了
消息平台仍然使用的napcat
利用uv来管理python的包管理，利用pnpm来进行dashboard的调试，由于前后端的完全隔离，利用vsc的ssh隧道转发，可以在本地启动前端，再连接服务器的后端进行联调~</p><h2 id="踩坑">踩坑</h2><h3 id="submodule问题">submodule问题</h3><p>开发的时候误将src/external/里面的东西作为git仓库了
解决方案是先拷贝该文件夹
然后将里面的.git文件删除</p><pre><code class="lang-bash">rm -rf .git</code></pre><p>然后再压缩复制到原来项目的地方
最后执行git add .
来导入不带有git信息的子模块</p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/project/qqbot%E5%BC%80%E5%8F%91#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          <entry>
            <title>服务器迁移</title>
            <link href='https://www.matto.top/posts/backend/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB'/>
            <id>https://www.matto.top/posts/backend/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB</id>
            <published>2024-10-09T07:24:00.000Z</published>
            <updated>2024-10-09T07:24:00.000Z</updated>
            <content type='html'><![CDATA[
              <blockquote>该渲染由 Kami API 生成，可能存在排版问题，最佳体验请前往：<a href='https://www.matto.top/posts/backend/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB'>https://www.matto.top/posts/backend/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB</a></blockquote>
<div><div><p>之前腾讯云买的 2 核 4G 的服务器过期了
该续期了
然而一看续期一年要 700r，好贵哦
于是翻了一波特价区，发现买个新的服务器只要 180r/年，那就开工迁移喽</p><h2 id="代理">代理</h2><p>clash 挂了，github 上没找到 release 包，正好试试比较新的 mihomo
download 之后照着<a href="https://wiki.metacubex.one/startup/service/">教程</a>做一遍
它这个官网首页和 github 里的 readme 全是钓鱼的，笑死
配置文件和 clash 比较类似，照着拷一下就好了</p><h2 id="ssh-连接">ssh 连接</h2><p>这次服务器找了半天好像没给 root 账号
自己手动重设了一下，发现 ssh 连不上
原因是改了配置文件之后没有重启 sshd 服务</p><pre><code class="lang-bash">PermitRootLogin yes
PasswordAuthentication yes</code></pre><h2 id="mysql">mysql</h2><p>主要就是数据库迁移
土法迁移，navicat 导出原来服务器数据库文件拷到本地，然后在新服务器上的对应数据库里面再直接执行。
可视化界面点点点，还行。</p><p>但是卡用户登录卡了很久
主要就是 mysqld.conf 文件修改了之后，没有 restart mysql 服务。
然后就是不停地创建账号，设置权限。</p><pre><code class="lang-sql">-- aoikaze换成自己的账号，114514换成自己的密码
CREATE USER &#x27;aoikaze&#x27;@&#x27;%&#x27; IDENTIFIED BY &#x27;114514&#x27;;
GRANT ALL PRIVILEGES ON *.* TO &#x27;aoikaze&#x27;@&#x27;%&#x27; WITH GRANT OPTION;
FLUSH PRIVILEGES;</code></pre><h2 id="redis">redis</h2><p>这里讲一下数据量比较小的 redis 迁移，用的是 redis 自带的指令
下好并启动好 redis 之后
在新的服务器上开好 6379 端口的防火墙
然后把/etc/redis/redis.conf 里面的 bind 127.0.0.1 ::1 改成 bind 0.0.0.0 ::1
protected-mode yes 改成 protected-mode no
然后重启 redis 服务</p><p>接着在老的服务器里面连一下新的服务器的 redis</p><pre><code class="lang-bash">redis-cli -h 新的服务器ip -p 6379</code></pre><p>然后执行</p><pre><code class="lang-bash">SLAVEOF 新服务器ip 6379</code></pre><p>然后就迁移好了</p><h2 id="nginx">nginx</h2><p>合了半天，这个是折磨我最惨的</p><p>nginx 安装的话，使用 apt 安装，然后配置/etc/nginx/nginx.conf 文件</p><p>问题所在是 ssl 证书
刚好之前的 ssl 证书快过期了，这边需要重新申请一下，索性用的是 certbot，自动续期</p><h2 id="默认启动">默认启动</h2><p>上面的mysql和redis还有nginx装好之后，可以加一个默认启动</p><pre><code class="lang-bash">systemctl enable mysql &amp;&amp; systemctl enable redis-server.service &amp;&amp; systemctl enable nginx</code></pre><h3 id="cerbot">cerbot</h3><p>certbot 是基于 let&#x27;s encrypt 用于申请 ssl 证书的
和传统的 ssl 证书类似，它也是先进行域名所有权验证，然后再派发证书
先进的地方在于，它可以搭配 nginx 或者 apache 一起使用，临时修改 nginx 配置文件，将鉴权信息配置好，然后重启 nginx 服务进行验证，最后再将 nginx 修改回去。
阿里云服务器用的 nginx-ui，里面有个一键配置 ssl 证书，用的就是这个。</p><h4 id="使用">使用</h4><p>其实也就安装和启动两部
先安装好，安装的话需要有 python3 的环境
网站是<a href="https://certbot.eff.org/instructions?ws=nginx&amp;os=pip&amp;tab=standard">certbot</a></p><pre><code class="lang-bash">sudo apt update
sudo apt install python3 python3-venv libaugeas0
sudo apt install certbot</code></pre><p>然后执行就好了
把下面的 email 和域名改成自己的</p><pre><code class="lang-bash">sudo certbot certonly --email example@qq.com --server https://acme-v02.api.letsencrypt.org/directory --agree-tos --manual -d example.com</code></pre><p>有两种验证方式
一种是设置 ip 地址 的解析，加上对应的 acame 的，设置相应的 txt 值来进行验证
还有一种是让 cerbot 自动修改 nginx 配置文件，来进行验证
这里腾讯云把我服务器的请求给拦了，乌鱼子，换 ip 解析验证了
根据提示在对应云服务管理里面设置域名解析</p><p>可以利用这个指令查询 cerbot 申请的证书的情况</p><pre><code class="lang-bash">sudo certbot certificates</code></pre><h2 id="阿里云迁移">阿里云迁移</h2><p>用了4年的阿里云服务器终于不给低价续啦
也是感慨，这台1核2G的服务器陪了我4年，最后还是因为500r左右的续费价格，只能放弃了
现在所有服务都在野草云上了..</p><h3 id="mx-space-core迁移">mx-space-core迁移</h3><p>阿里云现在跑的服务只有一个mx-space-core了
这个迁移很简单，现在原有的后台数据页，有很多备份记录，下载一个最新的在本地</p><p>然后在新服务器上重新起一下这个服务。
core包含redis、mongdb还有我没看懂的nest项目组织结构
还是老老实实docker部署吧
docker部署后还是老样子/proxy启动后台项目
在初始化界面有恢复按钮，把刚刚下载的备份文件传上去，就ok了，超丝滑
备份完了之后，所有博客和手记都是草稿状态
得重新点一下发布，不然会发现blogs是空的）</p></div></div>
              <p style='text-align: right'>
              <a href='https://www.matto.top/posts/backend/%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%BF%81%E7%A7%BB#comments'>看完了？说点什么呢</a>
              </p>
            ]]>
            </content>
            </entry>
          
    </feed>