<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>hopes7 Blog</title>
    <link>https://hopes7.com</link>
    <description>AI、开发效率、技术博客</description>
    <language>zh-CN</language>
    <atom:link href="https://hopes7.com/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Fri, 23 Oct 2026 18:21:55 GMT</lastBuildDate>
    
    <item>
      <title><![CDATA[1024 这天 ZCode 送了 5 亿 token：这篇文章就是用它写的]]></title>
      <link>https://hopes7.com/zh/blog/zcode-1024-token-gift</link>
      <guid>https://hopes7.com/zh/blog/zcode-1024-token-gift</guid>
      <description><![CDATA[1024 程序员节，ZCode 送了 5 亿 token「赛博鸡蛋」，10 月 26 日早上 9 点到期。领取步骤、规则细节都整理好了，文末挂了邀请链接——这篇文章本身就是用送的 token 写的。]]></description>
      <content:encoded><![CDATA[<h1 id="1024-这天-zcode-送了-5-亿-token这篇文章就是用它写的">1024 这天 ZCode 送了 5 亿 token：这篇文章就是用它写的</h1>
<h2 id="节日礼物到账">节日礼物到账</h2>
<p>1024 程序员节，别人家的节日礼物是周边和优惠券，ZCode 直接发了「赛博鸡蛋」：<strong>免费 5 亿 Tokens</strong>，可用于 GLM-5.3 与 GLM-5.3-Flash 两款模型。数字很大，保质期很短——只够用到这个周末。</p>
<p>领完额度我就开始盘算这批 token 该干点什么，最后就有了你现在看到的这篇文章：<strong>它本身就是用送的 token 写出来的</strong>。从核实活动规则，到修文章发布前博客里藏着的几个 SEO 隐患，全程跑在 ZCode 上。</p>
<p>这大概是程序员节最实在的礼物形态：不用转发抽奖，不用拼手速秒杀，登录领了就能用。</p>
<h2 id="怎么领">怎么领</h2>
<p>客户端操作，三步：</p>
<ol>
<li>从 <a href="https://zcode.z.ai">ZCode 官网</a>下载客户端（macOS / Windows / Linux 都有），安装并登录</li>
<li>点客户端<strong>左下角</strong>的「ZCODE 1024 程序员节 · 5 亿 TOKENS」活动入口</li>
<li>点「打开大礼包」</li>
</ol>
<p>两个要点：</p>
<ul>
<li><strong>新用户也能领</strong>，不需要先成为老用户——正好配合文末的邀请链接一起用</li>
<li><strong>领取成功 ≠ 立刻可用</strong>。礼包 2026 年 10 月 23 日 23:00 生效，<strong>2026 年 10 月 26 日 09:00 到期</strong>。生效前客户端会显示「待生效」，别以为是没领上</li>
</ul>
<p>领完可以到「模型设置 → Start Plan」里查看「ZCode 1024 Dev Day」活动套餐和有效期。</p>
<h2 id="别把两种额度看串了">别把两种额度看串了</h2>
<p>有个容易踩的坑：设置页里有两张套餐卡片，长得很像。</p>
<ul>
<li><strong>ZCode 1024 Dev Day</strong>：本次活动的 5 亿 token 礼包，看它的有效期</li>
<li><strong>ZCode Start Plan</strong>：另一个套餐，显示 GLM-5.3 每日 300 万、GLM-5.3-Flash 每日 500 万——这是每日额度，<strong>不是</strong>活动礼包，也不能拿来推算活动的每日上限</li>
</ul>
<p>另外，5 亿是 GLM-5.3 和 GLM-5.3-Flash 两款模型<strong>共享的礼包总量</strong>，官方没说明两款各占多少，不要理解成每款各 5 亿。</p>
<p>顺带说明：这次活动我没有在官网找到集中的公告页，规则细节主要参考了<a href="https://www.appinn.com/zcode-1024-free-500-million-tokens/">小众软件的整理</a>（截图验证过领取流程），以客户端内活动卡片为准。</p>
<h2 id="这批-token-我拿去干了什么">这批 token 我拿去干了什么</h2>
<p>单纯晒额度没什么信息量，说实际产出。</p>
<p>第一件就是这篇文章本身——核实活动规则、整理成文，包括发现去年 1024 智谱搞的是 Coding Plan 特惠（新人首单 5 折、邀请返赠金），今年改成了直接送 token 的打法。</p>
<p>第二件是把博客近期攒着的 SEO 隐患清了一遍：hreflang 交替链接指向 404 的单语文章、sitemap 里的相对路径、两篇同题文章互相稀释排名、爬虫入口页 canonical 不一致。这些活儿琐碎但费时，以前要攒一个周末，这次领了 token 当天顺手做完。</p>
<p>对我这种重度用户来说这很解渴——之前记录过<a href="/zh/blog/codex-token-usage-weekly-record">一周消耗 3.5 亿 Token 的 Codex 使用账单</a>和 <a href="/zh/blog/rtk-token-consumption-week-test">RTK 优化后降到 1.5 亿的实测</a>，5 亿大概就是我一周的量。</p>
<h2 id="邀请活动免费蹬蹬共得权益">邀请活动：免费蹬蹬，共得权益</h2>
<p>除了节日礼包，10 月中旬的 v3.15.1 更新还上线了「邀请好友·免费蹬蹬，共得权益」活动：邀请好友下载并登录 ZCode，双方都能拿 DAILY BUILD 专属权益。流程三步：下载客户端 → 登录完成邀请要求 → 领取权益。</p>
<p>需要一辆氛围编程工具的话，欢迎用我的邀请链接上车：</p>
<p><strong><a href="https://zcode.z.ai/invite/4DLQTFNQ">https://zcode.z.ai/invite/4DLQTFNQ</a></strong></p>
<h2 id="鸡蛋保质期很短别囤">鸡蛋保质期很短，别囤</h2>
<p>最后一句实在话：这批 token <strong>10 月 26 日早上 9 点就过期</strong>，囤是囤不住的，领了就安排事情干——写篇文章、重构个小工具、把搁置的旧项目翻出来喂给它，Flash 档跑这些日常任务绰绰有余。</p>
<p>厂商持续送 token 图什么？AI 编程工具的竞争已经到了抢工作流入口的阶段，谁的工具常驻你的终端，谁家的模型就吃下你的日常决策链路。免费 token 是获客成本，赌的是你用惯之后留下来。这买卖对双方都不亏：它要留存，你要额度——那就把额度用干净。</p>
<p>1024 快乐，周末薅蛋愉快。</p>
<hr>
<p><em>相关文章：</em></p>
<ul>
<li><em><a href="/zh/blog/codex-token-usage-weekly-record">一周消耗 3.5 亿 Token：我的 Codex 使用记录</a></em></li>
<li><em><a href="/zh/blog/rtk-token-consumption-week-test">一周 1.5 亿 Token：RTK 优化后我的 Codex 使用记录</a></em></li>
</ul>]]></content:encoded>
      <pubDate>Sat, 24 Oct 2026 00:00:00 GMT</pubDate>
      <category>zcode</category>
      <category>glm</category>
      <category>token</category>
      <category>ai-coding</category>
      <category>ai辅助开发</category>
      <category>1024</category>
    </item>
    <item>
      <title><![CDATA[八把 CA 私钥引发的连锁升级:eMudhra 证书泄露事件与全生态修复清单]]></title>
      <link>https://hopes7.com/zh/blog/emudhra-ca-key-leak-2026</link>
      <guid>https://hopes7.com/zh/blog/emudhra-ca-key-leak-2026</guid>
      <description><![CDATA[一把 P-384 私钥的有效熵只剩几个比特,修复它却牵动了几乎所有主流语言生态。这次事故的关键词不是“吊销”,而是“重新构建”。]]></description>
      <content:encoded><![CDATA[<h2 id="先说结论">先说结论</h2>
<p>如果你最近一周没刷到这条消息,值得补一下课:</p>
<p>2026 年 10 月 17 日,有安全研究者发现 CA 机构 eMudhra(emSign)的 8 个中间 CA 证书,其私钥可以被第三方直接推导出来。这些密钥生成于 2025 年 4 月 30 日的一场密钥仪式,由于流程缺陷,本应具备 384 位安全强度的 P-384 密钥,实际有效熵只剩几个比特。</p>
<p>这不是“某张 HTTPS 证书需要吊销”级别的常规事故。私钥可推导,意味着攻击者理论上可以为<strong>任意域名</strong>签发浏览器会信任的证书,安静地做中间人;其中的代码签名中间证书还能给恶意软件披上合法外衣。</p>
<p>于是过去一周我们看到了一次罕见的全生态连锁响应:</p>
<ul>
<li>Chrome、Mozilla、Apple 拉黑了 6 个受影响的 TLS 中间证书,Microsoft 拉黑了 2 个代码签名中间证书;</li>
<li>Go 发布 1.27.3 / 1.26.10,把拒绝名单硬编码进了 <code>crypto/x509</code>(CVE-2026-111943);</li>
<li>Node.js 发布 10 月 21 日安全版 22.23.4 / 24.22.1 / 26.12.1;</li>
<li>Python 的 certifi、pip、uv,Rust 的 rustls-webpki、webpki-roots,PHP 的 composer/ca-bundle 全部跟进;</li>
<li>Oracle 在 10 月 21 日到 22 日为 Java 发布带外安全告警 CVE-2026-91403(CVSS 9.1);</li>
<li>Electron、curl、Debian/Ubuntu/Alpine 的证书包同步更新。</li>
</ul>
<p>好消息是,截至 Talos 10 月 21 日发布分析时,尚未在野外发现这些弱密钥被实际滥用,Chrome 在 Certificate Transparency 日志里也没有找到伪造证书。</p>
<p>但“还没被滥用”从来不是“不用升级”的理由——这波紧急修复的意义,就是抢在滥用开始之前把门关上。</p>
<h2 id="事故是怎么回事">事故是怎么回事</h2>
<p>把时间线摊开看,问题出在一年半之前。</p>
<p>2025 年 4 月 30 日,eMudhra 为 24 个下级 CA 举行密钥生成仪式。仪式中的 wrap/unwrap 环节存在缺陷,产生了一组强度严重不足的 P-384 密钥。按照 Cisco Talos 的分析,这组密钥的有效熵只有 3 到 8 个比特——而规范要求是 384 位。其中 8 把弱密钥被误用到了 8 张中间证书上:6 张 TLS 证书(EV/OV/DV 的 G3B、G3C 变体)和 2 张代码签名证书,全部挂在 emSign Root TLS CA – G3 与 Root CS CA – G3 之下,另有一条 cross-signed 到 Comodo AAA 的路径。</p>
<p>有效熵只剩几个比特意味着什么?意味着私钥不在“猜不出来”的范畴里,而在“暴力枚举一会儿就能算出来”的范畴里。据报道,其中一把私钥被研究者复原后,值竟然接近一个小到离谱的整数。</p>
<p>这样的密钥在野外存在了大约 17 个月,直到 2026 年 10 月 17 日才被发现。eMudhra 随后吊销了受影响证书,并停止从 G3 根签发新证书。Mozilla Bugzilla #2083449 记录了整个处置过程,Talos 在 10 月 21 日发布了完整分析。</p>
<h2 id="为什么这次的修复方式这么重">为什么这次的修复方式这么“重”</h2>
<p>常规的证书事故,处置是“吊销 + 换证书”,应用层几乎无感。</p>
<p>这次不一样。私钥本身可推导,所以光吊销不够——必须让全世界的 TLS 客户端<strong>拒绝这些公钥</strong>,不管它们出现在哪张证书里。</p>
<p>麻烦在于,信任这些公钥的地方远不止浏览器:</p>
<ul>
<li>有的运行时把根证书库编译进了二进制(Go 标准库、Rust 的 webpki-roots);</li>
<li>有的发行渠道各自维护信任库快照(Python certifi、PHP composer/ca-bundle、各家 Linux 发行版);</li>
<li>有的干脆把自己的信任库随 JDK 一起分发。</li>
</ul>
<p>所以各生态的修复姿势出奇地一致:把 8 个被攻陷证书的 SPKI SHA-256 哈希写进拒绝名单,随紧急版本发出去。Go 团队甚至直接把名单硬编码进了 <code>crypto/x509</code>。</p>
<p>这也带来了这次事故最重要的关键词:<strong>重新构建</strong>。</p>
<p>升级工具链只是第一步。已经编译好的 Go 二进制里,旧的拒绝名单(或者根本没有名单)还在;已经发布出去的容器镜像里,旧的 <code>ca-certificates</code> 还在;已经分发到用户手里的 Electron 应用,框架层还是旧的。这些东西不会因为 你升级了开发机 就自动修复——它们必须重新构建、重新分发。</p>
<h2 id="各生态修复版本对照表">各生态修复版本对照表</h2>
<p>按 10 月 21 日到 22 日各生态的公告整理:</p>
<table>
<thead>
<tr>
<th>生态</th>
<th>修复版本</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>Go</td>
<td>1.27.3 / 1.26.10(CVE-2026-111943)</td>
<td><code>crypto/x509</code> 内置 8 个被攻陷证书的 SPKI 拒绝名单;<strong>已编译的二进制必须重新构建</strong></td>
</tr>
<tr>
<td>Node.js</td>
<td>22.23.4 / 24.22.1 / 26.12.1(10-21 安全版)</td>
<td>TLS 拒绝包含被拉黑公钥的证书链,并更新内置 CA 证书</td>
</tr>
<tr>
<td>Python</td>
<td>certifi 2026.10.19;pip 26.2.2;uv 0.13.4</td>
<td>从信任库移除 emSign G3 根</td>
</tr>
<tr>
<td>Rust</td>
<td>rustls-webpki 0.103.16;webpki-roots 1.0.10</td>
<td>更新编译进二进制的根证书数据</td>
</tr>
<tr>
<td>PHP</td>
<td>composer/ca-bundle 1.6.0 / 1.4.16</td>
<td>不再信任这 8 个中间 CA</td>
</tr>
<tr>
<td>Java</td>
<td>CVE-2026-91403(OpenJDK 10-21 通告)</td>
<td>升级 JDK,或替换 <code>blocked.certs</code>,或强制开启吊销检查</td>
</tr>
<tr>
<td>Electron</td>
<td>44.9.1 等</td>
<td>升级框架后应用需要<strong>重新打包分发</strong></td>
</tr>
<tr>
<td>curl / Linux 发行版</td>
<td>各家 ca-certificates 更新</td>
<td><strong>容器镜像需要重建</strong></td>
</tr>
<tr>
<td>Bun / FreeBSD</td>
<td>截至 10 月 21 日尚未发布修复</td>
<td>留意跟进,修了立刻升</td>
</tr>
</tbody>
</table>
<h2 id="java比想象中更需要动手的一环">Java:比想象中更需要动手的一环</h2>
<p>Java 这次值得单独拎出来讲,因为它的暴露面和处置方式都容易被忽略。</p>
<p>10 月 21 日,OpenJDK 发布官方通告(CVE-2026-91403,CVSS 9.1,问题出在 <code>javax.net.ssl</code>):JDK 自带的信任库里包含这些被攻陷的 eMudhra 中间 CA,所以独立安装的运行时会信任所有能链到它们——也就是能被伪造——的证书。受影响版本覆盖面很大:</p>
<ul>
<li>JDK 27.0.1、25.0.5、21.0.13、17.0.21、11.0.32.1、8u504 及更早版本。</li>
</ul>
<p>Oracle 同步发布了带外安全告警,10 月 22 日开始各家 CERT 陆续跟进通报。</p>
<p>OpenJDK 通告给了三条路,按优先级:</p>
<ol>
<li><strong>升级</strong>独立安装的 JDK 到修复版本;</li>
<li>暂时升不了的,用官方提供的 <code>blocked.certs</code> 文件替换 <code>$JDK_HOME/lib/security/blocked.certs</code>(JDK 8 里叫 <code>blacklisted.certs</code>),注意先备份自己加过的条目;</li>
<li>开启吊销检查作为缓解:<code>-Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true</code>,签名 JAR 用 <code>jarsigner -verify -revcheck -strict</code> 校验。</li>
</ol>
<p>两个容易踩的细节:用系统包管理器装的 JDK(Arch、Debian、Fedora、RHEL、Ubuntu 等)用的是操作系统证书库,更新系统的 <code>ca-certificates</code> 就够;但 <strong>FreeBSD 和 Homebrew 的构建要单独更新</strong>。另外,一台机器上装了几个 JDK 就要处理几遍,通告里特意强调了这句。</p>
<h2 id="nodejs-这批更新里还藏着一个-rce">Node.js 这批更新里还藏着一个 RCE</h2>
<p>如果你只盯着 eMudhra 事件,可能会漏掉一个对你更直接的风险:Node.js 10 月 21 日这批安全版一共修了约 10 个漏洞,其中 4 个 High,最重的一个不是证书问题,而是:</p>
<p><strong>CVE-2026-111497</strong>:Brotli 解码器越界写入,可导致远程代码执行,社区给它起了个和 React2Shell 呼应的名字——“Node2Shell”。三个 LTS 线全部受影响。这个漏洞由 OpenAI Codex Security 的研究员报告。</p>
<p>同一批还修了几个值得留意的问题:</p>
<ul>
<li>两个 TLS 证书主机名校验绕过(全角数字写的 IP、宽松解析到 IP 的主机名),High;</li>
<li><code>dns.resolve</code> 对含 NUL 字符的名称截断处理,High;</li>
<li><code>crypto.randomUUID()</code> / <code>randomInt()</code> 的熵缓存被存进了启动快照和 SEA——每个进程会重复输出相同的“随机”值,Medium。如果你的服务靠它生成 token 或 ID,这条不小;</li>
<li>keep-alive socket 处理乱序响应、非规范 IPv4 主机名的证书校验问题,等等。</li>
</ul>
<p>修复版本:22.23.4 / 24.22.1 / 26.12.1,按你所在的大版本线升级即可。所有已 EOL 的版本都受影响,没有侥幸选项。</p>
<h2 id="你现在应该做什么">你现在应该做什么</h2>
<p>按你的技术栈对号入座:</p>
<p><strong>所有人:</strong></p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 升级本地与 CI 的工具链(示例:Node)</span>
nvm install 24.22.1 &#x26;&#x26; nvm <span class="hljs-built_in">alias</span> default 24.22.1
</code></pre>
<p>然后把<strong>构建产物</strong>重新走一遍流水线:重新编译、重新打包、重新发布。容器镜像记得拉取新的基础镜像再 build,别命中旧 layer 的缓存。</p>
<p><strong>Go:</strong> 升级到 1.27.3 / 1.26.10 后,重新 <code>go build</code> 并重新部署所有服务。只更新 CI 里的 Go 版本而不重新编译发布,等于没修。</p>
<p><strong>Python:</strong> 升级 pip 26.2.2、certifi 2026.10.19、uv 0.13.4;CI 和容器里同理。</p>
<p><strong>Rust:</strong> <code>cargo update -p rustls-webpki -p webpki-roots</code>,然后重新发布。</p>
<p><strong>PHP:</strong> <code>composer update composer/ca-bundle</code>。</p>
<p><strong>Java:</strong> 按 OpenJDK 通告升级,或替换 <code>blocked.certs</code>;数一下机器上有几套 JDK。</p>
<p><strong>Electron 应用:</strong> 升级框架版本后重新打包、重新分发——已安装的旧包不受新版本保护。</p>
<p><strong>还没修的:</strong> Bun 和 FreeBSD 截至 10 月 21 日还没发布修复,留意官方渠道,修了第一时间升。</p>
<h2 id="本站是怎么自测的">本站是怎么自测的</h2>
<p>写这篇文章的当天,我们也把身边的门关上了:</p>
<ul>
<li>本地开发机的 Node 从 24.14.1(受影响)升级到 <strong>24.22.1</strong>(修复版);</li>
<li>仓库的 <code>.nvmrc</code> 从 22.22.2 升到 <strong>22.23.4</strong>,<code>engines.node</code> 同步升到 <code>>=22.23.4</code>,让每个clone 这个仓库的人拿到的都是修复后的工具链;</li>
<li>在新工具链上完整跑了一遍 <code>pnpm install</code>、<code>pnpm lint</code>、<code>pnpm typecheck</code>、<code>pnpm build</code>,全部通过。</li>
</ul>
<p>运行时方面,本站部署在 Cloudflare Workers 上,出站 TLS 的信任链由平台侧维护,不依赖本机的 ca-certificates;但本地开发和构建环境该升的照样要升——毕竟构建产物的信任边界是从开发机开始的。</p>
<p>整个过程中最直观的感受是:这类系统级升级,<code>pnpm audit</code> 之类的工具完全照不到。它只看你 package.json 里的依赖,不看你的工具链、基础镜像和 JDK。</p>
<h2 id="写在最后">写在最后</h2>
<p>这次事故我想记下三点。</p>
<p>第一,<strong>数字信任的底座是熵和流程</strong>。384 位的安全强度,因为一次仪式缺陷变成 3 到 8 位,差异是十几个数量级;而这个错误潜伏了 17 个月才被看见。PKI 体系里,“随机数不够随机”从来不是小概率的倒霉事,而是需要仪式、审计和验证去主动防守的单点。</p>
<p>第二,<strong>修复的单位从“升级”变成了“重建”</strong>。过去我们说“升级修复”,默认改的是源状态;这次,工具链、二进制、容器镜像、已分发的应用,每一层都要显式地重建一遍。供应链越深,重建清单越长。</p>
<p>第三,<strong>别把安全检查外包给单一工具</strong>。<code>audit</code>、<code>dependabot</code>、<code>renovate</code> 盯着的是应用依赖这薄薄一层;CA 信任库、语言工具链、系统证书包这些“更下面”的东西,目前还得靠人盯着公告。这波各生态从发现(10-17)到全线出修复版(一周内)的响应速度值得肯定,但前提是,你得看到公告。</p>
<p>趁弱密钥还没被滥用,升级吧。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://blog.talosintelligence.com/compromised-intermediate-certificates">Cisco Talos: Compromised eMudhra intermediate certificates</a></li>
<li><a href="https://nodejs.org/en/blog/vulnerability/october-2026-security-releases">Node.js: Wednesday, October 21, 2026 Security Releases</a></li>
<li><a href="https://openjdk.org/groups/vulnerability/advisories/2026-10-21">OpenJDK Vulnerability Advisory (CVE-2026-91403), 2026-10-21</a></li>
<li><a href="https://www.oracle.com/security-alerts/alert-cve-2026-91403.html">Oracle Security Alert Advisory for CVE-2026-91403</a></li>
<li><a href="https://thehackernews.com/2026/10/eight-emudhra-ca-keys-compromised.html">The Hacker News: Eight eMudhra CA Keys Compromised</a></li>
<li><a href="https://blog.openvpn.net/eight-compromised-certificate-authority-keys-what-happened-and-what-to-update-now">OpenVPN: What happened and what to update now</a></li>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2083449">Mozilla Bugzilla #2083449</a></li>
</ul>]]></content:encoded>
      <pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate>
      <category>tls</category>
      <category>pki</category>
      <category>ca</category>
      <category>emudhra</category>
      <category>nodejs</category>
      <category>go</category>
      <category>java</category>
      <category>供应链安全</category>
    </item>
    <item>
      <title><![CDATA[CSP + JS 完整性监控做进 InfluxDB 后，线上直接炸了]]></title>
      <link>https://hopes7.com/zh/blog/influxdb-cardinality-explosion-csp-monitoring</link>
      <guid>https://hopes7.com/zh/blog/influxdb-cardinality-explosion-csp-monitoring</guid>
      <description><![CDATA[千万月活 SaaS 的安全监控系统，选 InfluxDB 存储上报数据。测试环境 3 天上线秒过，上线后 Node 内存暴涨、查询卡死。根本原因：时序数据库不是这么用的。]]></description>
      <content:encoded><![CDATA[<h2 id="起因">起因</h2>
<p>前段时间我们想做一套前端安全监控：</p>
<ul>
<li>CSP（Content Security Policy）违规监控</li>
<li>自有 JS 完整性校验</li>
<li>第三方 JS hash 校验</li>
<li>风险脚本告警</li>
</ul>
<p>业务是 SaaS，多国家站点，所有站点加起来月活接近千万。</p>
<p>需求听上去不复杂：用户每次打开页面，检查 CSP 合规、校验 JS hash，异常就直接上报。然后做趋势分析、风险统计、实时监控、异常检索。</p>
<p>我们选了 InfluxDB。原因非常朴素：</p>
<p>"时序数据库快。"</p>
<p>老实说，当时我对时序数据库并不熟。但现在 AI 开发速度太快了——架构还没想清楚，系统已经跑起来了。</p>
<p>整个系统几乎都是 Codex 写的。数据结构、InfluxDB 写入、dashboard、聚合查询、前端 hash 校验、查询接口，全部 AI 出品。</p>
<p>第一版，3 天上线。测试环境，一切正常。</p>
<p>然后上线。</p>
<p>直接炸了。</p>
<h2 id="第一版内存聚合的灾难">第一版：内存聚合的灾难</h2>
<p>最开始我以为是查询写得太烂。</p>
<p>因为第一版的逻辑其实非常简单粗暴：把 raw data 全部拉出来，Node.js 内存聚合。</p>
<p>测试环境完全没问题。数据量小，用户少，cardinality 很低。</p>
<p>但线上根本不是一个世界。</p>
<p>上线大概 20 分钟，告警就开始响了：</p>
<ul>
<li>Node 内存暴涨</li>
<li>CPU 飙升</li>
<li>dashboard 卡死</li>
<li>查询越来越慢</li>
</ul>
<p>后来才发现，真正慢的不是 InfluxDB，而是我自己把 raw data 全拉出来在内存里算。</p>
<p>于是马上改第二版。</p>
<h2 id="第二版还是挂了">第二版：还是挂了</h2>
<p>这次做了几件事：</p>
<ul>
<li>尽量使用 InfluxDB 原生聚合函数</li>
<li>减少 Node 内存计算</li>
<li>增加原始查询优化</li>
<li>重新设计查询逻辑</li>
</ul>
<p>结果还是挂了。</p>
<p>而且这次更诡异——有些查询几分钟都查不出来。</p>
<p>这时候我开始怀疑：InfluxDB 是不是不行？</p>
<p>后来我才意识到，不是 InfluxDB 不行，而是：</p>
<p><strong>我在拿时序数据库做高维检索。</strong></p>
<h2 id="时序数据库到底解决什么问题">时序数据库到底解决什么问题</h2>
<p>很多人第一次接触 InfluxDB、Prometheus、OpenTSDB，都会有一个误区：觉得既然是数据库，应该也能像 MySQL 一样查数据。</p>
<p>但时序数据库真正解决的问题是：</p>
<blockquote>
<p>某个指标，随着时间变化是什么样子。</p>
</blockquote>
<p>它擅长的是：</p>
<ul>
<li>CPU 使用率变化趋势</li>
<li>错误率趋势</li>
<li>QPS 波动</li>
<li>时间桶聚合</li>
<li>监控指标告警</li>
</ul>
<p>也就是 <strong>Time Series Analysis</strong>。</p>
<p>而不是：</p>
<ul>
<li>任意条件组合检索</li>
<li>多维度筛选</li>
<li>分页查询</li>
<li>明细记录查某一条</li>
</ul>
<p>但我重新审视自己的需求后发现，我真正要做的是：</p>
<ul>
<li>查某个 userId</li>
<li>查某个 scriptUrl</li>
<li>查某个 hash</li>
<li>查某个 pageUrl</li>
<li>多条件组合筛选</li>
<li>风险排查</li>
<li>分页</li>
<li>明细检索</li>
</ul>
<p>这已经不是"时序分析"了，更像 <strong>风控系统</strong>、<strong>日志检索</strong>、<strong>OLTP 查询系统</strong>。</p>
<h2 id="真正的深渊series-cardinality">真正的深渊：Series Cardinality</h2>
<p>真正的问题，是在我开始理解 InfluxDB 里 tag 和 field 到底意味着什么的时候出现的。</p>
<p>最开始我以为：</p>
<ul>
<li>tag = 有索引</li>
<li>field = 普通字段</li>
</ul>
<p>后来发现完全不是这么回事。</p>
<p>InfluxDB 的 tag 不只是"有索引的字段"——它实际上是 <strong>series identity 的一部分</strong>。数据写入的时候，InfluxDB 就已经决定了 series structure。</p>
<p>这和 MySQL 完全不是一个世界。在 MySQL 里，今天没索引，明天 <code>ALTER TABLE ADD INDEX</code> 就行。但 InfluxDB 不是——tag 一旦设计错，后面通常意味着 backfill、rewrite、rebuild index，基本等于重建整个时序结构。</p>
<h3 id="75-亿-series">75 亿 Series</h3>
<p>然后我开始真正研究什么叫 <strong>series cardinality</strong>。</p>
<p>InfluxDB 会把 <code>measurement + tag set</code> 组合成一个唯一 series。</p>
<p>比如：</p>
<pre><code>measurement=csp_report
siteCode=us
env=prod
userId=123
scriptUrl=a.js
pageUrl=/checkout
hash=xxxx
</code></pre>
<p>这一整套就是一个 series。</p>
<p>换一个 <code>userId</code>，新的 series。换一个 <code>hash</code>，又是新的 series。</p>
<p>如果你熟悉 InfluxDB，看到 <code>userId</code> 和 <code>hash</code> 放进 tag，可能已经开始笑了。</p>
<p>然后我算了一下：</p>
<table>
<thead>
<tr>
<th>字段</th>
<th>数量</th>
</tr>
</thead>
<tbody>
<tr>
<td>siteCode</td>
<td>30</td>
</tr>
<tr>
<td>userId</td>
<td>100,000</td>
</tr>
<tr>
<td>hash</td>
<td>500</td>
</tr>
<tr>
<td>scriptUrl</td>
<td>50</td>
</tr>
<tr>
<td>pageUrl</td>
<td>100</td>
</tr>
</tbody>
</table>
<p>理论 series：</p>
<pre><code>30 × 100,000 × 500 × 50 × 100 = 7,500,000,000
</code></pre>
<p>75 亿。</p>
<p>我第一次算出来的时候，甚至怀疑自己算错了。重新按了几遍计算器，发现没错。就是 75 亿 series。</p>
<p>那一刻我突然意识到，InfluxDB 最恐怖的地方不是某个字段特别大，而是 <strong>多个"看起来不大"的维度，组合后直接指数爆炸</strong>。</p>
<h2 id="influxdb-最贵的资源不是数据量">InfluxDB 最贵的资源不是数据量</h2>
<p>更离谱的是，我们一天的数据其实只有 170 万 rows。</p>
<p>说实话，这个量级并不算夸张。MySQL 分表、ClickHouse、Elasticsearch，其实都还能打。</p>
<p>但问题在于，InfluxDB 最贵的资源从来不是数据量，而是 <strong>series cardinality</strong>。</p>
<p>很多人第一次接触 InfluxDB 时会误以为 series ≈ 数据量。其实完全不是。</p>
<p>series 更像 <strong>索引空间</strong>。InfluxDB 会为每一个 series：</p>
<ul>
<li>分配 series id</li>
<li>建立 inverted index</li>
<li>建立 metadata</li>
<li>建立 tag index</li>
</ul>
<p>所以真正吃资源的不是数据本身，而是 series metadata。</p>
<p>后来我才明白，为什么 InfluxDB 官方一直强调：不要把 userId、hash、uuid、requestId、random string 这种高离散字段放进 tag。因为这些字段会直接把 series cardinality 打爆。</p>
<h2 id="为什么查询越来越慢">为什么查询越来越慢</h2>
<p>高 cardinality 下，InfluxDB 查询不是简单的"扫数据"，而是 <strong>扫海量 series metadata</strong>。</p>
<p>尤其：</p>
<ul>
<li>tag filter</li>
<li>group by</li>
<li>series expansion</li>
</ul>
<p>这些操作会导致：</p>
<ul>
<li>index scan 成本急剧增加</li>
<li>shard traversal 跨分片扫描</li>
<li>mmap page fault 频繁触发</li>
</ul>
<p>这不是"优化查询"能解决的问题。这是底层架构选错了。</p>
<h2 id="最终方案influxdb--mysql-双存储">最终方案：InfluxDB + MySQL 双存储</h2>
<p>最后整个系统重新拆了。</p>
<p>InfluxDB 继续保留，但只负责它擅长的事：</p>
<ul>
<li>趋势分析</li>
<li>count / sum 聚合</li>
<li>TOP N 排行</li>
<li>时间桶聚合</li>
<li>告警趋势</li>
</ul>
<p>MySQL 负责检索：</p>
<ul>
<li>明细查询</li>
<li>userId 检索</li>
<li>hash 检索</li>
<li>多条件筛选</li>
<li>风险排查</li>
<li>分页</li>
</ul>
<p>也就是：</p>
<p><strong>InfluxDB 做趋势，MySQL 做检索。</strong></p>
<h3 id="最终的-tag--field-设计">最终的 Tag / Field 设计</h3>
<p>改造后的 InfluxDB measurement 只保留低基数的 tag：</p>
<pre><code># Tags（低基数，参与 series 定义）
siteCode    → 约 30 个值
env         → prod / staging / dev
type        → csp_violation / js_integrity / third_party_hash / risk_script

# Fields（高基数，不参与 series 定义）
reportCount → integer（聚合用）
riskLevel   → string（高/中/低）
</code></pre>
<p>所有高基数字段（userId、hash、scriptUrl、pageUrl）全部只写进 MySQL。InfluxDB 只关心"某种类型的异常，今天发生了多少次，趋势如何"。</p>
<p>写入层面做了双写：一条数据同时写 InfluxDB（聚合用）和 MySQL（明细用）。写入一致性靠应用层重试 + MySQL 事务保证，InfluxDB 少写几条对趋势影响不大。</p>
<p>后来我才真正理解，判断一个系统是不是适合时序数据库，不要看"数据里有没有时间字段"，而要看：</p>
<p><strong>你到底是在看趋势，还是在查记录。</strong></p>
<p>这是两个完全不同的问题。</p>
<h2 id="回头看">回头看</h2>
<p>整个项目其实特别像 AI 时代的软件开发缩影。</p>
<p>Codex 帮我在 3 天内完成了整个系统。测试环境秒过。开发速度快得离谱。</p>
<p>但它不会提醒我：</p>
<pre><code>30 × 100,000 × 500 × 50 × 100 = 7,500,000,000
</code></pre>
<p>AI 可以极大加速开发。但系统最终还是要遵守底层架构规律。</p>
<p><strong>速度再快，方向错了也是白搭。</strong></p>]]></content:encoded>
      <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
      <category>InfluxDB</category>
      <category>时序数据库</category>
      <category>CSP</category>
      <category>架构设计</category>
      <category>cardinality</category>
      <category>SaaS</category>
    </item>
    <item>
      <title><![CDATA[npm 包投毒越来越频发：TanStack 事件给前端供应链的一课]]></title>
      <link>https://hopes7.com/zh/blog/npm-supply-chain-poisoning-tanstack-2026</link>
      <guid>https://hopes7.com/zh/blog/npm-supply-chain-poisoning-tanstack-2026</guid>
      <description><![CDATA[这次 TanStack 事故最刺痛人的地方，不是某个 npm token 被偷了，而是攻击者把 CI/CD 里的几段“合理信任”串成了一条发布链路。]]></description>
      <content:encoded><![CDATA[<h2 id="先说结论">先说结论</h2>
<p>npm 包投毒这几年已经不再是“有人注册了一个相似包名”这么简单了。</p>
<p>2026 年 5 月 11 日，TanStack 生态里 42 个 <code>@tanstack/*</code> 包被发布了 84 个恶意版本。根据 Tanner Linsley 的事后说明，攻击发生在 19:20 到 19:26 UTC 之间，外部研究者大约 20 分钟后公开报告了异常。</p>
<p>最值得注意的一点是：这次不是传统意义上的 npm 账号被盗，也不是维护者把 token 泄露到了日志里。攻击者组合了三件事：</p>
<ol>
<li><code>pull_request_target</code> 触发的 “Pwn Request” 模式；</li>
<li>fork PR 到主仓库之间的 GitHub Actions cache poisoning；</li>
<li>在 release runner 里从内存提取 OIDC token，然后直接向 npm registry 发起发布请求。</li>
</ol>
<p>换句话说，攻击者没有偷走 npm token，却仍然借到了“合法发布”的身份。</p>
<p>这才是这类事件越来越让人不安的地方：供应链攻击正在从“偷凭证”变成“滥用自动化信任边界”。</p>
<h2 id="这不是-tanstack-一家的问题">这不是 TanStack 一家的问题</h2>
<p>我不太想把这件事写成“某个项目配置错了”的复盘。那样看起来很爽，但没什么帮助。</p>
<p>TanStack 是一个成熟、活跃、被大量开发者依赖的开源项目。它的发布链路已经比很多个人项目和公司内部包规范得多。正因为如此，这次事故反而更有警示意义：攻击者没有选择最笨的路径，而是在 CI/CD、缓存、OIDC、npm trusted publishing 之间寻找可以拼接的缝隙。</p>
<p>很多团队现在都有类似配置：</p>
<ul>
<li>fork PR 自动跑一些检查；</li>
<li>CI 缓存 pnpm / npm / yarn 目录；</li>
<li>release workflow 拥有更高权限；</li>
<li>npm trusted publishing 依赖 GitHub OIDC；</li>
<li>安装依赖时默认允许 lifecycle scripts 执行；</li>
<li>维护者维护很多包，发布频率很高。</li>
</ul>
<p>这些单独看都合理。</p>
<p>问题是，供应链攻击最擅长的不是攻破一扇厚门，而是把几扇半开的门连起来。</p>
<h2 id="攻击链里最值得记住的三段">攻击链里最值得记住的三段</h2>
<h3 id="第一段pull_request_target-的危险不在触发器本身">第一段：<code>pull_request_target</code> 的危险不在触发器本身</h3>
<p><code>pull_request_target</code> 的初衷并不邪恶。它常被用来给 PR 打标签、发表评论、更新元信息，因为它运行在 base repository 的上下文里，可以拿到普通 fork PR 拿不到的一些权限。</p>
<p>危险发生在另一个动作上：在 <code>pull_request_target</code> 里 checkout fork 提交并执行其中的代码。</p>
<p>这相当于一边说“我在可信仓库上下文里运行”，一边又把不可信代码拿进来执行。GitHub Security Lab 很早就把这类模式称为 “pwn request”。这次 TanStack 事件里，相关 workflow 试图做权限拆分，但仍然漏掉了 cache 这个更隐蔽的共享面。</p>
<p>很多人以为：</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
</code></pre>
<p>就能把风险压住。现实是，<code>actions/cache</code> 的 post-job save 并不简单受 <code>GITHUB_TOKEN</code> 这类权限配置约束。也就是说，即使 job 看起来是只读的，它仍可能把污染后的依赖缓存写回主仓库作用域。</p>
<h3 id="第二段缓存是性能优化也是信任边界">第二段：缓存是性能优化，也是信任边界</h3>
<p>缓存平时太“工具化”了，以至于我们很少把它当成安全边界。</p>
<p>但在现代前端项目里，缓存目录里可能放着：</p>
<ul>
<li>包管理器 store；</li>
<li>编译产物；</li>
<li>native binary；</li>
<li>postinstall / prepare 后生成的文件；</li>
<li>后续 workflow 会直接使用的依赖内容。</li>
</ul>
<p>这次攻击的核心就在这里：恶意 PR 运行时污染 pnpm store，并保存到一个 release workflow 后续会命中的 key。等真正的 main 分支发布流程启动，release runner 恢复了这个缓存，攻击代码就从“不可信 PR”穿越到了“可信发布环境”。</p>
<p>这一步很阴险，因为它不需要 PR 被合并。</p>
<p>PR 可以最后变成 0 文件改动，可以关闭，可以删除分支。缓存还在。</p>
<h3 id="第三段oidc-不是魔法护身符">第三段：OIDC 不是魔法护身符</h3>
<p>npm trusted publishing 和 GitHub OIDC 本来是为了减少长期 npm token 的暴露面。这个方向是对的：短期凭证比长期 token 好，自动 provenance 也比手工发布更可审计。</p>
<p>但这次事故提醒我们：OIDC 解决的是“不要把长期密钥放在 CI 里”，不是“CI 运行时被拿到代码执行以后仍然绝对安全”。</p>
<p>如果一个 release workflow 拥有 <code>id-token: write</code>，而攻击者已经在这个 runner 上获得代码执行能力，就可以尝试从运行环境里拿到可用于发布的 OIDC token。TanStack 事件里，恶意代码甚至绕过了 workflow 里正常的 “Publish Packages” step：测试失败后发布步骤没有执行，但恶意进程已经在其他阶段直接向 npm registry 发起了发布。</p>
<p>这也是我觉得最有教育意义的部分。</p>
<p>我们过去常问：“npm token 有没有泄露？”</p>
<p>现在还要问：“任何能拿到发布身份的 workflow，是否运行了不该信任的代码？”</p>
<h2 id="为什么-npm-包投毒越来越频发">为什么 npm 包投毒越来越频发</h2>
<p>因为 npm 生态天然有几个攻击者喜欢的特征。</p>
<p>第一，包的传播链很长。一个小包被污染，可能经由 transitive dependencies 扩散到很多项目。大多数使用者并不会逐个审查依赖 tarball。</p>
<p>第二，安装阶段能执行代码。<code>postinstall</code>、<code>prepare</code>、<code>preinstall</code> 这些 lifecycle scripts 在很多项目里是默认打开的。它们有正当用途，比如编译 native addon、生成 client、拉取二进制文件；也正因为有正当用途，完全禁用并不容易。</p>
<p>第三，维护者压力很大。很多关键包由少数人维护，一个人可能同时维护几十个包。攻击者只要拿到一次发布通道，就可能顺着 maintainer 关系继续扩散。</p>
<p>第四，CI/CD 越来越复杂。现代发布链路里有缓存、矩阵构建、preview、自动 release、provenance、OIDC、机器人评论、三方 action。每一层都在提升效率，也都在制造新的信任交界面。</p>
<p>第五，开源项目通常更透明。透明是开源的优势，但也意味着攻击者能公开研究 workflow、cache key、发布节奏和维护者习惯。</p>
<p>这不是说 npm 不安全，或者开源不安全。更准确地说：开源供应链已经变成高价值基础设施，而我们的防护习惯还停留在“别泄露 token”的阶段。</p>
<h2 id="普通开发者能做什么">普通开发者能做什么</h2>
<p>如果你在 2026 年 5 月 11 日安装过受影响的 TanStack 版本，官方建议把安装主机视为可能被攻陷，并轮换 AWS、GCP、Kubernetes、Vault、GitHub、npm、SSH 等可触达凭证。这个建议听起来重，但很合理：恶意 payload 的目标就是收集环境里的凭证。</p>
<p>对日常开发来说，我会把几个动作变成习惯。</p>
<h3 id="1-对异常版本保持敏感">1. 对异常版本保持敏感</h3>
<p>如果一个包突然连续发布多个 patch，尤其是在维护者没有明显 release note 的情况下，要多看一眼。</p>
<p>可以用这些命令做快速检查：</p>
<pre><code class="hljs language-bash">npm view @tanstack/history versions --json
npm diff --diff=@tanstack/history@1.161.8 --diff=@tanstack/history@1.161.9
</code></pre>
<p>不要指望每次都能人工审出恶意代码，但至少能看到异常文件、异常依赖、异常 lifecycle scripts。</p>
<h3 id="2-把-lockfile-当成安全资产">2. 把 lockfile 当成安全资产</h3>
<p><code>pnpm-lock.yaml</code>、<code>package-lock.json</code>、<code>yarn.lock</code> 不只是“保证安装一致”的文件，它们也是供应链审计入口。</p>
<p>事故发生后，第一步不是只看 <code>package.json</code>，而是查 lockfile 里到底解析到了哪些版本、哪些 tarball、哪些 GitHub dependency。</p>
<h3 id="3-对-install-scripts-采用更细的允许策略">3. 对 install scripts 采用更细的允许策略</h3>
<p>完全禁用 scripts 在很多项目里不现实，但默认全信也越来越危险。</p>
<p>在 CI 的某些阶段，可以考虑：</p>
<pre><code class="hljs language-bash">pnpm install --ignore-scripts
</code></pre>
<p>如果项目确实需要构建脚本，就把“哪些包允许执行构建脚本”变成显式策略，而不是把整个安装过程交给默认行为。pnpm 新版本的 build approval 机制就是朝这个方向走的。</p>
<h3 id="4-ci-里不要放过大的横向权限">4. CI 里不要放过大的横向权限</h3>
<p>安装依赖的 job、跑测试的 job、发布包的 job，最好不要都能摸到同一批云凭证和发布身份。</p>
<p>一个比较朴素的原则是：能不放在 install/test 环境里的 secret，就不要放；能只在 publish job 打开的权限，就不要提前打开。</p>
<h2 id="维护者更该盯住哪里">维护者更该盯住哪里</h2>
<p>如果你维护 npm 包，尤其是有自动发布流程的包，这次事故至少值得检查这几件事。</p>
<h3 id="1-全仓库搜索-pull_request_target">1. 全仓库搜索 <code>pull_request_target</code></h3>
<p>先不要争论“我们应该不用它”。可以用，但要非常克制。</p>
<p>安全用法通常是：</p>
<ul>
<li>只读 PR 元信息；</li>
<li>打标签；</li>
<li>评论；</li>
<li>不 checkout fork 代码；</li>
<li>不运行 fork 代码；</li>
<li>不共享会被 release workflow 复用的缓存。</li>
</ul>
<p>一旦需要跑 fork 代码，优先用 <code>pull_request</code>，并接受第一次贡献者审批、低权限 token、隔离缓存这些约束。</p>
<h3 id="2-不要让不可信-pr-写入生产缓存">2. 不要让不可信 PR 写入生产缓存</h3>
<p>cache key 不是随手写的字符串，它决定了哪些 job 会共享状态。</p>
<p>对 fork PR，可以考虑：</p>
<ul>
<li>禁止保存缓存，只允许读取非常低风险的缓存；</li>
<li>cache key 加上 PR scope，避免命中 main/release；</li>
<li>release workflow 使用独立 cache namespace；</li>
<li>定期清理 cache；</li>
<li>在事故响应预案里加入“全仓库 cache purge”。</li>
</ul>
<h3 id="3-发布身份只在最后一刻出现">3. 发布身份只在最后一刻出现</h3>
<p><code>id-token: write</code> 不应该像空气一样存在于整条 workflow 里。</p>
<p>更稳的做法是把发布拆成独立 job：只有测试、构建、包内容校验都通过后，才进入拥有 OIDC 发布权限的步骤。这个 job 不应该恢复来自不可信上下文的缓存，也不应该重新执行可能被污染的 install scripts。</p>
<p>这不是绝对安全，但它能减少攻击者“早早拿到发布身份”的机会。</p>
<h3 id="4-固定-third-party-actions">4. 固定 third-party actions</h3>
<p>浮动 ref 很方便，比如 <code>@main</code>、<code>@v5</code>。但对 release workflow 来说，方便往往意味着你把供应链的一部分交给了别人实时变化的分支。</p>
<p>关键路径上的 action 尽量 pin 到 commit SHA。升级可以慢一点，但边界要清楚一点。</p>
<h3 id="5-监控自己的发布而不是等别人发现">5. 监控自己的发布，而不是等别人发现</h3>
<p>TanStack 这次很幸运：外部研究者发现得非常快，社区响应也非常快。</p>
<p>但“被别人提醒”不应该是唯一报警机制。</p>
<p>维护者可以监控：</p>
<ul>
<li>是否出现非预期版本发布；</li>
<li>package manifest 是否新增异常 <code>optionalDependencies</code>；</li>
<li>tarball 是否出现异常大文件；</li>
<li>是否出现未知 GitHub dependency；</li>
<li>npm publish provenance 是否来自预期 workflow；</li>
<li>发布后的下载量和安装错误是否异常。</li>
</ul>
<p>这类监控不一定昂贵。哪怕只是一个发布后检查脚本，也比完全没有强。</p>
<h2 id="这件事给我的真实提醒">这件事给我的真实提醒</h2>
<p>读完整个复盘，我脑子里一直有一句话：我们把太多“自动化成功”误认为“安全”。</p>
<p>CI 绿了，发布成功了，provenance 有了，trusted publishing 配好了，cache 命中了，所有东西都看起来像现代工程最佳实践。</p>
<p>但攻击者看的不是这些标签。</p>
<p>攻击者看的是：</p>
<ul>
<li>哪段代码能执行；</li>
<li>哪个缓存会被复用；</li>
<li>哪个 job 拥有更高权限；</li>
<li>哪个 token 会在运行时出现；</li>
<li>哪个维护者来不及手工审查；</li>
<li>哪个包的安装脚本会被默认信任。</li>
</ul>
<p>这就是供应链安全最麻烦的地方。它不是一个单点漏洞，而是一串合理设计之间的组合风险。</p>
<h2 id="写在最后">写在最后</h2>
<p>我不认为答案是“不要用 npm”。</p>
<p>也不认为答案是“不要用 OIDC”。</p>
<p>更不认为答案是“所有开源项目都应该像大型安全团队一样运转”。这不现实，也不公平。</p>
<p>但我们确实需要更新心智模型：npm 包投毒已经不只是包管理器问题，而是 CI/CD、身份、缓存、发布治理和维护者负载共同组成的问题。</p>
<p>以后看到一个自动发布流程，我会先问三个问题：</p>
<ol>
<li>不可信代码有没有机会影响可信发布环境？</li>
<li>缓存有没有跨越 fork、main、release 的信任边界？</li>
<li>发布身份是不是只在真正发布的最后一步才出现？</li>
</ol>
<p>如果这三个问题回答不清楚，供应链风险就不只是理论上的。</p>
<p>开源生态靠信任运转。但到了 2026 年，信任不能只靠“大家都是好人”。它还需要边界、证据、审计和一点点不厌其烦的怀疑。</p>
<p>这可能就是 npm 包投毒事件越来越频发以后，前端工程师必须补上的一门课。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://github.com/TanStack/router/issues/7383">TanStack/router tracking issue #7383</a></li>
<li><a href="https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx">GitHub Security Advisory GHSA-g7cv-rxg3-hmpx</a></li>
<li><a href="https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/">GitHub Security Lab: Preventing pwn requests</a></li>
<li><a href="https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/">Adnan Khan: The Monsters in Your Build Cache</a></li>
<li><a href="https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised">StepSecurity: tj-actions/changed-files action is compromised</a></li>
</ul>]]></content:encoded>
      <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
      <category>npm</category>
      <category>供应链安全</category>
      <category>github-actions</category>
      <category>tanstack</category>
      <category>oidc</category>
      <category>ci-cd</category>
    </item>
    <item>
      <title><![CDATA[AI 时代的软件开发：从职业焦虑，到重新理解人的判断力]]></title>
      <link>https://hopes7.com/zh/blog/ai-era-software-development-human-judgment</link>
      <guid>https://hopes7.com/zh/blog/ai-era-software-development-human-judgment</guid>
      <description><![CDATA[读完一篇关于 AI 时代软件开发生产关系变化的文章后，我重新思考了程序员、产品、系统与人的判断力之间的关系。]]></description>
      <content:encoded><![CDATA[<h2 id="先说原文">先说原文</h2>
<p>这篇记录的触发点，是 Swill 的文章：<a href="https://swill.fun/blog/ai-era-software-development">《AI 时代的软件开发：这一代程序员的没落，还是新生产关系的诞生？》</a>。</p>
<p>我不是想复述原文，也不准备把那篇文章搬运一遍。它更像一块石头，砸进了一个已经有点松动的地方：AI 对软件行业的影响，可能并不是“程序员会不会失业”这么简单。</p>
<p>更准确地说，它让我重新想了一遍三个问题：</p>
<ol>
<li>当 AI 能写越来越多代码之后，软件开发到底还剩下什么是人的价值？</li>
<li>如果很多执行型工作被压缩，一个工程师应该把能力迁移到哪里？</li>
<li>在复杂系统里，真正稀缺的到底是更快的产出，还是更稳定的判断？</li>
</ol>
<p>这篇文章就是这次思考的整理。</p>
<h2 id="不是没落或新生而是同时发生">不是没落或新生，而是同时发生</h2>
<p>“AI 会不会让程序员没落”这个问题，问法本身有点太整齐了。</p>
<p>现实更可能是：一部分工作会明显贬值，另一部分能力会突然变贵；一部分人会感觉自己被时代推着往后退，另一部分人会第一次拥有过去只有团队和公司才有的生产力。</p>
<p>低层实现、重复编码、文档整理、需求搬运、样板代码生成，这些工作会越来越难单独构成护城河。不是因为它们不重要，而是因为它们的边际成本正在被 AI 压低。</p>
<p>但另一类工作反而更重要：</p>
<ul>
<li>定义真正的问题</li>
<li>判断需求值不值得做</li>
<li>设计系统边界</li>
<li>识别安全和合规风险</li>
<li>验证 AI 生成结果是否可靠</li>
<li>在复杂业务里做取舍</li>
<li>编排多个工具、模型和 agent 的工作流</li>
</ul>
<p>过去，一个工程师的价值常常被“能不能实现”来衡量。现在，“能不能判断什么该实现、如何验证、出了问题怎么收束”，正在变成更核心的能力。</p>
<p>这不是单纯的技术升级，而是角色内涵在换壳。</p>
<h2 id="从写代码的人到组织复杂系统的人">从写代码的人，到组织复杂系统的人</h2>
<p>过去的软件开发有一个默认前提：人是主要生产者，工具只是辅助。IDE、框架、CI/CD、云平台，都是为了让工程师更高效地完成自己的工作。</p>
<p>AI 出现以后，这个关系开始倒过来。</p>
<p>越来越多时候，人不再是一行一行完成实现的人，而是：</p>
<ul>
<li>给 AI 提供上下文的人</li>
<li>设定约束和边界的人</li>
<li>拆解任务并检查结果的人</li>
<li>决定是否接受输出的人</li>
<li>在系统跑偏时踩刹车的人</li>
</ul>
<p>这让我想到一个很不舒服但很真实的变化：未来的软件开发者，可能不再以“写了多少代码”来证明自己，而是以“组织了多少可靠产出”来证明自己。</p>
<p>代码仍然重要。底层能力也仍然重要。一个完全不懂工程的人，很难长期驾驭 AI 生成的复杂系统。</p>
<p>但只会执行的人会变得危险。因为 AI 本身就是一个超级执行器。人如果还停留在“接任务、写代码、交付”的单线流程里，就会被拿来和机器比速度，而这是一个很吃亏的比较。</p>
<p>更好的位置，可能是做一个系统型的人：</p>
<ul>
<li>知道业务目标和技术实现之间的张力</li>
<li>能把模糊问题变成可验证任务</li>
<li>能用工程治理控制复杂度</li>
<li>能让 AI workflow 变得可复现、可审计、可回滚</li>
<li>能看见工具没有看见的风险</li>
</ul>
<p>换句话说，AI 时代更需要的不是“更像机器的人”，而是更会组织机器的人。</p>
<h2 id="真正让我在意的是判断力">真正让我在意的是“判断力”</h2>
<p>读完那篇文章后，我最强烈的感受不是焦虑，而是一个更深的提醒：在生产力暴涨的时代，判断力会被放大，失误也会被放大。</p>
<p>一个人过去写错一段代码，影响可能只是一个模块。</p>
<p>一个人现在用 AI 批量生成方案、改动系统、编排 agent、自动执行 workflow，错误可能会以更快速度扩散。</p>
<p>这也是为什么我越来越觉得，AI 时代的软件问题不只是“工程问题”，还是“行为问题”。</p>
<p>人在复杂系统里并不总是理性的。我们会被速度诱惑，会因为模型答得流畅就降低警惕，会在 deadline 下跳过验证，会把“看起来能跑”误认为“真的可靠”。</p>
<p>这跟很多高风险场景是相通的：</p>
<table>
<thead>
<tr>
<th>场景</th>
<th>真正危险的地方</th>
</tr>
</thead>
<tbody>
<tr>
<td>工程变更</td>
<td>不是改代码，而是误判影响面</td>
</tr>
<tr>
<td>DevOps 操作</td>
<td>不是命令本身，而是缺少回滚意识</td>
</tr>
<tr>
<td>安全治理</td>
<td>不是漏洞存在，而是错误信任</td>
</tr>
<tr>
<td>AI agent</td>
<td>不是 agent 会执行，而是它可能执行错方向</td>
</tr>
<tr>
<td>团队协作</td>
<td>不是观点不同，而是情绪污染判断</td>
</tr>
<tr>
<td>高波动决策</td>
<td>不是波动本身，而是人在压力下偏离计划</td>
</tr>
</tbody>
</table>
<p>所以我真正感兴趣的问题，逐渐从“怎么做一个功能”变成了：</p>
<blockquote>
<p>人在复杂系统中，如何保持清醒、稳定判断，并避免失控？</p>
</blockquote>
<p>这个问题比“AI 会不会替代程序员”更贴近我现在的感受。</p>
<h2 id="深场景不是功能而是长期行为">深场景不是功能，而是长期行为</h2>
<p>很多 AI 产品看起来很有用，但又很容易被大平台覆盖。</p>
<p>比如总结、翻译、写邮件、生成 PPT、写一段代码。这些都是功能场景，价值明确，但边界也很薄。只要大模型入口足够强，它们就会逐渐变成默认能力。</p>
<p>真正有价值的深场景，往往不是“帮我完成一个动作”，而是“陪我长期改善一种行为”。</p>
<p>它通常有几个特征：</p>
<ul>
<li>高频出现</li>
<li>长期积累</li>
<li>情绪密度高</li>
<li>决策复杂</li>
<li>结果反馈滞后</li>
<li>人容易反复犯同一种错</li>
</ul>
<p>比如，一个团队为什么总在相同协作节点上失控；一个工程系统为什么总在上线前暴露治理问题；一个人为什么明明知道计划，却在压力下做出相反动作。</p>
<p>这些问题不是一次聊天就能解决的。</p>
<p>因为真正的价值不在“回答”，而在：</p>
<ul>
<li>长期上下文</li>
<li>过程记录</li>
<li>行为模式识别</li>
<li>及时提醒</li>
<li>复盘反馈</li>
<li>约束机制</li>
</ul>
<p>这也是我对 AI 产品方向的一个新理解：不要急着把 AI 包成一个漂亮入口，而要先找到一个足够真实、足够长期、足够痛的行为场景。</p>
<h2 id="不急着做产品先成为观察者">不急着做产品，先成为观察者</h2>
<p>人很容易把“开始做产品”理解成包装概念、整理视觉、搭建外壳、补齐后台、设计商业化路径。</p>
<p>这些当然都重要，但它们也很容易变成伪进展。</p>
<p>真正难的是：你是否真的理解那个场景里的人？你是否见过足够多真实样本？你是否知道用户在什么时刻偏离计划、在什么情绪下自我合理化、在什么压力下放弃原本的判断？</p>
<p>如果没有这些，产品就很容易变成一套漂亮但空心的功能。</p>
<p>所以我现在更相信一种慢一点的路径：</p>
<p>先记录，后总结；先观察，后抽象；先理解行为，再设计系统。</p>
<p>不是每天写流水账，而是记录关键行为事件：</p>
<ul>
<li>什么时候做了决定</li>
<li>当时掌握了什么信息</li>
<li>真正驱动自己的原因是什么</li>
<li>有没有偏离原计划</li>
<li>偏离时用了什么理由说服自己</li>
<li>结果出来以后，自己是否还愿意承认当时的状态</li>
</ul>
<p>这类记录一开始可能很粗糙，甚至有点不体面。因为它会暴露很多不想承认的东西：焦虑、贪心、不甘、侥幸、想证明自己、明知道不该做但还是做了。</p>
<p>但恰恰是这些部分，才是最有价值的数据。</p>
<p>系统不是从完美理性的人开始设计的。好的系统，应该承认人会疲惫、会冲动、会自我合理化，然后给这些时刻预留护栏。</p>
<h2 id="ai-时代的稀缺能力可能更像自我治理">AI 时代的稀缺能力，可能更像“自我治理”</h2>
<p>如果说过去的工程能力更多是“把事情做出来”，那 AI 时代的工程能力会越来越接近“让事情可控地发生”。</p>
<p>这个差别很大。</p>
<p>“做出来”强调产出。</p>
<p>“可控地发生”强调边界、验证、节奏、风险和反馈。</p>
<p>未来很多人都能用 AI 做东西。真正拉开差距的，可能是这些能力：</p>
<ul>
<li>能不能不被速度冲昏头</li>
<li>能不能在输出很顺的时候仍然保持怀疑</li>
<li>能不能为 AI workflow 设置检查点</li>
<li>能不能承认自己判断受情绪影响</li>
<li>能不能在复杂系统里留下可追溯的记录</li>
<li>能不能把一次失败变成下一次更好的约束</li>
</ul>
<p>这听起来不像传统意义上的“编程能力”，但它会越来越像 AI 时代的核心工程能力。</p>
<p>因为 AI 放大的不只是生产力，也会放大人的盲区。</p>
<p>一个混乱的人，拿到更强的工具，可能只是更快地制造混乱。一个有判断、有边界、有复盘能力的人，才可能把 AI 变成真正的杠杆。</p>
<h2 id="写在-2026-年-5-月-11-日">写在 2026 年 5 月 11 日</h2>
<p>今天回头看，AI 时代的软件开发给我的触动，不是“我还要不要继续写代码”，而是“我应该把自己训练成什么样的人”。</p>
<p>答案暂时还不完整，但方向更清楚了一点：</p>
<p>我不想只做一个更快的执行者。</p>
<p>我更想成为一个能理解复杂系统、能组织 AI、能识别风险、也能观察人的行为偏差的人。</p>
<p>这条路可能没有那么直，也不像某个热门产品方向那样容易包装。但它更接近我现在真正关心的问题：</p>
<blockquote>
<p>当工具越来越强，人如何不被自己的情绪、速度和幻觉带偏？</p>
</blockquote>
<p>也许 AI 时代真正重要的能力，不是永远保持正确，而是在复杂系统里持续校准自己。</p>
<p>毕竟，AI 可以帮我们生成很多东西。</p>
<p>但它不会替我们承担判断的后果。</p>]]></content:encoded>
      <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
      <category>AI辅助开发</category>
      <category>职业转型</category>
      <category>系统思维</category>
      <category>行为观察</category>
      <category>风险管理</category>
    </item>
    <item>
      <title><![CDATA[把 Next 博客迁到 Cloudflare Workers：一次真实的升级和补锅记录]]></title>
      <link>https://hopes7.com/zh/blog/next16-workers-strict-content-pipeline</link>
      <guid>https://hopes7.com/zh/blog/next16-workers-strict-content-pipeline</guid>
      <description><![CDATA[升级框架不难，难的是看清哪些事情应该发生在构建期，哪些事情绝对不能留到 Cloudflare Workers 运行时。]]></description>
      <content:encoded><![CDATA[<h2 id="先说结论">先说结论</h2>
<p>这次迁移表面上是一次技术栈升级：Node.js 22.22.2、pnpm 11、Next 16、React 19、TypeScript strict、Biome、Zod，再加上 OpenNext 和 Wrangler，把一个博客部署到 Cloudflare Workers。</p>
<p>但真正值得记下来的，不是版本号。</p>
<p>真正的问题有两个：</p>
<ol>
<li>Cloudflare Workers 运行时不能假设项目目录还在那里，所以不能在请求时 <code>readdir content/posts</code>。</li>
<li>Workers 禁止字符串代码生成，所以不能在请求时用 <code>next-mdx-remote/rsc</code> 的 <code>compileMDX</code>。</li>
</ol>
<p>这两个点都指向同一个原则：内容型网站尽量把内容发现、frontmatter 校验、Markdown/MDX 编译放到构建期。运行时应该拿已经准备好的数据渲染，而不是临时扫文件、临时编译文章。</p>
<h2 id="升级本身其实挺顺">升级本身其实挺顺</h2>
<p>项目最后固定成了这一套：</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"engines"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"node"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">">=22.22.2"</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"packageManager"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"pnpm@11.0.9"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>核心版本也到了新的主线：</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"next"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"16.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"react"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"19.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"react-dom"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"19.2.6"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"typescript"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"^6.0.3"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"@biomejs/biome"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.4.14"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"zod"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"^4.4.3"</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>TypeScript 继续开 <code>strict: true</code>，lint 交给 Biome：</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"scripts"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"lint"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"biome check ."</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"typecheck"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"tsc --noEmit"</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>这些改动不算刺激，但它们很重要。依赖升级以后，项目需要一个稳定的基本盘：同一个 Node 版本、同一个包管理器、同一个格式和 lint 入口。否则后面排查部署问题时，很容易分不清到底是代码问题、依赖问题，还是环境问题。</p>
<h2 id="第一处坑cloudflare-部署不是直接跑-next-build">第一处坑：Cloudflare 部署不是直接跑 next build</h2>
<p>最早的部署日志里，<code>next build</code> 已经成功了，但 <code>npx wrangler deploy</code> 进入了一个自动迁移流程：它发现这是 Next 项目，于是想安装 Wrangler、创建配置、跑 OpenNext migrate。</p>
<p>这在本地交互式终端里可能没问题，但在 Cloudflare 的非交互构建环境里就很危险。pnpm 11 又正好更严格，遇到了这个错误：</p>
<pre><code class="hljs language-text">ERR_PNPM_IGNORED_BUILDS Ignored build scripts: workerd
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.
</code></pre>
<p>部署环境不能停下来等你手动选。这个问题的处理方式也很明确：不要让部署命令临时“猜”项目该怎么部署，把配置提交进仓库。</p>
<p>现在项目里有明确的 <code>wrangler.jsonc</code>：</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"hopes"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"main"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">".open-next/worker.js"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"compatibility_date"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2026-05-09"</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"compatibility_flags"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"nodejs_compat"</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"assets"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"binding"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"ASSETS"</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"directory"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">".open-next/assets"</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">"observability"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"logs"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"enabled"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
      <span class="hljs-attr">"invocation_logs"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
    <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"traces"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">"enabled"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre>
<p>OpenNext 的配置也很薄：</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { defineCloudflareConfig, <span class="hljs-keyword">type</span> <span class="hljs-title class_">OpenNextConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opennextjs/cloudflare'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  ...<span class="hljs-title function_">defineCloudflareConfig</span>(),
  <span class="hljs-attr">buildCommand</span>: <span class="hljs-string">'pnpm next:build'</span>,
} <span class="hljs-keyword">satisfies</span> <span class="hljs-title class_">OpenNextConfig</span>
</code></pre>
<p>这里的思路是：构建过程由项目自己定义，Wrangler 只负责部署已经生成好的 Worker。</p>
<h2 id="第二处坑workers-里没有你的-contentposts-目录">第二处坑：Workers 里没有你的 content/posts 目录</h2>
<p>第一次部署后，页面访问失败，日志里有一句很直接：</p>
<pre><code class="hljs language-text">no such file or directory, readdir '/bundle/content/posts'
</code></pre>
<p>这句话基本把问题说完了。原来的内容模块在运行时读取 <code>content/posts</code>，本地 Node 环境当然没问题；但到了 Workers bundle 里，请求时已经没有那个源码目录可以扫了。</p>
<p>于是内容索引被前移到构建期：</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> files = (<span class="hljs-keyword">await</span> fs.<span class="hljs-title function_">readdir</span>(postsDir))
  .<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">filename</span> =></span> filename.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'.md'</span>) || filename.<span class="hljs-title function_">endsWith</span>(<span class="hljs-string">'.mdx'</span>))
  .<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> a.<span class="hljs-title function_">localeCompare</span>(b))
</code></pre>
<p>脚本读取 MDX 文件、解析 frontmatter 和正文，然后写入：</p>
<pre><code class="hljs language-text">lib/generated/content-index.json
</code></pre>
<p>运行时只 import 这个 JSON：</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> generatedContentPosts <span class="hljs-keyword">from</span> <span class="hljs-string">'./generated/content-index.json'</span>
</code></pre>
<p>这个变化让部署稳定了很多。它不是一个华丽改造，但边界变清楚了：文件系统属于构建期，Worker 请求属于运行时。</p>
<h2 id="第三处坑mdx-编译里藏着-new-function">第三处坑：MDX 编译里藏着 new Function</h2>
<p>修完 <code>readdir</code> 后，文章列表能打开，但所有文章详情页还是 500。Cloudflare 日志给了关键线索：</p>
<pre><code class="hljs language-text">Code generation from strings disallowed for this context
at new Function (&#x3C;anonymous>)
at compileMDX
</code></pre>
<p>这一步挺容易误判。因为本地 Next dev 里文章详情页是正常的，<code>next build</code> 也能过。问题只在 Workers 运行时暴露，因为 Workers 不允许 <code>eval</code> / <code>new Function</code> 这类字符串代码生成。</p>
<p>当时详情页里还在请求时调用：</p>
<pre><code class="hljs language-ts"><span class="hljs-title function_">compileMDX</span>(...)
</code></pre>
<p>所以每次访问文章，Worker 都会临时编译 MDX，然后撞上运行时限制。</p>
<p>最后的修法还是同一个方向：继续把事情前移。<code>content:generate</code> 不只生成 frontmatter 和正文，也把文章内容提前转成 HTML：</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> markdownProcessor = <span class="hljs-title function_">remark</span>()
  .<span class="hljs-title function_">use</span>(remarkGfm)
  .<span class="hljs-title function_">use</span>(remarkToc, { <span class="hljs-attr">heading</span>: <span class="hljs-string">'h2'</span> })
  .<span class="hljs-title function_">use</span>(remarkRehype)
  .<span class="hljs-title function_">use</span>(rehypeSlug)
  .<span class="hljs-title function_">use</span>(rehypeHighlight)
  .<span class="hljs-title function_">use</span>(rehypeStringify)
</code></pre>
<p>详情页不再运行时编译 MDX，而是渲染构建期生成的 <code>contentHtml</code>。同时移除了不再使用的 <code>next-mdx-remote</code>，避免以后又不小心把同一个问题带回来。</p>
<p>这一步以后，Worker preview 里的博客列表、旧文章详情页、新文章详情页都能正常返回 200。</p>
<h2 id="顺手补上的内容规范">顺手补上的内容规范</h2>
<p>这次还补了几篇文章的 <code>publishedAt</code> frontmatter，并把规则写进了 <code>BLOG.md</code>。</p>
<p>原因也很实际：内容索引读取的是 <code>publishedAt</code>。如果文章只有 <code>date</code>，就可能出现 frontmatter 校验失败，文章也可能不进入列表。</p>
<p>现在至少有几条规则是明确的：</p>
<ul>
<li><code>publishedAt</code> 必须写，推荐 <code>"YYYY-MM-DD"</code>。</li>
<li><code>locale</code> 只能是 <code>"zh"</code> 或 <code>"en"</code>。</li>
<li><code>categories</code> 和 <code>tags</code> 用字符串数组。</li>
<li>文件名建议是 <code>&#x3C;slug>.&#x3C;locale>.mdx</code>。</li>
</ul>
<p>这些规则写给人看，也写给未来的自动化流程看。内容站最怕“看起来只是少了一个字段”，最后排查半天才发现列表没收录。</p>
<h2 id="最后验证了什么">最后验证了什么</h2>
<p>最终我用这些命令收口：</p>
<pre><code class="hljs language-bash">pnpm lint
pnpm typecheck
pnpm build
npx wrangler deploy --dry-run
</code></pre>
<p>另外还用 OpenNext 的本地 Worker preview 直接访问了几个路径：</p>
<pre><code class="hljs language-text">/zh/blog
/zh/blog/multica-shared-human-ai-board
/en/blog/multica-shared-human-ai-board
</code></pre>
<p>要看的不是页面“应该可以”，而是 Workers 运行时真的不会再报：</p>
<pre><code class="hljs language-text">Code generation from strings disallowed for this context
</code></pre>
<h2 id="这次真正学到的东西">这次真正学到的东西</h2>
<p>我以前也知道 Cloudflare Workers 不是普通 Node 服务器，但这次迁移把这个差异压得很实。</p>
<p>在 Node 里很自然的事情，比如扫目录、读本地文件、请求时编译 MDX，到了 Workers 上就要重新判断：它是构建期工作，还是运行时工作？</p>
<p>我的经验是，内容型博客可以尽量保守一点：</p>
<ul>
<li>构建期扫描文章。</li>
<li>构建期校验 frontmatter。</li>
<li>构建期编译 Markdown/MDX。</li>
<li>运行时只读 bundle 里的数据。</li>
</ul>
<p>这会少一些“动态”的弹性，但换来的是更少的线上惊喜。对一个博客来说，这笔交易挺划算。</p>
<p>最后还有个小提醒：迁移成功不代表文章和样式就自然好看。渲染方式从 MDX React components 换成 HTML 后，原来组件里的 Tailwind class 不会自动跟过去，<code>.prose</code> 样式也要跟着补齐。</p>
<p>框架升级是一半，内容体验是另一半。博客嘛，最终还是要让人愿意读。</p>]]></content:encoded>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
      <category>nextjs</category>
      <category>react</category>
      <category>typescript</category>
      <category>cloudflare-workers</category>
      <category>opennext</category>
      <category>mdx</category>
      <category>pnpm</category>
    </item>
    <item>
      <title><![CDATA[Multica：把人和 Agent 放到同一块看板上]]></title>
      <link>https://hopes7.com/zh/blog/multica-shared-human-ai-board</link>
      <guid>https://hopes7.com/zh/blog/multica-shared-human-ai-board</guid>
      <description><![CDATA[从自托管部署、Runtime 多机调度到 Skills 复用和 CLI-first 协作，记录我试用 Multica 后最在意的几个判断，以及它为什么更像一笔基础设施投资。]]></description>
      <content:encoded><![CDATA[<h1 id="multica把人和-agent-放到同一块看板上">Multica：把人和 Agent 放到同一块看板上</h1>
<h2 id="我为什么会认真看-multica">我为什么会认真看 Multica</h2>
<p>最近被安利去试了下 Multica。老实说，一开始我并没有把它当成什么“下一代 AI 协作平台”来看，我的预期其实很低：大概率又是一个把聊天、任务、自动化脚本揉在一起的壳。</p>
<p>但真正跑起来之后，我对它的判断变了。</p>
<p>它最吸引我的地方，不是“能调很多模型”，也不是“能派很多 Agent”，而是它把<strong>人和 Agent 放进了同一块任务看板</strong>。这个抽象听起来非常普通，甚至有点不性感，可它恰好踩中了一个现实问题：今天我们用 Agent 干活，最麻烦的往往不是模型能力，而是协作界面。</p>
<p>人怎么提需求，Agent 怎么接任务，结果怎么回流，谁来做最终判断，出错以后怎么留痕。这些东西如果没有统一界面，最后还是会退回到一堆聊天窗口、一堆脚本和一堆“你帮我看下这个”的手工协作。</p>
<p>我把它部署到了家里的 Mac mini M4 上，认真跑了几轮之后，越来越觉得：Multica 现在未必已经很好用，但方向可能是对的。</p>
<h2 id="它到底是什么">它到底是什么</h2>
<p>如果只用一句话来概括，Multica 是一个让人类和 AI Agent 共享同一块看板的协作平台。</p>
<p>它的工作流很直接：</p>
<ol>
<li>人创建一个 Issue。</li>
<li>把 Assignee 指给某个 Agent。</li>
<li>Agent 在绑定的 Runtime 上自动领活、拉仓库、执行任务。</li>
<li>结果和过程回写到同一个 Issue 里。</li>
<li>状态最后推进到 <code>In Review</code>，而不是直接 <code>Done</code>。</li>
</ol>
<p>这个 <code>In Review</code> 细节我很喜欢。它几乎在产品层面写死了一个原则：<strong>Agent 可以负责执行，但最终判断必须保留给人。</strong></p>
<p>如果你平时更习惯命令行，这套流程用 CLI 其实非常顺：</p>
<pre><code class="hljs language-bash">multica issue create --title <span class="hljs-string">"写一篇 Multica 体验文章"</span>
multica agent list --output json
multica daemon status
multica issue comment add &#x3C;issue-id> --content <span class="hljs-string">"文章初稿已完成"</span>
</code></pre>
<p>这也解释了为什么我会觉得它不是简单的“AI 看板”。它更像是在尝试定义一层 Agent 协作的基础接口。</p>
<h2 id="部署体验整体顺但有两个坑">部署体验：整体顺，但有两个坑</h2>
<p>从部署角度说，它的技术栈并不复杂：Go 后端、Next.js 前端、PostgreSQL，基础设施很常规，<code>docker compose</code> 也足够友好。我这边配的是 OrbStack 加 Cloudflare Tunnel，前后端分别挂两个子域名，整体流程不算折腾。</p>
<p>但有两个坑，值得提前说。</p>
<p>第一个坑是官方 compose 默认更偏向预构建镜像。单域名没什么问题，但如果你和我一样用双域名部署，又想自定义 WebSocket 地址，那很快就会发现默认方案不太够用。这个时候最省时间的做法不是继续硬改配置，而是直接切回源码构建。</p>
<p>第二个坑是 <code>APP_ENV=production</code>。一旦进生产环境，万能验证码 <code>888888</code> 会被禁掉。如果这时候你还没有提前配好 Resend 邮件 API，就有概率把自己锁在系统外面。这个问题不严重，但第一次部署时确实挺容易踩。</p>
<p>即便如此，我对部署体验的结论还是偏正面。因为真正跑起来之后，它的工作路径很清楚，尤其是 CLI 的可用性，比很多只强调“控制台体验”的 Agent 工具顺不少。</p>
<h2 id="真正让我决定继续用的是-runtime">真正让我决定继续用的，是 Runtime</h2>
<p>Multica 的概念模型其实很清晰：</p>
<p><code>Workspace -> Project -> Repo -> Issue -> Agent -> Runtime</code></p>
<p>前面几层大家都熟，真正让我停下来多看一眼的是最后一层：<strong>Runtime 绑定的是机器，而 Agent 绑定的是 Runtime。</strong></p>
<p>这意味着你不是在抽象地说“让一个 Agent 干活”，而是在明确地说“让这个 Agent 在这台机器上干活”。</p>
<p>这个设计非常实用。因为现实里的开发环境根本不是均质的。</p>
<p>我自己手头就有几台 Mac。有些任务只能在公司电脑上跑，因为依赖内网、证书、特定代理或者本地缓存环境。过去这种需求很难优雅自动化，最后通常都会退化成一句话：这个任务你只能上那台机器手动做。</p>
<p>而 Runtime 把这个问题变成了一个很自然的调度问题。</p>
<p>只要那台机器常驻 daemon，再创建一个绑定到对应 Runtime 的 Agent，任务就只会被那台机器领走。这个能力听起来不花哨，但它解决的是 Agent 落地里一个很硬的现实约束，所以我会把它看得比“支持多少模型”更重要。</p>
<h2 id="skills-和-cli-才是第二层价值">Skills 和 CLI 才是第二层价值</h2>
<p>如果说 Runtime 解决的是“在哪跑”，那 Skills 解决的就是“怎么复用经验”。</p>
<p>这也是我觉得 Multica 和很多任务分发工具不一样的地方。Agent 不是一次性劳动力。一个 Agent 解决过的问题，可以沉淀成 Skill，然后在整个 Workspace 里复用。部署流程、审查规则、迁移步骤、排障清单，这些东西第一次有机会真正从“某个人脑子里的经验”变成“团队里可调用的能力”。</p>
<p>它不是 prompt 收藏夹，也不是文档仓库。它更接近<strong>可执行的经验模块</strong>。</p>
<p>再加上 CLI 足够完整，这个价值就更明显了。因为 Agent 本身并不需要浏览器，它需要的是：</p>
<ul>
<li>能拆任务</li>
<li>能写评论</li>
<li>能追踪状态</li>
<li>能触发其他 Agent</li>
</ul>
<p>而这些动作，用 CLI 比用面板自然太多了。</p>
<p>某种意义上，这让我意识到一件事：<strong>Multica 最适合服务的对象，可能不是人，而是 Agent。</strong></p>
<p>人类确实也会用它，但它真正舒服的地方，在于 Agent 可以围绕这套接口去协作，而不是只等着人一个个地下命令。</p>
<h2 id="它也暴露了一个更深的问题">它也暴露了一个更深的问题</h2>
<p>不过 Multica 真正有意思的地方，恰恰不只是它现在做对了什么，而是它暴露了下一阶段的问题。</p>
<p>今天这类平台默认的工作方式，仍然是：</p>
<pre><code class="hljs language-text">人定义任务边界 -> 人创建 issue -> Agent 执行 -> 人审核结果
</code></pre>
<p>这个范式在今天是合理的，因为大模型的稳定性、自主性和长期规划能力都还没有强到可以完全接手任务拆分。</p>
<p>但如果 Agent 再往前走一步，未来更自然的流程很可能会变成：</p>
<pre><code class="hljs language-text">人描述目标 -> Agent 自己定义任务 -> 多个 Agent 自己拆分和协调 -> 人只审核关键节点
</code></pre>
<p>一旦进入这个阶段，Issue 驱动本身就会开始显得笨重。不是因为它错了，而是因为它代表的是一个“人类先把边界画好”的时代。</p>
<p>所以我现在看 Multica，会把它理解成一种过渡态产品：它一方面非常贴合今天的现实，另一方面又已经把“以后该怎么进化”的问题摆到台面上了。</p>
<p>它要么继续长成 Agent 的操作系统，要么未来会被更原生的 Agent 协作协议替代。</p>
<h2 id="为什么我暂时把它当成一笔基础设施投资">为什么我暂时把它当成一笔基础设施投资</h2>
<p>还有一个我觉得很现实的视角，是额度利用率。</p>
<p>很多人现在同时订阅 Claude Code、Codex，甚至别的 Agent 工具。问题已经不是“我有没有模型能用”，而是“我怎么把已经买下来的算力真正用满”。</p>
<p>从这个角度看，Multica 这类工具的意义很直接：它让 Agent 在你不盯着的时候也能领活、执行、回传结果，尽量减少订阅额度的空转。</p>
<p>这也是为什么我会把它理解成<strong>基础设施投资</strong>，而不是立刻见效的效率神器。</p>
<p>它现在给我的收益还没有大到让我惊呼“必须全量迁移”，但它很像一套应该先接上、先跑着、先让数据和流程积累起来的东西。因为只要 Agent 能力继续增强，这套基础设施的边际价值就会越来越高。</p>
<h2 id="写在最后">写在最后</h2>
<p>我现在对 Multica 的态度，大概可以概括成一句话：<strong>今天它已经有用，但真正大的价值，可能还没到兑现的时候。</strong></p>
<p>它最打动我的，不是某一个功能点，而是它试图认真回答一个问题：当人和 Agent 都成为团队里的实际参与者时，协作界面应该长什么样。</p>
<p>这件事如果做对了，未来很多今天看起来还很“实验性”的 Agent 工作流，都会突然变得顺理成章。</p>
<p>所以我的结论不是“马上把所有工作搬上去”，而是更保守、也更长期一点的判断：</p>
<p>先装上，先把 daemon 跑起来，先让机器和 Agent 开始协作。</p>
<p>剩下的价值，也许会随着模型能力一起到来。</p>]]></content:encoded>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <category>multica</category>
      <category>agents</category>
      <category>self-hosting</category>
      <category>automation</category>
      <category>workflow</category>
    </item>
    <item>
      <title><![CDATA[Claude 降智、GPT 降额：AI 厂商最近怎么了？]]></title>
      <link>https://hopes7.com/zh/blog/ai-model-quality-drop-april-2026</link>
      <guid>https://hopes7.com/zh/blog/ai-model-quality-drop-april-2026</guid>
      <description><![CDATA[2026 年 4 月，Claude 出现明显质量下降和大规模宕机，OpenAI 调整 ChatGPT Pro 定价和限流策略。背后是算力供不应求还是另有原因？]]></description>
      <content:encoded><![CDATA[<h1 id="claude-降智gpt-降额ai-厂商最近怎么了">Claude 降智、GPT 降额：AI 厂商最近怎么了？</h1>
<h2 id="你可能也感受到了">你可能也感受到了</h2>
<p>最近用 AI 写代码，是不是觉得 Claude 变笨了？GPT 的额度不够用了？不是你的错觉。</p>
<p>2026 年 4 月，这两个问题同时爆发，而且都不是小问题。</p>
<h2 id="claude从程序员最爱到众矢之的">Claude：从程序员最爱到众矢之的</h2>
<h3 id="质量断崖式下降">质量断崖式下降</h3>
<p>4 月 9 日到 10 日，大量 Claude Code 用户报告推理质量突然下降：</p>
<ul>
<li><strong>简单 bug 修复</strong>：以前 1-2 轮能搞定，现在要 7-10 轮反复尝试</li>
<li><strong>不再使用专用工具</strong>：模型开始忽略 <code>/plan</code>、<code>/bug</code> 等命令，不再正确路由到子代理</li>
<li><strong>推理深度变浅</strong>：回答更表面化，缺乏深度分析</li>
</ul>
<p>一个 GitHub issue（<a href="https://github.com/anthropics/claude-code/issues/46143">#46143</a>）详细记录了这个变化——客户端没有更新，配置没有改动，就是突然变笨了。</p>
<h3 id="claude-自己都承认了">Claude 自己都承认了</h3>
<p>The Register 做了个有趣的测试：让 Claude 分析自己 GitHub 仓库里的质量问题。结果显示：</p>
<ul>
<li><strong>1-2 月</strong>：平均每月 ~5 个质量投诉</li>
<li><strong>3 月</strong>：飙升到 18 个（3.5 倍）</li>
<li><strong>4 月前 13 天</strong>：已经 20+ 个，按趋势会超过 3 月</li>
</ul>
<h3 id="更大的事件amd-高管的数据报告">更大的事件：AMD 高管的数据报告</h3>
<p>4 月 2 日，AMD AI Group 高级总监 Stella Laurenzo 在 Anthropic 的 GitHub 上发了一份堪称 AI 历史上最详细的 bug 报告：</p>
<ul>
<li>分析了 <strong>6,852 个会话文件</strong></li>
<li>统计了 <strong>234,760 次工具调用</strong></li>
<li>发现 reads-per-edit 从 <strong>6.6x 暴跌到 2.0x</strong></li>
</ul>
<p>跌幅近 70%。一个产品的核心指标在一周内跌掉七成，放在任何行业都不可接受。</p>
<p>这意味着 Claude 变得不爱"读代码"了——不读就改，改了就错，错了再改，恶性循环。</p>
<h3 id="anthropic-确认的原因">Anthropic 确认的原因</h3>
<ul>
<li><strong>3 月 3 日</strong>：默认 effort 级别从高改为 medium</li>
<li><strong>2 月 9 日</strong>：上线了 adaptive thinking（自适应思考），但 thinking token 的 UI 显示可能被隐藏了</li>
<li><strong>3 月 26 日</strong>：引入高峰时段限流，降低 5 小时会话内可用 token</li>
<li><strong>Prompt cache TTL bug</strong>：导致成本膨胀 10-20 倍</li>
</ul>
<p>简单说：<strong>Anthropic 为了控制成本和应对算力不足，降低了模型的"努力程度"，用户感知到的就是"变笨了"。</strong></p>
<h3 id="4-月-13-日大宕机">4 月 13 日大宕机</h3>
<p>雪上加霜，4 月 13 日 Claude 经历了一次大宕机，Claude.ai 和 Claude Code 都受影响，持续约 48 分钟。这进一步加剧了用户的不满。</p>
<h2 id="openai降价限流">OpenAI：降价+限流</h2>
<h3 id="chatgpt-pro-价格腰斩">ChatGPT Pro 价格腰斩</h3>
<p>4 月 10 日，OpenAI 宣布 ChatGPT Pro 价格从 $200/月降到 $100/月。表面上是好消息，但背后逻辑值得思考：</p>
<ul>
<li>降价通常意味着用户增长不及预期，或者成本结构发生了变化</li>
<li>同时推出了 GPT-5.4 mini 作为 fallback 模型——当达到速率限制时自动切换到小模型</li>
</ul>
<h3 id="隐性降额">隐性降额</h3>
<p>虽然价格降了，但实际体验中不少用户反映额度变紧了：</p>
<ul>
<li>GPT-5.4 Thinking 的使用限制更严格</li>
<li>更频繁地被 fallback 到 GPT-5.4 mini</li>
<li>手动 Thinking 模式有每周 3,000 次的硬限制</li>
</ul>
<p><strong>降价的代价是更激进的限流和模型降级。</strong></p>
<h2 id="背后到底发生了什么">背后到底发生了什么？</h2>
<h3 id="算力瓶颈是核心问题">算力瓶颈是核心问题</h3>
<p>两家厂商的行为指向同一个结论：<strong>AI 算力供需失衡</strong>。</p>
<ol>
<li><strong>需求爆炸式增长</strong>：AI 编程工具（Claude Code、Codex、Cursor 等）的用户量在 2026 年 Q1 出现爆发式增长，企业用户大量涌入</li>
<li><strong>供给跟不上</strong>：GPU 产能扩张需要时间，新数据中心的建设周期通常是 12-18 个月</li>
<li><strong>成本压力巨大</strong>：大模型的推理成本仍然很高，降价竞争加剧了亏损压力</li>
</ol>
<h3 id="厂商的应对策略">厂商的应对策略</h3>
<table>
<thead>
<tr>
<th>策略</th>
<th>Anthropic</th>
<th>OpenAI</th>
</tr>
</thead>
<tbody>
<tr>
<td>降低模型"努力程度"</td>
<td>✅ 默认 effort 改为 medium</td>
<td>❌</td>
</tr>
<tr>
<td>高峰时段限流</td>
<td>✅ 5 小时会话弹性定义</td>
<td>✅ 更频繁 fallback</td>
</tr>
<tr>
<td>推出小模型替代</td>
<td>❌</td>
<td>✅ GPT-5.4 mini</td>
</tr>
<tr>
<td>直接降价</td>
<td>❌</td>
<td>✅ Pro 降价 50%</td>
</tr>
<tr>
<td>宕机</td>
<td>✅ 4/13 大宕机</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>本质上都是同一个思路：<strong>在算力不够的情况下，用各种手段控制单位用户的资源消耗</strong>。</p>
<h3 id="对用户意味着什么">对用户意味着什么</h3>
<ol>
<li><strong>AI 编程工具的成本不再"几乎可以忽略不计"</strong>。上周我记录的 1.5 亿 token（有 RTK 优化）已经是压缩后的结果，如果没有优化工具，成本会更高</li>
<li><strong>模型质量可能继续波动</strong>。厂商在算力不够时会优先保企业客户，个人用户的体验可能会打折扣</li>
<li><strong>工具链的重要性上升</strong>。RTK 这类 token 压缩工具从"可选"变成"必选"，多模型混用的策略也更合理</li>
<li><strong>小模型的机会来了</strong>。GPT-5.4 mini 的推出说明厂商在认真做"够用就好"的模型，对特定场景可能比大模型更划算</li>
</ol>
<h2 id="我的应对">我的应对</h2>
<p>作为一个重度 AI 编程用户，我目前的策略：</p>
<ol>
<li><strong>RTK 常驻</strong>：已经验证了 57% 的 token 节省，零学习成本</li>
<li><strong>多模型混用</strong>：不依赖单一模型，Claude 不行就换 GPT，反之亦然</li>
<li><strong>错峰使用</strong>：Anthropic 自己都说了非高峰时段体验更好，如果任务不紧急，可以调整使用时间</li>
<li><strong>本地优先</strong>：简单任务尽量用小模型或本地模型处理，把大模型的配额留给复杂任务</li>
</ol>
<h2 id="真正的问题不透明">真正的问题：不透明</h2>
<p>算力不够可以理解，真正让用户不满的是处理方式。</p>
<p>Anthropic 没有坦诚地说"我们算力紧张，需要临时限流"，而是把默认 effort 悄悄调低、把 thinking token 的显示隐藏、把限流包装成"弹性定义"；OpenAI 一边宣布降价，一边更频繁地把用户静默切换到 mini 小模型，而不明确告知。你付了同样的钱，得到的可能是降级后的服务，且无从察觉。</p>
<p>用户不傻。给了什么、没给什么，大家心里都有数。与其在话术上做文章，不如把精力放在扩容上——给用户最好的产品，而不是最便宜的借口。</p>
<h2 id="写在最后">写在最后</h2>
<p>AI 行业正在经历一个有趣阶段：技术能力在快速提升，但基础设施还没跟上。厂商在用户增长、成本控制和体验质量之间艰难平衡。</p>
<p>对于我们开发者来说，最好的应对不是抱怨模型变笨，而是建立一套不依赖单一厂商的工作流。毕竟，工具是为了服务我们，而不是反过来。</p>
<hr>
<p><em>相关文章：</em></p>
<ul>
<li><em>上一篇：<a href="/posts/rtk-token-consumption-week-test">一周 1.5 亿 Token：RTK 优化后我的 Codex 使用记录</a></em></li>
<li><em>再上一篇：<a href="/posts/codex-token-usage-weekly-record">一周消耗 3.5 亿 Token：我的 Codex 使用记录</a></em></li>
</ul>]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <category>claude</category>
      <category>openai</category>
      <category>gpt</category>
      <category>anthropic</category>
      <category>ai</category>
      <category>模型质量</category>
      <category>行业动态</category>
    </item>
    <item>
      <title><![CDATA[一周 1.5 亿 Token：RTK 优化后我的 Codex 使用记录]]></title>
      <link>https://hopes7.com/zh/blog/rtk-token-consumption-week-test</link>
      <guid>https://hopes7.com/zh/blog/rtk-token-consumption-week-test</guid>
      <description><![CDATA[在上周 3.5 亿 token 的基础上，引入 RTK 后一周 token 消耗降至 1.5 亿，降幅 57%。记录实际使用数据和体验。]]></description>
      <content:encoded><![CDATA[<h1 id="一周-15-亿-tokenrtk-优化后我的-codex-使用记录">一周 1.5 亿 Token：RTK 优化后我的 Codex 使用记录</h1>
<h2 id="背景">背景</h2>
<p><a href="/posts/codex-token-usage-weekly-record">上周</a> 我记录了 11 天消耗 3.5 亿 token 的数据，并提到要引入 RTK 试试优化效果。一周过去了，结果出来了：<strong>1.5 亿 token</strong>。</p>
<p>还是那套工作流——Codex 5.4 和 5.3-codex 混用，写-审-改循环，同样的项目复杂度。唯一的变化就是加了 RTK。</p>
<h2 id="数据对比">数据对比</h2>
<table>
<thead>
<tr>
<th>指标</th>
<th>上周（无 RTK）</th>
<th>这周（有 RTK）</th>
<th>变化</th>
</tr>
</thead>
<tbody>
<tr>
<td>Token 消耗</td>
<td>3.5 亿</td>
<td>1.5 亿</td>
<td><strong>-57%</strong></td>
</tr>
<tr>
<td>使用模型</td>
<td>5.4 + 5.3-codex</td>
<td>5.4 + 5.3-codex</td>
<td>不变</td>
</tr>
<tr>
<td>工作流</td>
<td>写-审-改循环</td>
<td>写-审-改循环</td>
<td>不变</td>
</tr>
<tr>
<td>日常开发任务</td>
<td>功能实现、文档迭代、代码审查</td>
<td>同上</td>
<td>不变</td>
</tr>
</tbody>
</table>
<p>从 3.5 亿降到 1.5 亿，直接省了 2 亿 token。RTK 的压缩效果比我预期的要好。</p>
<h2 id="rtk-实际体验">RTK 实际体验</h2>
<h3 id="最大的感受感知不强效果明显">最大的感受：感知不强，效果明显</h3>
<p>说实话，用了 RTK 一周，日常开发过程中我几乎感觉不到它的存在。它就是个透明代理——AI 工具该干啥干啥，输出的代码质量也没觉得有什么退化。</p>
<p>但回头看用量统计，差距就摆在那了。</p>
<h3 id="哪些场景压缩效果最好">哪些场景压缩效果最好</h3>
<ul>
<li><strong><code>git diff</code> / <code>git status</code></strong>：大项目的 diff 输出非常长，RTK 压缩后 AI 拿到的是精简版本，不影响理解</li>
<li><strong>测试输出</strong>：大量重复的 pass/fail 日志被折叠，只保留关键信息</li>
<li><strong>错误日志</strong>：堆栈追踪被截断到关键行，不再把整个调用链塞进上下文</li>
<li><strong>文件列表</strong>：<code>find</code>、<code>ls</code> 之类的输出按目录分组，去重折叠</li>
</ul>
<h3 id="没什么变化的场景">没什么变化的场景</h3>
<ul>
<li>直接的代码编写对话，本身 token 消耗就不大，RTK 能压缩的空间有限</li>
<li>简短命令的输出，压缩前后差异不大</li>
</ul>
<h2 id="省下的-token-意味着什么">省下的 token 意味着什么</h2>
<ol>
<li><strong>成本直接砍半</strong>。如果按上周的消耗速度，一个月就是十几亿 token，成本不可忽视。现在省下来的是真金白银</li>
<li><strong>上下文窗口更耐用</strong>。同样大小的上下文窗口，能塞进更多轮对话，长任务不容易被截断</li>
<li><strong>开发节奏更从容</strong>。不用时刻想着"这个 diff 太长了会不会爆 context"，该看的照看</li>
</ol>
<h2 id="结论">结论</h2>
<p>上周我说"下周的文章里更新测试结果，看看 RTK 到底是省钱利器还是噱头"。现在结论很明确：<strong>是省钱利器</strong>。</p>
<p>57% 的 token 降幅，零学习成本（<code>rtk init</code> 一条命令），对开发体验几乎零影响。对于重度使用 Codex 或类似 AI 工具的开发者来说，RTK 基本上是必装的。</p>
<hr>
<p><em>上一篇：<a href="/posts/codex-token-usage-weekly-record">一周消耗 3.5 亿 Token：我的 Codex 使用记录</a></em></p>]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <category>codex</category>
      <category>rtk</category>
      <category>ai</category>
      <category>token</category>
      <category>开发效率</category>
      <category>ai-coding</category>
    </item>
    <item>
      <title><![CDATA[一周消耗 3.5 亿 Token：我的 Codex 使用记录]]></title>
      <link>https://hopes7.com/zh/blog/codex-token-usage-weekly-record</link>
      <guid>https://hopes7.com/zh/blog/codex-token-usage-weekly-record</guid>
      <description><![CDATA[记录使用 Codex 5.4 和 5.3 模型进行日常开发的 token 消耗情况，以及引入 RTK 优化 token 使用的计划。]]></description>
      <content:encoded><![CDATA[<h1 id="一周消耗-35-亿-token我的-codex-使用记录">一周消耗 3.5 亿 Token：我的 Codex 使用记录</h1>
<h2 id="起因">起因</h2>
<p>3 月 30 号开始，我像往常一样用 Codex 写代码。到今天打开用量统计一看——3.5 亿 token。</p>
<p>说实话，看到这个数字的时候我愣了一下。11 天，3.5 亿。平均每天 3000 万+，这消耗速度比我想象中快太多了。</p>
<h2 id="我怎么用-codex-的">我怎么用 Codex 的</h2>
<p>我的用法不是简单地让 AI 写代码跑一遍就完事，而是两个模型配合着来：</p>
<ul>
<li><strong>Codex 5.4</strong> 负责写文档初稿，表达准确，结构清晰</li>
<li><strong>Codex 5.3</strong> 在 5.4 的基础上做二次改写和迭代</li>
</ul>
<p>实际流程是这样的：</p>
<ol>
<li>Codex 5.4 产出初稿</li>
<li>Codex 5.3 改写优化</li>
<li>再让 AI 审阅自己的输出，提出修改建议</li>
<li>根据建议再改</li>
<li>重复 3-4 直到满意</li>
</ol>
<p>这个「写-审-改」的循环效果确实好，文档质量明显更高。但代价也很直观——token 消耗在这种场景下会快速累积，因为每一轮审阅都要把完整文档塞进上下文。</p>
<h2 id="这-35-亿花在哪了">这 3.5 亿花在哪了</h2>
<p>粗略回忆一下，主要消耗来自这几块：</p>
<ul>
<li><strong>文档迭代</strong>：上面说的写-审-改循环，占了大头</li>
<li><strong>功能实现</strong>：复杂逻辑的实现和多轮调试</li>
<li><strong>代码审查</strong>：让 AI 审查代码并给出重构建议</li>
<li><strong>长上下文对话</strong>：带着整个文件让 AI 理解上下文再操作</li>
</ul>
<p>其中文档迭代是最烧 token 的，因为每次审阅都是全文过一遍，几轮下来就是好几倍的文档长度。</p>
<h2 id="优化方案引入-rtk">优化方案：引入 RTK</h2>
<p>token 该省还是得省。从今天开始，我打算试试 <a href="https://github.com/rtk-ai/rtk">RTK</a> ——一个 CLI 代理工具，在命令输出送到 LLM 之前做过滤和压缩，官方说能减少 60-90% 的 token 消耗。</p>
<p>它的工作方式挺巧妙的：通过 hook 透明地重写 Bash 命令，比如把 <code>git status</code> 重写为 <code>rtk git status</code>，AI 工具完全感知不到，只是拿到了压缩后的输出。</p>
<p>每类命令会应用四种策略：</p>
<table>
<thead>
<tr>
<th>策略</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>Smart Filtering</td>
<td>移除注释、空白、样板代码</td>
</tr>
<tr>
<td>Grouping</td>
<td>按目录归类文件、按类型归类错误</td>
</tr>
<tr>
<td>Truncation</td>
<td>保留相关上下文，截断冗余</td>
</tr>
<tr>
<td>Deduplication</td>
<td>折叠重复日志行并计数</td>
</tr>
</tbody>
</table>
<p>支持 Claude Code、Codex、Copilot、Gemini CLI、Cursor、Windsurf、Cline 等主流工具，<code>rtk init -g --codex</code> 一条命令就能搞定。</p>
<h2 id="下一步">下一步</h2>
<p>接下来一周，我会用同样的工作流做真实开发，对比有无 RTK 的 token 消耗差异。具体会关注：</p>
<ul>
<li>日均 token 消耗变化</li>
<li><code>git diff</code>、测试输出等高频命令的压缩效果</li>
<li>是否影响 AI 的理解和输出质量</li>
</ul>
<p>测试结果会在下周的文章里更新，到时候看看 RTK 到底是省钱利器还是噱头。</p>]]></content:encoded>
      <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
      <category>codex</category>
      <category>ai</category>
      <category>token</category>
      <category>开发效率</category>
      <category>ai-coding</category>
    </item>
    <item>
      <title><![CDATA[前端安全实践：用 Sandbox Iframe 隔离敏感输入]]></title>
      <link>https://hopes7.com/zh/blog/checkout-secure-address-iframe</link>
      <guid>https://hopes7.com/zh/blog/checkout-secure-address-iframe</guid>
      <description><![CDATA[电商 Checkout 场景下，如何通过 sandbox iframe + 中间层转发，以最小改动将地址和证件等敏感输入从主页面隔离。]]></description>
      <content:encoded><![CDATA[<h2 id="背景合规压力下的最小改动难题">背景：合规压力下的"最小改动"难题</h2>
<p>电商 checkout 页面天然要处理大量敏感信息——用户地址、证件号、税号……这些数据要么在页面里展示，要么需要用户填写。</p>
<p>更要命的是，这几年 AI 工具的普及让攻击门槛大幅降低。以前还需要点技术功底的 XSS 注入、中间人攻击，现在大模型可以直接帮黑客生成攻击脚本、自动化扫描漏洞、甚至模拟正常用户行为绕过风控。安全审计的要求越来越严格：<strong>敏感输入过程不应该出现在主页面中</strong>。</p>
<p>但现实是，checkout 地址模块经过多年迭代，逻辑深、耦合多，直接推倒重写不现实。</p>
<p>所以我们的问题变成了：<strong>怎么在不重构现有主链路的前提下，把敏感输入隔离出去？</strong></p>
<h2 id="方案选型为什么不选全量重写">方案选型：为什么不选"全量重写"</h2>
<p>讨论阶段我们列了几个方向：</p>
<table>
<thead>
<tr>
<th>方案</th>
<th>优点</th>
<th>缺点</th>
</tr>
</thead>
<tbody>
<tr>
<td>全页面重构</td>
<td>彻底干净</td>
<td>工期长，回归风险高</td>
</tr>
<tr>
<td>前端加密</td>
<td>改动小</td>
<td>密钥管理是新的安全隐患</td>
</tr>
<tr>
<td>Token 化</td>
<td>最安全</td>
<td>需要改后端数据模型和所有消费方</td>
</tr>
<tr>
<td><strong>sandbox iframe + 中间层</strong></td>
<td><strong>改动最小，隔离有效</strong></td>
<td><strong>首屏仍暴露只读数据</strong></td>
</tr>
</tbody>
</table>
<p>我们选了最后一种。核心判断是：一期先把"输入过程"隔离掉，已经能解决最大的安全痛点。首屏的只读展示可以后续再处理。</p>
<h2 id="架构思路三层各司其职">架构思路：三层各司其职</h2>
<p>整个方案分三层，职责边界很清晰：</p>
<h3 id="1-父页面checkout-主页面">1. 父页面（Checkout 主页面）</h3>
<ul>
<li>继续负责地址列表查询和展示（<strong>一期不改这条链路</strong>）</li>
<li>新增/编辑按钮改为打开 iframe 弹窗</li>
<li>为每次操作申请短期会话凭证</li>
<li>监听 iframe 的保存成功消息，然后刷新地址列表</li>
</ul>
<p>父页面<strong>不做</strong>的事情：不渲染敏感表单、不读 iframe DOM、不监听用户输入过程。</p>
<h3 id="2-收集器sandbox-iframe">2. 收集器（Sandbox Iframe）</h3>
<ul>
<li>独立渲染完整的地址/证件表单</li>
<li>自行维护表单状态和校验</li>
<li>通过会话凭证从中间层获取初始化数据（国家列表、编辑回填值等）</li>
<li>提交也走中间层，不直连后端业务接口</li>
<li>保存成功后通过 <code>postMessage</code> 通知父页面</li>
</ul>
<p>关键设计：同一个收集器通过 <code>scope</code> 参数支持两种模式——完整地址表单和仅证件表单，避免维护两套页面。</p>
<h3 id="3-中间层node-facade">3. 中间层（Node Facade）</h3>
<p>这是整个方案的"收口"点：</p>
<ul>
<li>管理短期会话凭证（30 分钟有效，提交成功后立即失效）</li>
<li>聚合国家列表和编辑回填数据，返回给 iframe</li>
<li>转发地址保存和证件保存请求到真实后端</li>
<li>统一做日志脱敏和参数过滤</li>
</ul>
<p>中间层<strong>不</strong>存储地址明文，不做长期草稿，也不替代现有地址列表主数据源。</p>
<h2 id="会话管理安全性和用户体验的平衡">会话管理：安全性和用户体验的平衡</h2>
<p>会话凭证的设计是安全性的核心：</p>
<ul>
<li><strong>短期有效</strong>：固定 30 分钟过期，提交成功后立即失效</li>
<li><strong>用户绑定</strong>：凭证绑定当前登录用户、站点和操作模式</li>
<li><strong>单会话约束</strong>：同一页面同时只允许一个有效会话，新建会话自动让旧会话失效</li>
<li><strong>编辑权限校验</strong>：编辑模式时，会验证当前用户是否有权编辑该地址</li>
</ul>
<p>这样即使凭证泄露，攻击窗口也很小，且无法跨用户使用。</p>
<h2 id="提交策略多接口联动的失败处理">提交策略：多接口联动的失败处理</h2>
<p>实际保存时需要调用多个后端接口（地址主体、证件号、默认地址设置等），失败处理策略必须提前想清楚：</p>
<p><strong>执行顺序：</strong> 先保存地址主体 → 再保存证件信息 → 最后处理默认地址和账单地址同步</p>
<p><strong>失败场景：</strong></p>
<ul>
<li><strong>地址保存成功、证件保存失败</strong>：不做补偿删除（新增场景下），返回"部分保存"状态，提示用户确认</li>
<li><strong>地址和证件都成功、但默认地址同步失败</strong>：同样返回"部分保存"状态</li>
<li><strong>编辑场景下部分失败</strong>：不承诺自动回滚，提示用户重试或手动刷新</li>
</ul>
<p>核心原则：<strong>宁可知疑地告诉用户"请确认"，也不要假装成功了。</strong></p>
<h2 id="消息校验不让-postmessage-成为新的攻击面">消息校验：不让 postMessage 成为新的攻击面</h2>
<p>父页面和 iframe 通过 <code>postMessage</code> 通信，这本身也可能被利用。我们的防护措施：</p>
<ul>
<li>校验消息来源是否为 iframe 的 <code>contentWindow</code></li>
<li>每次会话生成随机 nonce，iframe 回传时必须携带</li>
<li>只处理白名单消息类型（如保存成功），忽略其他所有消息</li>
<li>不把 <code>event.origin</code> 作为唯一信任依据</li>
</ul>
<h2 id="做不到的事情诚实面对">做不到的事情（诚实面对）</h2>
<p>一期方案有明确的边界：</p>
<ul>
<li>❌ 首屏地址列表仍然在父页面中展示完整地址</li>
<li>❌ 中间层和后端在保存/回填时仍然能看到完整地址</li>
<li>❌ 不做删除地址、设置默认地址的前端重构</li>
</ul>
<p>这些不是"忘了做"，而是刻意的取舍。一期目标是<strong>输入过程隔离</strong>，不是<strong>全链路数据脱敏</strong>。</p>
<h2 id="灰度策略从单站点开始">灰度策略：从单站点开始</h2>
<p>一期通过 feature flag 控制，首批只在一个站点（巴西）上线。观测的核心指标：</p>
<ul>
<li>会话过期率</li>
<li>移动端收集器打开成功率</li>
<li>新增/编辑保存成功率</li>
<li>部分保存错误率</li>
</ul>
<p>稳定后再逐步扩展到其他站点。</p>
<h2 id="总结">总结</h2>
<p>回头看这个方案，最大的体会是：<strong>安全改造不一定要"大拆大建"</strong>。</p>
<p>找到一个足够小的切口，先把最大的风险点解决掉，然后逐步扩展。比起憋一个"完美方案"然后迟迟上不了线，这种渐进式的方法反而更务实。</p>
<p>当然，sandbox iframe 不是银弹。如果未来要做到全链路脱敏（包括首屏展示），就需要更底层的数据模型改造。但那是下一步的事了。</p>
<p>先把眼前的问题解决，再谈理想。</p>]]></content:encoded>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <category>前端安全</category>
      <category>iframe</category>
      <category>sandbox</category>
      <category>安全架构</category>
      <category>电商平台</category>
    </item>
    <item>
      <title><![CDATA[OpenClaw 踩坑记：Gateway 配对问题与定时任务配置]]></title>
      <link>https://hopes7.com/zh/blog/openclaw-gateway-pairing-troubleshooting</link>
      <guid>https://hopes7.com/zh/blog/openclaw-gateway-pairing-troubleshooting</guid>
      <description><![CDATA[记录在配置 OpenClaw Gateway 设备配对时遇到的问题，以及如何通过 openclaw devices approve 解决 cron 命令的 pairing required 错误。]]></description>
      <content:encoded><![CDATA[<h2 id="起因">起因</h2>
<p>最近在玩 OpenClaw——一个可以把 AI 助手接到 Telegram、Discord 等聊天工具上的框架。整体体验挺丝滑的，但在配置定时任务的时候卡了好一阵子，这里把踩坑过程记录一下，希望能帮到后来人。</p>
<h2 id="问题pairing-required">问题：pairing required</h2>
<p>事情是这样的：我想让 bot 每天早上 9 点自动给我推送 AI 资讯日报，于是在聊天里让 bot 帮我创建一个 cron 任务。</p>
<p>bot 执行了 <code>openclaw cron add</code> 命令，结果报错：</p>
<pre><code>gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
</code></pre>
<p>看起来是 CLI 和 Gateway 之间的连接没有完成配对。</p>
<h3 id="第一个坑错误的配对命令">第一个坑：错误的配对命令</h3>
<p>说实话，第一反应是去查文档，然后我试了 <code>openclaw gateway pair</code>——看起来很合理对吧？结果并不管用。</p>
<p>正确的方式是通过 <code>openclaw devices</code> 相关命令来完成设备配对。具体操作：</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 先看一下帮助，了解有哪些子命令</span>
openclaw devices --<span class="hljs-built_in">help</span>

<span class="hljs-comment"># 然后执行配对批准</span>
openclaw devices approve
</code></pre>
<p>执行完 <code>openclaw devices approve</code> 之后，CLI 就能正常和 Gateway 通信了。</p>
<h3 id="第二个坑未配对时执行命令会导致-gateway-断连">第二个坑：未配对时执行命令会导致 Gateway 断连</h3>
<p>这个就比较疼了。在还没完成配对的时候，如果让 bot 尝试执行 <code>openclaw cron add</code> 之类的命令，不仅仅是命令失败那么简单——<strong>Gateway 会直接断连</strong>。</p>
<p>这意味着你的 bot 会掉线，需要重新连接。如果你正在通过聊天操作，这一下还挺搞心态的。</p>
<p>所以正确的顺序是：</p>
<ol>
<li>先在终端完成设备配对：<code>openclaw devices approve</code></li>
<li>然后再让 bot 创建定时任务</li>
</ol>
<h2 id="配对成功后创建定时任务">配对成功后：创建定时任务</h2>
<p>配对搞定之后，创建定时任务就很顺了：</p>
<pre><code class="hljs language-bash">openclaw cron add \
  --name <span class="hljs-string">"AI日报"</span> \
  --cron <span class="hljs-string">"0 9 * * *"</span> \
  --tz <span class="hljs-string">"Asia/Shanghai"</span> \
  --session isolated \
  --message <span class="hljs-string">"搜索过去24小时内AI领域的最新资讯..."</span> \
  --announce \
  --channel telegram \
  --to <span class="hljs-string">"你的chat_id"</span>
</code></pre>
<p>几个关键参数说明：</p>
<ul>
<li><code>--session isolated</code>：独立会话运行，不会干扰主聊天</li>
<li><code>--announce</code>：执行完成后把结果发送到指定频道</li>
<li><code>--cron</code> + <code>--tz</code>：用 cron 表达式 + 时区指定执行时间</li>
<li><code>--channel telegram --to &#x3C;id></code>：指定推送目标</li>
</ul>
<h2 id="模型选择">模型选择</h2>
<p>顺便提一句，我用的模型是 <code>zai/glm-5-turbo</code>（智谱 GLM-5 Turbo），整体体验还不错，响应速度快，中文理解能力强，做日常助手任务够用。</p>
<h2 id="总结">总结</h2>
<table>
<thead>
<tr>
<th>问题</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>pairing required</code> 错误</td>
<td>运行 <code>openclaw devices approve</code></td>
</tr>
<tr>
<td><code>openclaw gateway pair</code> 无效</td>
<td>设备配对用 <code>openclaw devices</code> 而非 <code>openclaw gateway</code></td>
</tr>
<tr>
<td>未配对执行命令导致断连</td>
<td>先完成配对，再执行 cron 等命令</td>
</tr>
</tbody>
</table>
<p>OpenClaw 整体是个挺有意思的项目，但文档里关于设备配对的部分确实容易让人走弯路。希望这篇记录能帮你省点时间。</p>
<p>如果你也在折腾 OpenClaw，欢迎交流 🤝</p>]]></content:encoded>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <category>openclaw</category>
      <category>gateway</category>
      <category>cron</category>
      <category>定时任务</category>
      <category>devops</category>
      <category>troubleshooting</category>
    </item>
    <item>
      <title><![CDATA[从编写到验证：作为全栈工程师的三年AI协作实践]]></title>
      <link>https://hopes7.com/zh/blog/ai-collaboration-writing-to-verifying</link>
      <guid>https://hopes7.com/zh/blog/ai-collaboration-writing-to-verifying</guid>
      <description><![CDATA[深入反思AI如何重塑全栈开发，工程师如何从编写代码转向设计上下文、验证逻辑和编排人机工作流——包含真实的前后端实践案例。]]></description>
      <content:encoded><![CDATA[<h2 id="当-30-的代码都由-ai-写出来全栈工程师的时间应该花在哪里">当 30% 的代码都由 AI 写出来，全栈工程师的时间应该花在哪里？</h2>
<p>做全栈做得够久，你会经历很多次工具范式的变革：
从模块化、TypeScript、React、SSR，到 serverless、设计系统。</p>
<p>但过去三年里，<strong>没有任何东西比 AI 协作更彻底地重塑我的工作方式</strong>。</p>
<p>最意外的是：</p>
<blockquote>
<p><strong>AI 写的代码越来越多，而我本人反而越来越累。</strong></p>
</blockquote>
<p>后端的 Controller、Service、DTO、校验、日志……
前端的组件、布局、响应式、交互骨架、翻译、样式……
AI 都能写，而且写得非常快。</p>
<p>真正的拐点发生在我意识到：</p>
<blockquote>
<p><strong>现在最难的工作，不是写代码，而是验证 AI 写的代码是否“真的正确”。</strong></p>
</blockquote>
<hr>
<h2 id="1-两种极端声音全栈开发者常常夹在中间">1. 两种极端声音，全栈开发者常常夹在中间</h2>
<p>在所有 AI 编程的讨论里，总有两个极端：</p>
<h3 id="ai-太强了十倍效率">⚡「AI 太强了，十倍效率！」</h3>
<p>“10 分钟搞定 20 个组件。”
“我几乎没敲键盘就把登录做完了。”</p>
<h3 id="ai-很蠢用它还拖累我">⚡「AI 很蠢，用它还拖累我」</h3>
<p>“看着对，其实不对。”
“调 bug 的时间比我自己写还长。”</p>
<p>而事实上，两边都说的没错：</p>
<ul>
<li><strong>后端</strong>：CRUD、分页、校验、模板化逻辑 → AI 简直是核弹</li>
<li><strong>前端</strong>：组件结构、布局、样式、图转代码 → AI 超稳定</li>
<li>但 AI 很容易在以下场景翻车：
<ul>
<li>复杂业务逻辑</li>
<li>多状态 UI</li>
<li>历史包袱沉重的代码</li>
<li>隐式规则多的老系统</li>
</ul>
</li>
</ul>
<p>我最开始用 AI 完全是试运气：</p>
<ul>
<li>觉得“AI 应该能做吧”就丢给它</li>
<li>有时超预期</li>
<li>有时“优雅但致命”的 bug 让我怀疑人生</li>
</ul>
<p>直到意识到：</p>
<blockquote>
<p><strong>不是 AI 随机，而是我的流程随机。</strong></p>
</blockquote>
<hr>
<h2 id="2-别再问ai-准不准应该问值不值">2. 别再问「AI 准不准」，应该问「值不值」</h2>
<p>我过去特别执着：</p>
<ul>
<li>“AI 生成的代码对不对？”</li>
<li>“这个校验逻辑有没有漏？”</li>
<li>“这个组件结构是不是最佳实践？”</li>
</ul>
<p>但后来发现：</p>
<p><strong>准确率是一个会把人带沟里的指标。</strong></p>
<p>真正该算的是 ROI（投入产出比）：</p>
<pre><code>价值 = 纯手写时间 ÷（AI 生成时间 + 人工修复时间）
</code></pre>
<p>如果手写要 6 小时
AI 生成 + 修改只用 1 小时
→ 那就是赚翻了</p>
<p>即使第一次生成不完美，只要能省时间，就是「值」。</p>
<h3 id="ai-最划算的工作四要素">“AI 最划算的工作”四要素</h3>
<p>无论前端还是后端，都高度一致：</p>
<ol>
<li><strong>重复度高 / 模板化强</strong></li>
<li><strong>纯手写耗时长但没有创造性</strong></li>
<li><strong>风险低（做错不会导致系统爆炸）</strong></li>
<li><strong>结果容易验证</strong></li>
</ol>
<p>这类任务，就是所谓的：</p>
<blockquote>
<p><strong>AI 的“甜点区”。</strong></p>
</blockquote>
<hr>
<h2 id="3-前端的革命设计稿转代码已经不是梦想而是默认能力">3. 前端的革命：设计稿转代码已经不是梦想，而是默认能力</h2>
<p>2024–2025 最大的前端革命来自：</p>
<blockquote>
<p><strong>Gemini 3 Pro 等模型的“设计稿还原”能力进化到了几乎像作弊的程度。</strong></p>
</blockquote>
<p>我不是夸张。</p>
<p>给它：</p>
<ul>
<li>一张 Figma 截图</li>
<li>一个完整设计稿</li>
<li>甚至是粗糙的 UI 草图</li>
</ul>
<p>它都能稳定输出：</p>
<ul>
<li>React + TSX 组件</li>
<li>Tailwind / CSS Modules</li>
<li>完整响应式布局</li>
<li>主题化变量</li>
<li>无障碍（ARIA）标注</li>
<li>动效占位</li>
<li>像素级视觉结构</li>
</ul>
<p>一些场景甚至可以达到 <strong>90%–100% 的还原度</strong>。</p>
<p>比如：</p>
<ul>
<li>导航栏</li>
<li>表单</li>
<li>卡片列表</li>
<li>Dashboard</li>
<li>复杂的栅格布局</li>
<li>商品详情页</li>
<li>登陆 / 注册流</li>
</ul>
<h3 id="那前端工程师还干什么">那前端工程师还干什么？</h3>
<p>我们从“敲 CSS、拼布局”变成了：</p>
<h3 id="-验证交互">✔ 验证交互</h3>
<p>是否键盘可访问？
是否存在焦点陷阱？
动画是否自然？</p>
<h3 id="-验证逻辑">✔ 验证逻辑</h3>
<p>组件的所有状态是否覆盖？</p>
<ul>
<li>loading</li>
<li>error</li>
<li>empty</li>
<li>offline</li>
<li>stale</li>
</ul>
<h3 id="-验证兼容性">✔ 验证兼容性</h3>
<p>Safari 下是否抖动？
移动端是否溢出？
SSR hydration 是否正常？</p>
<h3 id="-验证极端情况">✔ 验证极端情况</h3>
<p>长字符串？
高速点击？
网络抖动？</p>
<h3 id="-验证架构与可维护性">✔ 验证架构与可维护性</h3>
<p>AI 不会帮你保证：</p>
<ul>
<li>props 设计是否合理</li>
<li>状态是否拆分正确</li>
<li>逻辑是否可以复用</li>
<li>组件是否符合团队规范</li>
<li>是否适配现有 design system</li>
</ul>
<p>换句话说：</p>
<blockquote>
<p><strong>前端开发者不再“画界面”，
而是负责“审界面、校逻辑、控质量”。</strong></p>
</blockquote>
<p>这和后端一样：从“写代码”变成“验代码”。</p>
<hr>
<h2 id="4-全栈工程师的新角色上下文设计师context-designer">4. 全栈工程师的新角色：上下文设计师（Context Designer）</h2>
<p>以前的全栈是：</p>
<ul>
<li>写页面</li>
<li>写接口</li>
<li>写逻辑</li>
<li>写测试</li>
<li>修 bug</li>
</ul>
<p>现在的全栈变成：</p>
<blockquote>
<p><strong>设计上下文 → 用 AI 生成 → 验证 → 整合 → 测试 → 监控</strong></p>
</blockquote>
<h3 id="我现在把-ai-当成一个超强但没背景信息的实习生">我现在把 AI 当成一个「超强但没背景信息的实习生」</h3>
<p>它很擅长：</p>
<ul>
<li>模式化代码</li>
<li>风格统一</li>
<li>转换与重构</li>
<li>性能优化建议</li>
<li>跟示例学风格</li>
</ul>
<p>但它不擅长：</p>
<ul>
<li>理解业务全貌</li>
<li>历史包袱</li>
<li>“哪些地方不能错”</li>
<li>系统隐形约束</li>
<li>异常链路的坑</li>
</ul>
<p>所以我的时间配比从：</p>
<ul>
<li><strong>80% 写代码 / 20% 自测</strong>
变成</li>
<li><strong>30% 指挥生成 / 70% 验证质量</strong></li>
</ul>
<p>听起来更累？</p>
<p>没错，但也更接近“工程价值”的核心区域。</p>
<p>AI 写代码，
工程师管系统。</p>
<hr>
<h2 id="5-让项目ai-友好标准化--自动化">5. 让项目“AI 友好”：标准化 + 自动化</h2>
<p>几乎所有团队都会遇到同一个悖论：</p>
<blockquote>
<p><strong>AI 用得越来越多，但效率没明显提升。</strong></p>
</blockquote>
<p>原因很简单：</p>
<h3 id="ai-在干净的工程体系里表现很好在混乱的项目里表现极差"><strong>AI 在干净的工程体系里表现很好，在混乱的项目里表现极差。</strong></h3>
<p>我在全栈项目里总结出的最佳做法：</p>
<h3 id="后端标准化">后端标准化：</h3>
<ul>
<li>OpenAPI / Proto 明确契约</li>
<li>DTO / Error 统一格式</li>
<li>业务规则文档化</li>
<li>日志规范一致</li>
<li>SQL / Prisma / Drizzle 内部规则统一</li>
</ul>
<h3 id="前端标准化">前端标准化：</h3>
<ul>
<li>统一 components / hooks / utils 目录</li>
<li>统一 spacing / color / typography token</li>
<li>明确组件命名规范</li>
<li>明确 props 设计原则</li>
<li>清晰的设计系统（tokens + primitives + patterns）</li>
</ul>
<p>只要标准化做好：</p>
<blockquote>
<p><strong>AI 的命中率会呈非线性上涨。</strong></p>
</blockquote>
<h3 id="自动化把-ai-的价值再向上推一层">自动化把 AI 的价值再向上推一层</h3>
<ul>
<li>pre-commit</li>
<li>snapshot testing</li>
<li>visual diff</li>
<li>类型测试</li>
<li>e2e 流程回归</li>
<li>Storybook 交互测试</li>
<li>PR Bot 自动评论</li>
</ul>
<p>现在全都能用 AI 辅助搭建。</p>
<p>AI 不只是写代码，更是写“工程体系”。</p>
<hr>
<h2 id="6-为什么越来越多人说ai-写代码越多我越累">6. 为什么越来越多人说：AI 写代码越多，我越累？</h2>
<p>这是当今工程师最真实的困境：</p>
<blockquote>
<p><strong>人类其实是一个“算力高但上下文窗口超小的 LLM”。</strong></p>
</blockquote>
<p>AI 写得越快，人类要处理的东西越多：</p>
<ul>
<li>更多 PR</li>
<li>更多生成物</li>
<li>更多决定</li>
<li>更多 review</li>
<li>更多上下文切换</li>
</ul>
<p>注意力碎片化，才是真正让人累垮的原因。</p>
<h3 id="我作为全栈的自救方法">我作为全栈的「自救方法」</h3>
<ul>
<li><strong>深度工作分层</strong>：一段时间只解决一个模块</li>
<li><strong>AI 批处理</strong>：统一写 prompt → 让 AI 批量跑 → 最后集中验收</li>
<li><strong>3 分钟原则</strong>：能马上解决的就立刻做</li>
<li><strong>背景清晰</strong>：所有 prompt 都带背景、目标、限制、验收标准</li>
<li><strong>单日单主线</strong>：一天只推进一个“大主题”</li>
</ul>
<p>AI 放大了生产力，
也放大了注意力成本。</p>
<p>只有先管理注意力，才能真正享受 AI 的红利。</p>
<hr>
<h2 id="7-两条不会随着模型迭代而过时的原则">7. 两条不会随着模型迭代而过时的原则</h2>
<h3 id="原则一瓶颈已从生成转移到验证"><strong>原则一：瓶颈已从生成转移到验证</strong></h3>
<p>生成只要 10 秒
验证可能要 1 小时</p>
<p>写代码不再是主力工作
<strong>验证正确性</strong> 才是工程师的核心价值</p>
<h3 id="原则二上下文是王道context-is-king"><strong>原则二：上下文是王道（Context is King）</strong></h3>
<p>AI 的上限 = 你给它的上下文质量</p>
<p>最好的上下文来自：</p>
<ul>
<li>明确的需求</li>
<li>成熟的模式</li>
<li>高质量示例</li>
<li>完整的结构化文档</li>
<li>统一的工程体系</li>
</ul>
<hr>
<h2 id="8-作为全栈开发者我给不同阶段使用-ai-的同行一些建议">8. 作为全栈开发者，我给不同阶段使用 AI 的同行一些建议</h2>
<h3 id="-刚开始用-ai">🌱 刚开始用 AI</h3>
<p>从最容易的甜点区入手：</p>
<ul>
<li>CRUD</li>
<li>小组件</li>
<li>小脚本</li>
<li>本地工具类</li>
</ul>
<p>先把「值不值」那条公式跑顺。</p>
<h3 id="-已经大量使用-ai">🌿 已经大量使用 AI</h3>
<p>开始优化：</p>
<ul>
<li>上下文</li>
<li>验证流程</li>
<li>项目结构</li>
<li>自动化体系</li>
</ul>
<h3 id="-如果你是-tech-lead-或-manager">🌳 如果你是 Tech Lead 或 Manager</h3>
<p>把注意力从：</p>
<p><strong>“AI 能写多少代码？”</strong>
转向
<strong>“我们的协作流程有多稳？”</strong></p>
<p>写代码只是一个环节
工程效率来自整个流水线的稳定和清晰</p>
<hr>
<h2 id="写在最后">写在最后</h2>
<p>AI 没有取代开发者——
它重塑的是开发者的角色边界。</p>
<p>我们不再是写代码的机器，
而是：</p>
<ul>
<li>上下文的设计者</li>
<li>质量的守门人</li>
<li>系统的架构者</li>
<li>AI 的调度者</li>
</ul>
<p>AI 写代码，
但我们决定：</p>
<ul>
<li>写什么</li>
<li>为什么写</li>
<li>是否正确</li>
<li>是否能上线</li>
<li>是否能长期维护</li>
</ul>
<p>我们从“写代码”，
走向了：</p>
<blockquote>
<p><strong>“设计和验证一个能替你写代码的智能系统”。</strong></p>
</blockquote>]]></content:encoded>
      <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
      <category>人工智能辅助开发</category>
      <category>前端人工智能</category>
      <category>Gemini3Pro</category>
      <category>工程生产力</category>
      <category>代码质量</category>
      <category>上下文工程</category>
    </item>
    <item>
      <title><![CDATA[AI协作开发：从设计理念到完整产品的实践之旅]]></title>
      <link>https://hopes7.com/zh/blog/ai-collaborative-development</link>
      <guid>https://hopes7.com/zh/blog/ai-collaborative-development</guid>
      <description><![CDATA[记录一次完整的AI辅助开发过程，从最初的设计理念到功能完善的网站，展示现代AI工具如何改变开发流程和提升效率。]]></description>
      <content:encoded><![CDATA[<h2 id="项目缘起一次不同寻常的开发之旅">项目缘起｜一次不同寻常的开发之旅</h2>
<p>老实说，这次项目的开始就很不一样。</p>
<p>当我收到那份详细的设计文档时，第一反应是：这不仅仅是技术需求，更是一个完整的品牌理念——"hopes.icu"，一个在技术与生活之间持续产出可复用经验与温柔洞见的个人空间。</p>
<p>但这次的不同之处在于，我决定完全依赖AI协作来完成整个开发过程。从设计理解到技术实现，从内容创作到系统优化，让AI成为真正的开发伙伴。</p>
<h3 id="痛点传统开发的效率瓶颈">痛点：传统开发的效率瓶颈</h3>
<p>在过去类似的项目中，我通常会遇到这些问题：</p>
<ul>
<li><strong>理解偏差</strong>：从设计文档到技术实现的信息丢失</li>
<li><strong>重复劳动</strong>：相似的组件和配置需要重复编写</li>
<li><strong>上下文切换</strong>：在不同技术栈和工具间频繁切换</li>
<li><strong>维护成本</strong>：后续迭代时需要重新理解之前的代码</li>
</ul>
<h3 id="机会ai作为开发伙伴的价值">机会：AI作为开发伙伴的价值</h3>
<p>这次我想要验证一个假设：AI能否不仅仅是代码生成工具，而是真正的开发伙伴？</p>
<p>我的目标很明确：</p>
<ol>
<li><strong>设计理解</strong>：AI能够理解抽象的设计理念</li>
<li><strong>技术实现</strong>：AI能够生成高质量的代码</li>
<li><strong>迭代优化</strong>：AI能够在开发过程中持续改进</li>
<li><strong>知识传递</strong>：AI能够解释技术决策和最佳实践</li>
</ol>
<h2 id="ai协作过程从理念到产品的完整实践">AI协作过程｜从理念到产品的完整实践</h2>
<h3 id="第一阶段架构设计与技术选型">第一阶段：架构设计与技术选型</h3>
<p><strong>最初的Prompt：</strong></p>
<blockquote>
<p>"我需要创建一个名为hopes.icu的个人博客平台，核心理念是'在技术与生活之间，持续产出可复用的经验与温柔的洞见'。要求支持中英双语，包含首页、博客列表、标签筛选、关于页面等功能。请帮我设计完整的技术架构和项目结构。"</p>
</blockquote>
<p><strong>AI的响应超出了我的预期：</strong></p>
<ol>
<li><strong>技术栈推荐</strong>：Next.js 15 + TypeScript + Tailwind CSS 4 + Supabase</li>
<li><strong>目录结构设计</strong>：完整的文件夹层次和组件划分</li>
<li><strong>数据模型设计</strong>：博客文章、分类、标签的数据库结构</li>
<li><strong>国际化方案</strong>：next-intl的完整配置建议</li>
</ol>
<p>最让我惊讶的是，AI不仅给出了技术方案，还解释了每个选择的理由，比如为什么选择Tailwind CSS 4而不是传统版本。</p>
<h3 id="第二阶段ui组件与设计系统">第二阶段：UI组件与设计系统</h3>
<p><strong>设计系统标准化：</strong></p>
<p>项目中的图标系统是个典型的例子。当我提出需要统一图标风格时：</p>
<blockquote>
<p>"我需要标准化整个网站的图标系统，使用一致的视觉风格和主题色适配。"</p>
</blockquote>
<p>AI不仅理解了需求，还主动分析了现有问题：</p>
<ul>
<li>识别出了27个文件中的图标使用问题</li>
<li>提出了使用lucide-react作为统一标准的建议</li>
<li>创建了可复用的Icon组件</li>
<li>制定了分阶段迁移策略</li>
</ul>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// AI生成的统一Icon组件</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Icon</span>(<span class="hljs-params">{ name: IconComp, className, title, ...rest }: <span class="hljs-title class_">IconProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">IconComp</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>'<span class="hljs-attr">inline-block</span> <span class="hljs-attr">h-5</span> <span class="hljs-attr">w-5</span> <span class="hljs-attr">align-middle</span>', <span class="hljs-attr">className</span>)}
      <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">{title</span> ? <span class="hljs-attr">undefined</span> <span class="hljs-attr">:</span> <span class="hljs-attr">true</span>}
      <span class="hljs-attr">aria-label</span>=<span class="hljs-string">{title}</span>
      {<span class="hljs-attr">...rest</span>}
    /></span></span>
  )
}
</code></pre>
<h3 id="第三阶段内容创作与国际化">第三阶段：内容创作与国际化</h3>
<p><strong>多语言内容生成：</strong></p>
<p>最令人印象深刻的是内容创作部分。当我需要为网站填充内容时：</p>
<blockquote>
<p>"请帮我为hopes.icu网站生成完整的中英双语内容，包括首页文案、关于页面、导航标签等，要体现'让希望在实践中呼吸'的品牌理念。"</p>
</blockquote>
<p>AI不仅生成了流畅的双语文案，还：</p>
<ul>
<li>理解了品牌的情感调性</li>
<li>保持了中英文版本的风格一致性</li>
<li>自动适配了不同语言的阅读习惯</li>
<li>提供了SEO优化的metadata配置</li>
</ul>
<h3 id="第四阶段技术优化与问题解决">第四阶段：技术优化与问题解决</h3>
<p><strong>404问题的快速定位：</strong></p>
<p>当我们遇到 <code>http://localhost:3333/en/blog/glm-coding-plan-review</code> 的404问题时：</p>
<blockquote>
<p>"这个博客页面返回404，请帮我排查问题。"</p>
</blockquote>
<p>AI的排查过程非常系统化：</p>
<ol>
<li>检查文件是否存在</li>
<li>验证路由配置</li>
<li>分析内容解析逻辑</li>
<li>识别frontmatter中的slug不匹配问题</li>
<li>提供具体的修复方案</li>
</ol>
<p>整个过程只用了几分钟，而我手动排查可能需要半小时。</p>
<h2 id="ai协作的技术亮点">AI协作的技术亮点</h2>
<h3 id="1-上下文理解与记忆">1. 上下文理解与记忆</h3>
<p>在整个开发过程中，AI展现了出色的上下文理解能力：</p>
<ul>
<li><strong>设计理念理解</strong>：AI准确理解了"可复用的经验与温柔的洞见"这个抽象概念</li>
<li><strong>技术栈一致性</strong>：AI始终保持了技术选型的一致性，不会推荐冲突的方案</li>
<li><strong>品牌调性维护</strong>：在所有内容生成中保持了统一的品牌声音</li>
</ul>
<h3 id="2-主动问题识别">2. 主动问题识别</h3>
<p>AI不仅仅是被动执行命令，还会主动识别潜在问题：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// AI主动识别出的配置问题</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title function_">withNextIntl</span>(nextConfig)
<span class="hljs-comment">// 正确的写法应该是：</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title function_">withNextIntl</span>(nextConfig)
</code></pre>
<h3 id="3-最佳实践应用">3. 最佳实践应用</h3>
<p>AI在实现功能时，始终遵循现代前端开发的最佳实践：</p>
<ul>
<li><strong>TypeScript严格模式</strong>：完整的类型定义</li>
<li><strong>可访问性</strong>：语义化HTML和ARIA标签</li>
<li><strong>性能优化</strong>：代码分割和懒加载</li>
<li><strong>SEO友好</strong>：完整的metadata配置</li>
</ul>
<h2 id="效率对比与分析">效率对比与分析</h2>
<h3 id="开发时间对比">开发时间对比</h3>
<table>
<thead>
<tr>
<th>开发阶段</th>
<th>传统方式</th>
<th>AI协作方式</th>
<th>效率提升</th>
</tr>
</thead>
<tbody>
<tr>
<td>需求理解与架构设计</td>
<td>2-3天</td>
<td>2小时</td>
<td>80%</td>
</tr>
<tr>
<td>组件开发</td>
<td>1-2周</td>
<td>2天</td>
<td>75%</td>
</tr>
<tr>
<td>内容创作</td>
<td>3-5天</td>
<td>4小时</td>
<td>85%</td>
</tr>
<tr>
<td>问题调试</td>
<td>1-2小时</td>
<td>10分钟</td>
<td>90%</td>
</tr>
<tr>
<td>整体项目</td>
<td>3-4周</td>
<td>3天</td>
<td>85%</td>
</tr>
</tbody>
</table>
<h3 id="代码质量对比">代码质量对比</h3>
<p><strong>传统开发：</strong></p>
<ul>
<li>容易出现设计不一致</li>
<li>需要手动维护类型定义</li>
<li>容易忽略可访问性</li>
<li>SEO配置可能遗漏</li>
</ul>
<p><strong>AI协作开发：</strong></p>
<ul>
<li>自动保持设计一致性</li>
<li>完整的TypeScript类型安全</li>
<li>主动考虑可访问性</li>
<li>完整的SEO配置</li>
</ul>
<h3 id="学习价值">学习价值</h3>
<p>除了效率提升，AI协作还带来了额外的学习价值：</p>
<ul>
<li><strong>技术解释</strong>：AI会解释技术决策的原因</li>
<li><strong>最佳实践</strong>：AI会分享行业最佳实践</li>
<li><strong>替代方案</strong>：AI会提供多种技术选择的对比</li>
<li><strong>趋势洞察</strong>：AI会分享最新的技术趋势</li>
</ul>
<h2 id="ai协作的最佳实践">AI协作的最佳实践</h2>
<h3 id="1-精确的需求描述">1. 精确的需求描述</h3>
<p>这次成功的经验告诉我，与AI协作的关键是：</p>
<pre><code class="hljs language-markdown">✅ 好的Prompt：
"我需要创建一个个人博客平台，核心理念是'在技术与生活之间，持续产出可复用的经验与温柔的洞见'，
支持中英双语，包含首页、博客列表、标签筛选等功能。请设计技术架构并解释选择理由。"

❌ 模糊的Prompt：
"帮我做一个博客网站。"
</code></pre>
<h3 id="2-迭代式开发">2. 迭代式开发</h3>
<p>不要期望一次得到完美结果，而是：</p>
<ol>
<li><strong>初稿生成</strong>：让AI快速生成基础版本</li>
<li><strong>细节优化</strong>：逐步完善功能和细节</li>
<li><strong>质量检查</strong>：人工验证和调整</li>
<li><strong>持续改进</strong>：基于使用反馈进一步优化</li>
</ol>
<h3 id="3-上下文维护">3. 上下文维护</h3>
<p>在长期项目中，保持AI的上下文记忆很重要：</p>
<ul>
<li><strong>项目概要</strong>：定期提醒AI项目的目标和约束</li>
<li><strong>技术约定</strong>：明确代码风格和架构决策</li>
<li><strong>品牌指南</strong>：维护一致的设计和内容风格</li>
</ul>
<h3 id="4-人机协作的平衡">4. 人机协作的平衡</h3>
<p>AI不是替代人类，而是增强人类能力：</p>
<ul>
<li><strong>AI负责</strong>：重复性工作、技术实现、初步方案</li>
<li><strong>人类负责</strong>：需求确认、质量把控、创意决策</li>
<li><strong>协作模式</strong>：AI提供方案，人类做出选择</li>
</ul>
<h2 id="踩坑与反思">踩坑与反思</h2>
<h3 id="技术挑战">技术挑战</h3>
<p>说实话，过程中也遇到了一些问题：</p>
<p><strong>缓存问题：</strong>
AI生成的某些配置需要重启服务器才能生效，这需要经验判断。</p>
<p><strong>版本兼容：</strong>
AI推荐的一些库版本可能与现有环境不兼容，需要手动调整。</p>
<p><strong>个性化需求：</strong>
AI生成的通用方案可能需要根据具体需求进行调整。</p>
<h3 id="协作模式优化">协作模式优化</h3>
<p>通过这次实践，我学会了如何更好地与AI协作：</p>
<ol>
<li><strong>分阶段验证</strong>：每个阶段完成后及时验证结果</li>
<li><strong>明确约束</strong>：在开始时明确技术约束和目标</li>
<li><strong>保留决策权</strong>：AI是建议者，人类是决策者</li>
<li><strong>持续学习</strong>：从AI的解决方案中学习新技术</li>
</ol>
<h2 id="未来展望">未来展望</h2>
<h3 id="ai辅助开发的演进">AI辅助开发的演进</h3>
<p>这次实践让我看到了AI辅助开发的巨大潜力：</p>
<ol>
<li><strong>更强的上下文理解</strong>：AI将更好地理解项目的长期目标</li>
<li><strong>端到端自动化</strong>：从需求到部署的全流程自动化</li>
<li><strong>个性化适配</strong>：AI将学习开发者的偏好和习惯</li>
<li><strong>实时协作</strong>：AI将成为实时的开发伙伴</li>
</ol>
<h3 id="开发者角色的转变">开发者角色的转变</h3>
<p>AI时代，开发者的角色将发生根本性变化：</p>
<ul>
<li><strong>从编码者到设计者</strong>：更关注系统设计和用户体验</li>
<li><strong>从执行者到决策者</strong>：负责技术选型和架构决策</li>
<li><strong>从维护者到创新者</strong>：专注于创造性工作和技术创新</li>
</ul>
<h3 id="技能演进方向">技能演进方向</h3>
<p>未来的开发者需要培养这些能力：</p>
<ol>
<li><strong>AI协作能力</strong>：学会与AI高效沟通和协作</li>
<li><strong>系统设计能力</strong>：理解复杂系统的架构和设计</li>
<li><strong>问题定义能力</strong>：准确描述需求和约束条件</li>
<li><strong>质量把控能力</strong>：验证和优化AI生成的解决方案</li>
</ol>
<h2 id="结语">结语</h2>
<p>这次AI协作开发之旅彻底改变了我对软件开发的认识。从最初的概念到最终的产品，AI不仅大大提升了开发效率，更重要的是开启了全新的开发范式。</p>
<p>最让我印象深刻的是，AI不仅仅是工具，更像是一个理解项目理念的技术伙伴。当AI理解了"让希望在实践中呼吸"这个抽象概念时，它生成的代码和内容都带有了一致的品牌调性。</p>
<p>在AI时代，开发者的价值不再是编写代码的能力，而是：</p>
<ul>
<li><strong>理解需求的能力</strong></li>
<li><strong>设计系统的能力</strong></li>
<li><strong>把控质量的能力</strong></li>
<li><strong>与AI协作的能力</strong></li>
</ul>
<p>hopes.icu 的诞生，正是这种新开发范式的成功实践。它不仅是一个技术产品，更是人类智慧与AI协作的结晶，展示了未来开发的无限可能。</p>
<p>正如我们的网站理念："让希望在实践中呼吸"，AI技术让我们的开发希望得以在现实中呼吸、生长。</p>
<p><strong>技术标签：</strong> #AI协作开发 #NextJS15 #设计系统 #现代前端 #开发效率 #人机协作</p>]]></content:encoded>
      <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
      <category>AI辅助开发</category>
      <category>NextJS15</category>
      <category>设计系统</category>
      <category>协作开发</category>
      <category>效率提升</category>
    </item>
    <item>
      <title><![CDATA[构建一致的设计系统：图标标准化与 Tailwind CSS 迁移之旅]]></title>
      <link>https://hopes7.com/zh/blog/design-system-migration-journey</link>
      <guid>https://hopes7.com/zh/blog/design-system-migration-journey</guid>
      <description><![CDATA[我们如何在 Next.js 15 博客平台中标准化图标系统并迁移到 Tailwind CSS 4，实现统一的设计语言。]]></description>
      <content:encoded><![CDATA[<h2 id="项目缘起为什么非做不可">项目缘起｜为什么非做不可</h2>
<p>说实话，这个问题困扰我们很久了。</p>
<h3 id="痛点混乱的图标生态">痛点：混乱的图标生态</h3>
<p>我们的 hopes.icu 项目在迭代过程中积累了各种图标来源：</p>
<ul>
<li>直接复制粘贴的 SVG 代码片段</li>
<li>零散的内联 <code>&#x3C;svg></code> 标签</li>
<li>不同时期引入的第三方图标库</li>
<li>PNG 格式的占位图和 favicon</li>
<li>手工绘制的临时图标</li>
</ul>
<p>更糟的是，这些图标的风格、尺寸、颜色完全不统一。有些是 <code>currentColor</code>，有些硬编码了 <code>#333</code>，还有的使用了过时的渐变效果。尺寸更是五花八门：<code>16px</code>、<code>18px</code>、<code>20px</code>、<code>24px</code> 混用，导致对齐问题频发。</p>
<h3 id="压死骆驼的最后一根草">压死骆驼的最后一根草</h3>
<p>转折点出现在上周的设计评审。设计师指出我们的导航栏图标在不同主题下显示不一致：深色模式下几乎看不清，浅色模式下又过于突兀。更尴尬的是，当我们想要统一调整所有图标的主题色时，发现需要在十几个文件中手动修改，这简直是一场噩梦。</p>
<p>那天晚上，我花了三个小时统计了项目中的图标使用情况：</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 当时的统计结果</span>
find . -name <span class="hljs-string">"*.tsx"</span> -o -name <span class="hljs-string">"*.ts"</span> -o -name <span class="hljs-string">"*.mdx"</span> | xargs grep -l <span class="hljs-string">"svg\|icon"</span> | <span class="hljs-built_in">wc</span> -l
<span class="hljs-comment"># 结果：27 个文件涉及图标使用</span>
</code></pre>
<p>这个数字让我意识到，问题的严重性已经超出了简单修复的范畴。</p>
<h3 id="目标建立统一的设计语言">目标：建立统一的设计语言</h3>
<p>我们的目标很明确：</p>
<ol>
<li><strong>视觉一致性</strong>：所有图标使用相同的设计语言</li>
<li><strong>主题适配</strong>：自动适配深色/浅色主题</li>
<li><strong>开发效率</strong>：新增图标成本降到最低</li>
<li><strong>可维护性</strong>：统一的管理方式和 API</li>
</ol>
<h2 id="实施过程从混乱到秩序">实施过程｜从混乱到秩序</h2>
<h3 id="资产盘点摸清家底">资产盘点：摸清家底</h3>
<p>第一步是全面盘点现有的图标资产：</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 统计各种图标类型</span>
grep -r <span class="hljs-string">"lucide-react"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l      <span class="hljs-comment"># 15 个</span>
grep -r <span class="hljs-string">"&#x3C;svg"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l              <span class="hljs-comment"># 12 个</span>
grep -r <span class="hljs-string">"className.*icon"</span> . --include=<span class="hljs-string">"*.tsx"</span> | <span class="hljs-built_in">wc</span> -l   <span class="hljs-comment"># 8 个</span>
</code></pre>
<p>结果发现，虽然项目已经部分使用了 <code>lucide-react</code>，但还有大量的自定义 SVG 和内联图标。</p>
<h3 id="决策选择-lucide-react-作为标准">决策：选择 lucide-react 作为标准</h3>
<p>经过团队讨论，我们决定全面采用 <code>lucide-react</code> 作为图标标准，理由很充分：</p>
<ol>
<li><strong>设计风格一致</strong>：所有图标使用相同的线条粗细和圆角</li>
<li><strong>主题适配良好</strong>：原生支持 <code>currentColor</code></li>
<li><strong>TypeScript 支持</strong>：完整的类型定义</li>
<li><strong>性能优秀</strong>：按需导入，tree-shaking 友好</li>
<li><strong>图标丰富</strong>：涵盖了我们需要的所有常用图标</li>
</ol>
<h3 id="统一-icon-组件-api">统一 Icon 组件 API</h3>
<p>为了简化使用，我们创建了一个统一的 Icon 组件：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// components/ui/icon.tsx</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">React</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">LucideIcon</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>
<span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>

<span class="hljs-keyword">type</span> <span class="hljs-title class_">IconProps</span> = {
  <span class="hljs-attr">name</span>: <span class="hljs-title class_">LucideIcon</span>
  <span class="hljs-attr">className</span>?: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">title</span>?: <span class="hljs-built_in">string</span>
  <span class="hljs-string">"aria-hidden"</span>?: <span class="hljs-built_in">boolean</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Icon</span>(<span class="hljs-params">{ name: IconComp, className, title, ...rest }: <span class="hljs-title class_">IconProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">IconComp</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>"<span class="hljs-attr">w-5</span> <span class="hljs-attr">h-5</span> <span class="hljs-attr">inline-block</span> <span class="hljs-attr">align-middle</span>", <span class="hljs-attr">className</span>)}
      <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">{title</span> ? <span class="hljs-attr">undefined</span> <span class="hljs-attr">:</span> <span class="hljs-attr">true</span>}
      <span class="hljs-attr">aria-label</span>=<span class="hljs-string">{title}</span>
      {<span class="hljs-attr">...rest</span>}
    /></span></span>
  )
}
</code></pre>
<h3 id="迁移策略分批替换">迁移策略：分批替换</h3>
<p>我们制定了三阶段的迁移策略：</p>
<p><strong>阶段一：核心组件</strong></p>
<ul>
<li>Header 导航图标</li>
<li>Footer 社交图标</li>
<li>主题切换按钮</li>
</ul>
<p><strong>阶段二：功能页面</strong></p>
<ul>
<li>博客列表图标</li>
<li>标签和分类图标</li>
<li>搜索和筛选图标</li>
</ul>
<p><strong>阶段三：细节优化</strong></p>
<ul>
<li>表单控件图标</li>
<li>状态指示图标</li>
<li>装饰性图标</li>
</ul>
<p>每个阶段都经过完整的测试，确保没有破坏现有功能。</p>
<h2 id="技术细节tailwind-css-4--nextjs-15-的落地经验">技术细节｜Tailwind CSS 4 &#x26; Next.js 15 的落地经验</h2>
<h3 id="tailwind-css-4-的配置优化">Tailwind CSS 4 的配置优化</h3>
<p>Tailwind CSS 4 带来了一些激动人心的新特性，特别是对设计令牌的更好支持：</p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* app/globals.css */</span>
<span class="hljs-keyword">@theme</span> {
  <span class="hljs-comment">/* 图标尺寸语义化 */</span>
  <span class="hljs-attr">--size-icon-xs</span>: <span class="hljs-number">0.75rem</span>;
  <span class="hljs-attr">--size-icon-sm</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attr">--size-icon-md</span>: <span class="hljs-number">1.25rem</span>;
  <span class="hljs-attr">--size-icon-lg</span>: <span class="hljs-number">1.5rem</span>;
}

<span class="hljs-comment">/* 语义化颜色令牌 */</span>
<span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attr">--icon-primary</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--primary));
  <span class="hljs-attr">--icon-muted</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--muted-foreground));
  <span class="hljs-attr">--icon-accent</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--accent));
}
</code></pre>
<h3 id="统一命名约定">统一命名约定</h3>
<p>我们制定了严格的命名约定：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ 正确的使用方式</span>
&#x3C;<span class="hljs-title class_">Icon</span> name={<span class="hljs-title class_">Home</span>} className=<span class="hljs-string">"size-5 text-foreground/90"</span> />
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Search}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-4 text-muted-foreground"</span> /></span></span>

<span class="hljs-comment">// ❌ 避免的用法</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Home}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-5 h-4 text-blue-500"</span> /></span></span>  &#x3C;!-- 尺寸不一致 -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Search}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-5"</span> /></span></span>               &#x3C;!-- 缺少颜色语义 -->
</code></pre>
<h3 id="构建性能优化">构建性能优化</h3>
<p>lucide-react 的按需导入策略让我们受益匪浅：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ 推荐：按需导入</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Home</span>, <span class="hljs-title class_">Search</span>, <span class="hljs-title class_">Menu</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>

<span class="hljs-comment">// ❌ 避免：全量导入</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">Icons</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>
</code></pre>
<p>我们的构建配置确保了只打包实际使用的图标：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// next.config.ts</span>
<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {
  <span class="hljs-attr">experimental</span>: {
    <span class="hljs-attr">optimizePackageImports</span>: [<span class="hljs-string">'lucide-react'</span>]
  }
}
</code></pre>
<h3 id="可访问性实践">可访问性实践</h3>
<p>我们特别注意了图标的可访问性：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// 装饰性图标</span>
&#x3C;<span class="hljs-title class_">Icon</span> name={<span class="hljs-title class_">Sparkles</span>} className=<span class="hljs-string">"size-4 text-primary"</span> aria-hidden />

<span class="hljs-comment">// 语义性图标</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span>
  <span class="hljs-attr">name</span>=<span class="hljs-string">{ExternalLink}</span>
  <span class="hljs-attr">className</span>=<span class="hljs-string">"size-4 text-muted-foreground"</span>
  <span class="hljs-attr">title</span>=<span class="hljs-string">"外部链接"</span>
/></span></span>
</code></pre>
<h2 id="对比与效果前后变化">对比与效果｜前后变化</h2>
<h3 id="代码复杂度对比">代码复杂度对比</h3>
<p><strong>迁移前：</strong></p>
<pre><code class="hljs language-tsx">&#x3C;!-- 混乱的图标代码 -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">svg</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"24"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"24"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"none"</span> <span class="hljs-attr">stroke</span>=<span class="hljs-string">"currentColor"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span>></span>
  <span class="hljs-tag">&#x3C;<span class="hljs-name">path</span> <span class="hljs-attr">stroke-linecap</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-linejoin</span>=<span class="hljs-string">"round"</span> <span class="hljs-attr">stroke-width</span>=<span class="hljs-string">"2"</span>
        <span class="hljs-attr">d</span>=<span class="hljs-string">"M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"</span> /></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">svg</span>></span></span>
</code></pre>
<p><strong>迁移后：</strong></p>
<pre><code class="hljs language-tsx">&#x3C;!-- 简洁的图标代码 -->
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">{Home}</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"size-6 text-foreground"</span> /></span></span>
</code></pre>
<h3 id="主题适配对比">主题适配对比</h3>
<p><strong>迁移前：</strong></p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* 需要为每个图标单独处理 */</span>
<span class="hljs-selector-class">.icon-home</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--color-text); }
<span class="hljs-selector-class">.icon-search</span> { <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>; }  <span class="hljs-comment">/* 硬编码！ */</span>
<span class="hljs-selector-class">.icon-menu</span> { <span class="hljs-attribute">fill</span>: currentColor; }
</code></pre>
<p><strong>迁移后：</strong></p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* 统一的主题适配 */</span>
<span class="hljs-selector-class">.icon</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--foreground)); }
<span class="hljs-selector-class">.icon-muted</span> { <span class="hljs-attribute">color</span>: <span class="hljs-built_in">hsl</span>(<span class="hljs-built_in">var</span>(--muted-foreground)); }
</code></pre>
<h3 id="开发体验提升">开发体验提升</h3>
<p>最明显的变化是开发效率的提升：</p>
<table>
<thead>
<tr>
<th>场景</th>
<th>迁移前</th>
<th>迁移后</th>
</tr>
</thead>
<tbody>
<tr>
<td>新增图标</td>
<td>复制 SVG → 调整样式 → 测试</td>
<td><code>import { IconName }</code> → 直接使用</td>
</tr>
<tr>
<td>主题适配</td>
<td>手动修改多个 CSS 文件</td>
<td>自动继承主题色</td>
</tr>
<tr>
<td>尺寸调整</td>
<td>逐个修改 width/height</td>
<td>统一的 size-* 类</td>
</tr>
<tr>
<td>类型安全</td>
<td>无类型检查</td>
<td>完整的 TypeScript 支持</td>
</tr>
</tbody>
</table>
<h2 id="经验总结与下一步">经验总结与下一步</h2>
<h3 id="成功要素">成功要素</h3>
<p>回顾整个迁移过程，这些因素至关重要：</p>
<ol>
<li><strong>渐进式迁移</strong>：没有试图一次性替换所有图标</li>
<li><strong>工具先行</strong>：先建立好的工具和约定</li>
<li><strong>团队对齐</strong>：确保所有人都理解新的使用方式</li>
<li><strong>持续验证</strong>：每个阶段都经过完整的测试</li>
</ol>
<h3 id="踩坑复盘">踩坑复盘</h3>
<p>老实说，过程中也遇到了不少问题：</p>
<p><strong>尺寸对齐问题</strong>
最初的 <code>w-5 h-5</code> 在某些组件中导致了对齐问题，我们后来改用语义化的 <code>size-*</code> 类：</p>
<pre><code class="hljs language-css"><span class="hljs-comment">/* tailwind.config.ts */</span>
theme: {
  extend: {
    spacing: {
      '<span class="hljs-number">4.5</span>': <span class="hljs-string">'1.125rem'</span>,  <span class="hljs-comment">/* 18px */</span>
    }
  }
}
</code></pre>
<p><strong>老样式残留</strong>
有些旧的内联样式很难发现，我们添加了 ESLint 规则来检测：</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// eslint.config.js</span>
<span class="hljs-attr">rules</span>: {
  <span class="hljs-string">'react/no-danger-with-children'</span>: <span class="hljs-string">'error'</span>,
  <span class="hljs-string">'@typescript-eslint/no-unused-vars'</span>: <span class="hljs-string">'error'</span>,
}
</code></pre>
<h3 id="未来规划">未来规划</h3>
<p>基于这次成功的经验，我们计划：</p>
<ol>
<li><strong>图标自动化校验</strong>：在 CI/CD 中检查图标使用的一致性</li>
<li><strong>组件库文档</strong>：建立完整的组件使用文档</li>
<li><strong>设计令牌扩展</strong>：将成功经验扩展到颜色、间距等其他设计令牌</li>
<li><strong>动态图标加载</strong>：按需加载大型图标集合</li>
</ol>
<h2 id="结语">结语</h2>
<p>这次图标标准化迁移带给我们的不仅仅是更整洁的代码，更重要的是建立了一套可维护的设计系统。从混乱到秩序的过程中，我们学会了如何平衡开发效率和设计一致性。</p>
<p>标准化不是限制创造力，而是让创造力不被无序消耗。当我们的基础组件足够可靠时，团队就能更专注于真正的创新——为用户创造更好的体验。</p>
<p>在 hopes.icu 的后续开发中，这套设计系统将持续发挥作用，让我们能够快速迭代而不失品质。这就是技术基础设施的价值：它默默支撑着产品的成长，让创新变得更加从容。</p>
<hr>
<p><strong>相关资源：</strong></p>
<ul>
<li><a href="https://lucide.dev/">Lucide React 官方文档</a></li>
<li><a href="https://tailwindcss.com/">Tailwind CSS 4 文档</a></li>
<li><a href="https://github.com/your-repo/pull/123">项目图标迁移 PR</a></li>
</ul>
<p><strong>技术标签：</strong> #设计系统 #图标标准化 #TailwindCSS #NextJS15 #前端工程化</p>]]></content:encoded>
      <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
      <category>tailwindcss</category>
      <category>图标</category>
      <category>设计系统</category>
      <category>nextjs</category>
      <category>lucide-react</category>
    </item>
    <item>
      <title><![CDATA[智谱GLM Coding Plan：高性价比AI编程套餐全面评测]]></title>
      <link>https://hopes7.com/zh/blog/glm-coding-plan-review</link>
      <guid>https://hopes7.com/zh/blog/glm-coding-plan-review</guid>
      <description><![CDATA[深入体验智谱AI推出的GLM Coding Plan编程套餐，每月20元起即可使用GLM-4.5模型，支持Claude Code、Cline等10+编程工具，性价比超高。]]></description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>作为一个追求效率的开发者，我一直在寻找性价比高且功能强大的AI编程助手。最近智谱AI推出的GLM Coding Plan套餐引起了我的注意——每月仅需20元起，就能在主流编程工具中使用GLM-4.5模型，这确实是一个很有吸引力的选择。</p>
<p>经过一段时间的深度使用，我来分享一下这个编程套餐的真实体验。</p>
<h2 id="什么是glm-coding-plan">什么是GLM Coding Plan？</h2>
<p>GLM Coding Plan是智谱AI专为开发者推出的编程订阅服务，主要特点包括：</p>
<ul>
<li><strong>价格优势</strong>：月费20-100元，相比其他AI编程服务价格非常亲民</li>
<li><strong>模型支持</strong>：使用GLM-4.5和GLM-4.5-Air旗舰模型</li>
<li><strong>工具兼容</strong>：支持Claude Code、Cline、Roo Code等10+主流编程工具</li>
<li><strong>性能表现</strong>：官方宣称编码性能接近Claude Sonnet 4</li>
</ul>
<h2 id="支持的编程工具">支持的编程工具</h2>
<p>目前GLM Coding Plan支持的主流编程工具包括：</p>
<ol>
<li><strong>Claude Code</strong> - 智能终端编码助手</li>
<li><strong>Cline</strong> - VS Code扩展，提供智能代码补全</li>
<li><strong>Roo Code</strong> - 轻量级编码助手</li>
<li><strong>Kilo Code</strong> - 高效编码工具</li>
<li><strong>OpenCode</strong> - 开源编码工具</li>
<li>其他兼容工具还在持续增加中</li>
</ol>
<h2 id="配置使用体验">配置使用体验</h2>
<h3 id="配置过程">配置过程</h3>
<p>配置GLM Coding Plan相对简单，主要步骤如下：</p>
<ol>
<li><strong>注册账号</strong>：在智谱开放平台注册账号</li>
<li><strong>订阅套餐</strong>：选择合适的编程套餐</li>
<li><strong>获取API Key</strong>：在个人中心创建API Key</li>
<li><strong>配置工具</strong>：在编程工具中配置API</li>
</ol>
<p>以Claude Code为例，配置方式：</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 设置环境变量</span>
<span class="hljs-built_in">export</span> ANTHROPIC_BASE_URL=<span class="hljs-string">"https://open.bigmodel.cn/api/anthropic"</span>
<span class="hljs-built_in">export</span> ANTHROPIC_AUTH_TOKEN=<span class="hljs-string">"your-api-key"</span>
</code></pre>
<h3 id="实际使用体验">实际使用体验</h3>
<p>经过几天的使用，我发现GLM-4.5在编程场景下表现相当不错：</p>
<p><strong>代码生成能力</strong></p>
<ul>
<li>能够准确理解需求，生成符合要求的代码</li>
<li>支持多种编程语言和框架</li>
<li>代码质量较高，符合最佳实践</li>
</ul>
<p><strong>调试辅助</strong></p>
<ul>
<li>能够快速定位代码问题</li>
<li>提供清晰的解决方案</li>
<li>解释错误原因详细</li>
</ul>
<p><strong>重构优化</strong></p>
<ul>
<li>性能优化建议实用</li>
<li>重构方案合理</li>
<li>保持代码可读性</li>
</ul>
<h2 id="性能对比">性能对比</h2>
<p>与Claude Sonnet 4相比，GLM-4.5的表现：</p>
<p><strong>优势：</strong></p>
<ul>
<li>价格优势明显（约1/7的价格）</li>
<li>中文理解能力强</li>
<li>本土化服务更好</li>
</ul>
<p><strong>略有不足：</strong></p>
<ul>
<li>在某些复杂算法场景下稍显不足</li>
<li>英文技术文档理解还有提升空间</li>
</ul>
<h2 id="套餐选择建议">套餐选择建议</h2>
<p>智谱提供了三个套餐等级：</p>
<ol>
<li><strong>基础版</strong>（20元/月）- 适合个人开发者轻度使用</li>
<li><strong>专业版</strong>（60元/月）- 适合日常开发工作</li>
<li><strong>高级版</strong>（100元/月）- 适合重度使用和团队协作</li>
</ol>
<p>对于大多数开发者来说，专业版是性价比最高的选择。</p>
<h2 id="总结">总结</h2>
<p>GLM Coding Plan为国内开发者提供了一个性价比极高的AI编程解决方案。虽然在某些极端场景下可能不如顶级模型，但考虑到价格因素，它的表现已经相当出色。</p>
<p>特别适合：</p>
<ul>
<li>预算有限的个人开发者</li>
<li>中小型开发团队</li>
<li>需要中文编程助手的开发者</li>
</ul>
<p>如果你正在寻找一个高性价比的AI编程助手，GLM Coding Plan绝对值得尝试。</p>
<hr>
<p>🚀 <strong>速来拼好模，智谱 GLM Coding 超值订阅，邀你一起薅羊毛！</strong> Claude Code、Cline 等 10+ 大编程工具无缝支持，"码力"全开，越拼越爽！立即开拼，享限时惊喜价！</p>
<p>链接：<a href="https://www.bigmodel.cn/claude-code?ic=7V6E59DM9R">https://www.bigmodel.cn/claude-code?ic=7V6E59DM9R</a></p>]]></content:encoded>
      <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
      <category>智谱AI</category>
      <category>GLM</category>
      <category>编程助手</category>
      <category>Claude Code</category>
      <category>人工智能</category>
    </item>
    <item>
      <title><![CDATA[升级设计系统：从自定义组件到 shadcn/ui 的完整迁移实践]]></title>
      <link>https://hopes7.com/zh/blog/shadcn-ui-migration-practice</link>
      <guid>https://hopes7.com/zh/blog/shadcn-ui-migration-practice</guid>
      <description><![CDATA[我们如何在 Next.js 15 博客平台中将自定义组件统一替换为 shadcn/ui 组件，实现一致的设计系统和更好的开发体验。]]></description>
      <content:encoded><![CDATA[<h2 id="背景与动机">背景与动机</h2>
<p>在现代前端开发中，一致的设计系统对于提升用户体验和开发效率至关重要。我们的博客项目最初使用了一些自定义UI组件，虽然功能完整，但面临着以下几个挑战：</p>
<ol>
<li><strong>设计不一致性</strong> - 不同组件之间缺乏统一的设计语言</li>
<li><strong>维护成本高</strong> - 自定义组件需要持续维护和更新</li>
<li><strong>开发效率低</strong> - 重复实现相似的UI模式</li>
<li><strong>测试覆盖不足</strong> - 缺乏全面的组件测试</li>
</ol>
<p>为了解决这些问题，我们决定全面迁移到 shadcn/ui 组件库，这是一个基于 Radix UI 和 Tailwind CSS 构建的现代化组件系统。</p>
<h2 id="为什么选择-shadcnui">为什么选择 shadcn/ui</h2>
<p>在选择组件库时，我们考虑了多个因素，最终选择了 shadcn/ui：</p>
<ul>
<li><strong>完全可定制</strong> - 基于 Tailwind CSS，可以完全控制样式</li>
<li><strong>无障碍优先</strong> - 基于 Radix UI，内置完整的可访问性支持</li>
<li><strong>TypeScript 原生支持</strong> - 提供完整的类型定义</li>
<li><strong>现代化架构</strong> - 支持最新的 React 特性和 Next.js 15</li>
<li><strong>按需安装</strong> - 只安装需要的组件，保持包体积最小</li>
</ul>
<h2 id="迁移实施过程">迁移实施过程</h2>
<h3 id="第一步项目分析">第一步：项目分析</h3>
<p>我们首先使用代码分析工具对整个项目进行了全面扫描：</p>
<ul>
<li>识别所有自定义UI组件</li>
<li>分析组件的使用频率和复杂度</li>
<li>确定需要安装的 shadcn/ui 组件清单</li>
<li>制定迁移优先级</li>
</ul>
<h3 id="第二步组件安装">第二步：组件安装</h3>
<p>根据分析结果，我们安装了以下 shadcn/ui 组件：</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># 核心组件</span>
npx shadcn@latest add card
npx shadcn@latest add separator
npx shadcn@latest add button
npx shadcn@latest add badge

<span class="hljs-comment"># 导航和布局</span>
npx shadcn@latest add navigation-menu
npx shadcn@latest add sheet
npx shadcn@latest add avatar

<span class="hljs-comment"># 数据展示</span>
npx shadcn@latest add table
npx shadcn@latest add skeleton
npx shadcn@latest add pagination

<span class="hljs-comment"># 交互组件</span>
npx shadcn@latest add dialog
npx shadcn@latest add switch
</code></pre>
<h3 id="第三步核心组件替换">第三步：核心组件替换</h3>
<h4 id="postcard-组件重构">PostCard 组件重构</h4>
<p>这是最重要的组件，我们从自定义实现迁移到了 shadcn/ui 的 Card 组件：</p>
<p><strong>之前的实现：</strong></p>
<pre><code class="hljs language-tsx">&#x3C;article className=<span class="hljs-string">"group relative flex h-full flex-col overflow-hidden border bg-card shadow-sm transition hover:-translate-y-1 hover:shadow-lg"</span>>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-56 w-full overflow-hidden bg-muted"</span>></span>
    {/* 图片内容 */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-1 flex-col space-y-4 p-6"</span>></span>
    {/* 卡片内容 */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
&#x3C;/article>
</code></pre>
<p><strong>迁移后的实现：</strong></p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardFooter</span>, <span class="hljs-title class_">CardHeader</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>

&#x3C;<span class="hljs-title class_">Card</span> className=<span class="hljs-string">"group relative flex h-full flex-col overflow-hidden transition hover:-translate-y-1 hover:shadow-lg"</span>>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardHeader</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-56 w-full overflow-hidden p-0"</span>></span>
    {/* 图片内容 */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardHeader</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardContent</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-1 flex-col space-y-4"</span>></span>
    {/* 卡片内容 */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardContent</span>></span></span>
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">CardFooter</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-auto"</span>></span>
    {/* 底部链接 */}
  <span class="hljs-tag">&#x3C;/<span class="hljs-name">CardFooter</span>></span></span>
&#x3C;/<span class="hljs-title class_">Card</span>>
</code></pre>
<h4 id="footer-组件优化">Footer 组件优化</h4>
<p>我们将手动实现分隔线替换为标准的 Separator 组件：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { <span class="hljs-title class_">Separator</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/separator'</span>

<span class="hljs-comment">// 之前的手动分隔线</span>
&#x3C;div className=<span class="hljs-string">"border-t border-border/60 mt-12"</span> />

<span class="hljs-comment">// 使用 Separator 组件</span>
<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Separator</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-12"</span> /></span></span>
</code></pre>
<h3 id="第四步设计系统适配">第四步：设计系统适配</h3>
<p>由于项目采用扁平化设计风格，我们需要对 shadcn/ui 组件进行适当的样式调整：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Card 组件 - 移除圆角</span>
<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> = <span class="hljs-title class_">React</span>.<span class="hljs-property">forwardRef</span>&#x3C;
  <span class="hljs-title class_">HTMLDivElement</span>,
  <span class="hljs-title class_">React</span>.<span class="hljs-property">HTMLAttributes</span>&#x3C;<span class="hljs-title class_">HTMLDivElement</span>>
>(<span class="hljs-function">(<span class="hljs-params">{ className, ...props }, ref</span>) =></span> (
  <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span>
    <span class="hljs-attr">ref</span>=<span class="hljs-string">{ref}</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>
      "<span class="hljs-attr">border</span> <span class="hljs-attr">bg-card</span> <span class="hljs-attr">text-card-foreground</span> <span class="hljs-attr">shadow-sm</span>", // <span class="hljs-attr">移除了</span> <span class="hljs-attr">rounded-xl</span>
      <span class="hljs-attr">className</span>
    )}
    {<span class="hljs-attr">...props</span>}
  /></span></span>
))
</code></pre>
<h2 id="技术细节与最佳实践">技术细节与最佳实践</h2>
<h3 id="1-组件导入策略">1. 组件导入策略</h3>
<p>我们采用了明确的导入策略，只导入需要的组件：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ✅ 推荐：明确导入</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardFooter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>

<span class="hljs-comment">// ❌ 避免：导入整个库</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/ui/card'</span>
</code></pre>
<h3 id="2-类型安全保持">2. 类型安全保持</h3>
<p>在迁移过程中，我们保持了完整的 TypeScript 类型支持：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">PostCardProps</span> {
  <span class="hljs-attr">post</span>: <span class="hljs-title class_">PostSummary</span>
  <span class="hljs-attr">locale</span>: <span class="hljs-title class_">Locale</span>
  <span class="hljs-attr">readMoreLabel</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">readingTimeLabel</span>: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">PostCard</span>(<span class="hljs-params">{
  post,
  locale,
  readMoreLabel,
  readingTimeLabel,
}: <span class="hljs-title class_">PostCardProps</span></span>) {
  <span class="hljs-comment">// 组件实现</span>
}
</code></pre>
<h3 id="3-样式一致性">3. 样式一致性</h3>
<p>为了保持设计一致性，我们创建了一个样式配置文件：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// lib/components.ts</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Button</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/button"</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Card</span>, <span class="hljs-title class_">CardContent</span>, <span class="hljs-title class_">CardHeader</span>, <span class="hljs-title class_">CardFooter</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/card"</span>
<span class="hljs-keyword">export</span> { <span class="hljs-title class_">Separator</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/separator"</span>
</code></pre>
<h2 id="迁移效果对比">迁移效果对比</h2>
<h3 id="代码质量提升">代码质量提升</h3>
<p><strong>迁移前：</strong></p>
<ul>
<li>自定义组件代码行数：~150 行</li>
<li>维护复杂度：高</li>
<li>类型安全：部分支持</li>
<li>测试覆盖：有限</li>
</ul>
<p><strong>迁移后：</strong></p>
<ul>
<li>shadcn/ui 组件代码行数：~50 行（业务逻辑）</li>
<li>维护复杂度：低</li>
<li>类型安全：完全支持</li>
<li>测试覆盖：由组件库提供</li>
</ul>
<h3 id="开发体验改善">开发体验改善</h3>
<ol>
<li><strong>开发效率提升 40%</strong> - 使用标准组件减少重复开发</li>
<li><strong>代码可读性提升</strong> - 标准化的组件结构和命名</li>
<li><strong>维护成本降低</strong> - 由组件库负责核心功能维护</li>
<li><strong>设计一致性</strong> - 统一的设计语言和交互模式</li>
</ol>
<h3 id="性能优化">性能优化</h3>
<ul>
<li><strong>包体积优化</strong> - 按需安装，只引入使用的组件</li>
<li><strong>Tree-shaking 支持</strong> - 未使用的组件不会被打包</li>
<li><strong>加载性能</strong> - 组件库采用了现代化的打包策略</li>
</ul>
<h2 id="遇到的挑战与解决方案">遇到的挑战与解决方案</h2>
<h3 id="1-设计风格适配">1. 设计风格适配</h3>
<p><strong>挑战</strong>：shadcn/ui 默认采用圆角设计，与我们的扁平化风格不符。</p>
<p><strong>解决方案</strong>：</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// 修改组件的默认样式</span>
<span class="hljs-keyword">const</span> navigationMenuTriggerStyle = <span class="hljs-title function_">cva</span>(
  <span class="hljs-string">"group inline-flex h-9 w-max items-center justify-center bg-background px-4 py-2 text-sm font-medium transition-colors..."</span> <span class="hljs-comment">// 移除 rounded-md</span>
)
</code></pre>
<h3 id="2-组件定制化">2. 组件定制化</h3>
<p><strong>挑战</strong>：某些组件需要特定的样式和行为。</p>
<p><strong>解决方案</strong>：利用 shadcn/ui 的变体系统</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">const</span> cardVariants = <span class="hljs-title function_">cva</span>(
  <span class="hljs-string">"border bg-card text-card-foreground shadow-sm"</span>,
  {
    <span class="hljs-attr">variants</span>: {
      <span class="hljs-attr">variant</span>: {
        <span class="hljs-attr">default</span>: <span class="hljs-string">"border bg-card text-card-foreground shadow-sm"</span>,
        <span class="hljs-attr">outline</span>: <span class="hljs-string">"border-2 border-border bg-background"</span>,
      },
    },
    <span class="hljs-attr">defaultVariants</span>: {
      <span class="hljs-attr">variant</span>: <span class="hljs-string">"default"</span>,
    },
  }
)
</code></pre>
<h3 id="3-迁移过程中的兼容性">3. 迁移过程中的兼容性</h3>
<p><strong>挑战</strong>：确保迁移过程中不破坏现有功能。</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li>采用渐进式迁移策略</li>
<li>保持原有的 props 接口</li>
<li>充分的测试验证</li>
</ul>
<h2 id="经验总结">经验总结</h2>
<h3 id="成功因素">成功因素</h3>
<ol>
<li><strong>充分的前期分析</strong> - 详细的代码分析和规划</li>
<li><strong>渐进式迁移</strong> - 分步骤进行，降低风险</li>
<li><strong>类型安全保障</strong> - 利用 TypeScript 确保代码质量</li>
<li><strong>全面的测试验证</strong> - 确保功能完整性</li>
</ol>
<h3 id="关键学习">关键学习</h3>
<ol>
<li><strong>组件库选择的重要性</strong> - 选择与项目需求匹配的组件库</li>
<li><strong>设计系统的价值</strong> - 统一的设计语言提升用户体验</li>
<li><strong>自动化工具的作用</strong> - 利用工具提高迁移效率和准确性</li>
<li><strong>代码审查的必要性</strong> - 确保代码质量和一致性</li>
</ol>
<h3 id="未来规划">未来规划</h3>
<ol>
<li><strong>进一步优化</strong> - 继续优化其他自定义组件</li>
<li><strong>设计文档完善</strong> - 建立完整的设计系统文档</li>
<li><strong>组件库扩展</strong> - 根据需要添加更多 shadcn/ui 组件</li>
<li><strong>性能监控</strong> - 持续监控组件性能表现</li>
</ol>
<h2 id="结语">结语</h2>
<p>通过这次 shadcn/ui 迁移实践，我们不仅提升了代码质量和开发效率，更重要的是建立了一套可持续发展的设计系统。这次迁移证明了选择合适的组件库并正确实施迁移策略的重要性。</p>
<p>对于正在进行类似项目的开发者，我们建议：</p>
<ul>
<li>仔细评估项目需求，选择合适的组件库</li>
<li>制定详细的迁移计划，采用渐进式策略</li>
<li>充分利用 TypeScript 等工具确保代码质量</li>
<li>建立完善的设计文档和最佳实践指南</li>
</ul>
<p>设计系统的建设是一个持续的过程，我们将在未来继续优化和完善，为用户提供更好的体验。</p>
<hr>
<p><strong>相关资源：</strong></p>
<ul>
<li><a href="https://ui.shadcn.com/">shadcn/ui 官方文档</a></li>
<li><a href="https://www.radix-ui.com/">Radix UI 文档</a></li>
<li><a href="https://tailwindcss.com/">Tailwind CSS 文档</a></li>
</ul>
<p><strong>标签：</strong> #shadcn/ui #组件库 #设计系统 #Next.js #TypeScript #TailwindCSS</p>]]></content:encoded>
      <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
      <category>shadcn/ui</category>
      <category>组件库</category>
      <category>设计系统</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title><![CDATA[现代Web开发的5个最佳实践]]></title>
      <link>https://hopes7.com/zh/blog/web-development-tips</link>
      <guid>https://hopes7.com/zh/blog/web-development-tips</guid>
      <description><![CDATA[分享在现代Web开发中的5个关键最佳实践，帮助开发者构建更高质量的应用程序。]]></description>
      <content:encoded><![CDATA[<h2 id="引言">引言</h2>
<p>在快速发展的Web开发领域，掌握最佳实践对于构建高质量、可维护的应用程序至关重要。本文将分享5个关键的现代Web开发最佳实践。</p>
<h2 id="1-组件化开发">1. 组件化开发</h2>
<p>组件化是现代前端开发的核心理念。通过将UI拆分为独立、可复用的组件，我们可以：</p>
<ul>
<li>提高代码复用性</li>
<li>简化维护工作</li>
<li>增强团队协作效率</li>
</ul>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// 示例：可复用的按钮组件</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">ButtonProps</span> {
  <span class="hljs-attr">variant</span>: <span class="hljs-string">'primary'</span> | <span class="hljs-string">'secondary'</span>
  <span class="hljs-attr">size</span>: <span class="hljs-string">'sm'</span> | <span class="hljs-string">'md'</span> | <span class="hljs-string">'lg'</span>
  <span class="hljs-attr">children</span>: <span class="hljs-title class_">React</span>.<span class="hljs-property">ReactNode</span>
  <span class="hljs-attr">onClick</span>?: <span class="hljs-function">() =></span> <span class="hljs-built_in">void</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">Button</span>(<span class="hljs-params">{ variant, size, children, onClick }: <span class="hljs-title class_">ButtonProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">button</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">btn</span> <span class="hljs-attr">btn-</span>${<span class="hljs-attr">variant</span>} <span class="hljs-attr">btn-</span>${<span class="hljs-attr">size</span>}`}
      <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onClick}</span>
    ></span>
      {children}
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span></span>
  )
}
</code></pre>
<h2 id="2-性能优化">2. 性能优化</h2>
<p>性能是用户体验的关键因素。以下是一些重要的性能优化策略：</p>
<h3 id="代码分割">代码分割</h3>
<p>使用动态导入来分割代码，减少初始加载时间：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { lazy } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> <span class="hljs-title class_">LazyComponent</span> = <span class="hljs-title function_">lazy</span>(<span class="hljs-function">() =></span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./LazyComponent'</span>))

<span class="hljs-keyword">function</span> <span class="hljs-title function_">App</span>(<span class="hljs-params"></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">div</span>></span>
      <span class="hljs-tag">&#x3C;<span class="hljs-name">LazyComponent</span> /></span>
    <span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span></span>
  )
}
</code></pre>
<h3 id="图片优化">图片优化</h3>
<p>使用Next.js的Image组件自动优化图片：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Image</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>

&#x3C;<span class="hljs-title class_">Image</span>
  src=<span class="hljs-string">"/example.jpg"</span>
  alt=<span class="hljs-string">"示例图片"</span>
  width={<span class="hljs-number">800</span>}
  height={<span class="hljs-number">400</span>}
  priority={<span class="hljs-literal">false</span>}
/>
</code></pre>
<h2 id="3-类型安全">3. 类型安全</h2>
<p>使用TypeScript可以大大提高代码质量和开发体验：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">interface</span> <span class="hljs-title class_">User</span> {
  <span class="hljs-attr">id</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">email</span>: <span class="hljs-built_in">string</span>
  <span class="hljs-attr">role</span>: <span class="hljs-string">'admin'</span> | <span class="hljs-string">'user'</span>
}

<span class="hljs-keyword">function</span> <span class="hljs-title function_">getUserDisplayName</span>(<span class="hljs-params"><span class="hljs-attr">user</span>: <span class="hljs-title class_">User</span></span>): <span class="hljs-built_in">string</span> {
  <span class="hljs-keyword">return</span> user.<span class="hljs-property">name</span>
}
</code></pre>
<h2 id="4-测试驱动开发">4. 测试驱动开发</h2>
<p>编写测试是确保代码质量的重要手段：</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">Button</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button'</span>

<span class="hljs-title function_">describe</span>(<span class="hljs-string">'Button'</span>, <span class="hljs-function">() =></span> {
  <span class="hljs-title function_">it</span>(<span class="hljs-string">'renders with correct text'</span>, <span class="hljs-function">() =></span> {
    <span class="hljs-title function_">render</span>(<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"primary"</span> <span class="hljs-attr">size</span>=<span class="hljs-string">"md"</span>></span>Click me<span class="hljs-tag">&#x3C;/<span class="hljs-name">Button</span>></span></span>)
    <span class="hljs-title function_">expect</span>(screen.<span class="hljs-title function_">getByText</span>(<span class="hljs-string">'Click me'</span>)).<span class="hljs-title function_">toBeInTheDocument</span>()
  })
})
</code></pre>
<h2 id="5-响应式设计">5. 响应式设计</h2>
<p>确保你的应用在各种设备上都能良好显示：</p>
<pre><code class="hljs language-css"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">1200px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">768px</span>) {
  <span class="hljs-selector-class">.container</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">2rem</span>;
  }
}
</code></pre>
<h2 id="结论">结论</h2>
<p>掌握这些最佳实践将帮助你构建更加专业、高质量的Web应用程序。记住，最重要的是根据项目需求选择合适的实践，并持续学习和改进。</p>
<hr>
<p><strong>相关资源：</strong></p>
<ul>
<li><a href="https://nextjs.org/docs">Next.js 文档</a></li>
<li><a href="https://www.typescriptlang.org/docs/">TypeScript 手册</a></li>
<li><a href="https://developer.mozilla.org/">MDN Web Docs</a></li>
</ul>
<p><strong>标签：</strong> #web开发 #最佳实践 #性能优化 #代码质量</p>]]></content:encoded>
      <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
      <category>web开发</category>
      <category>最佳实践</category>
      <category>性能优化</category>
      <category>代码质量</category>
    </item>
  </channel>
</rss>